diff --git a/.dockerignore b/.dockerignore index 1eb5a091..43c8a7da 100644 --- a/.dockerignore +++ b/.dockerignore @@ -56,3 +56,8 @@ web-client/.env.local target gateway-rs/target release-evidence + +# Regenerated by local TypeScript/Next builds; excluding them keeps the image ID +# stable between the system E2E and release builds of the same source. +**/*.tsbuildinfo +web-client/next-env.d.ts diff --git a/.env.example b/.env.example index d8155057..bdac7a77 100644 --- a/.env.example +++ b/.env.example @@ -84,6 +84,15 @@ AUTH_REFRESH_EXPIRE_FREQ=days # after placing the service behind a TLS-terminating reverse proxy. HTTPS_ONLY=false +# Keep proxy for existing reverse-proxy deployments. Native terminates TLS in the Rust gateway. +# Native mode requires both PEM files and refuses to start if they are invalid. +DOWNSTREAM_TLS_MODE=proxy +# DOWNSTREAM_TLS_CERT_FILE=/run/secrets/doorman/tls.crt +# DOWNSTREAM_TLS_KEY_FILE=/run/secrets/doorman/tls.key +# TLS_PROFILES_FILE=/run/secrets/doorman/tls-profiles.yaml +# Base64 of exactly 32 random bytes; required for admin-uploaded TLS material on every node. +# TLS_SECRET_ENCRYPTION_KEY= + # COOKIE_DOMAIN: Base domain for authentication cookies # - If set: Cookies sent WITH domain attribute for subdomain sharing (e.g., *.example.com) # - Also sent WITHOUT domain attribute for exact domain match (dual-cookie strategy) @@ -127,7 +136,9 @@ DISCOVERY_ALLOWED_HOSTS= # Optional: Custom Content Security Policy (defaults to restrictive policy) # CONTENT_SECURITY_POLICY=default-src 'self'; script-src 'self' 'unsafe-inline' -# HTTP Client Connection Pooling (httpx) +# Upstream HTTP connection pooling +# (HTTP_MAX_CONNECTIONS is a legacy Python total cap and is ignored by the Rust gateway; +# HTTP_MAX_KEEPALIVE bounds idle connections per upstream host) ENABLE_HTTPX_CLIENT_CACHE=true HTTP_MAX_CONNECTIONS=100 HTTP_MAX_KEEPALIVE=50 @@ -137,8 +148,8 @@ HTTP_KEEPALIVE_EXPIRY=30.0 # CRITICAL: Adjust these based on expected upstream response times HTTP_CONNECT_TIMEOUT=5.0 # Time to establish connection (5s recommended) HTTP_READ_TIMEOUT=30.0 # Time to read response body (30s default, increase for slow APIs) -HTTP_WRITE_TIMEOUT=30.0 # Time to send request body (30s default) -HTTP_TIMEOUT=30.0 # Connection pool timeout (time to acquire connection) +HTTP_WRITE_TIMEOUT=30.0 # Legacy Python write timeout (ignored by the Rust gateway) +HTTP_TIMEOUT=30.0 # Legacy Python pool-acquire timeout (ignored by the Rust gateway) # Note: Timeout errors return 504 Gateway Timeout to clients # Enable HTTP/2 (default: false) @@ -157,7 +168,7 @@ LOCAL_HOST_IP_BYPASS=false # Advanced compatibility toggles (for CI/Python 3.13 environments) # Set to 'true' only if you experience middleware/ASGI issues during tests. -# - DISABLE_PLATFORM_CHUNKED_WRAP: skip low-level chunked-body wrapping on /platform/* +# - DISABLE_PLATFORM_CHUNKED_WRAP: legacy Python ASGI toggle; ignored by the Rust gateway # - DISABLE_PLATFORM_CORS_ASGI: bypass ASGI CORS shim for /platform/* # - DISABLE_BODY_SIZE_LIMIT: disable body-size limit middleware entirely DISABLE_PLATFORM_CHUNKED_WRAP=false @@ -204,6 +215,8 @@ COMPRESSION_MINIMUM_SIZE=500 COMPRESSION_LEVEL=1 # Logging +# Set false to disable stored request/audit logs and the Logs UI. +LOGS_ENABLED=true # Format: json or plain LOG_FORMAT=plain # Optional: custom logs directory (defaults to logs) @@ -227,5 +240,5 @@ PID_FILE=doorman.pid # - Separate API domain: https://api.yourdomain.com NEXT_PUBLIC_GATEWAY_URL=http://localhost:3001 -# Uses gql.Client when this flag is true and an AIOHTTPTransport is importable +# Legacy Python gql.Client toggle; the Rust gateway always proxies GraphQL over HTTP DOORMAN_ENABLE_GQL_CLIENT=True diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e56ddea1..fbbbd05c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,8 @@ jobs: python3 scripts/check_parity_reference.py - name: Verify Python-to-Rust test coverage ledger run: python3 scripts/generate_test_coverage_ledger.py --check + - name: Verify Python-to-Rust source coverage ledger + run: python3 scripts/generate_source_coverage_ledger.py --check - name: Test release tooling run: python3 -m unittest discover -s scripts -p 'test_*.py' - name: Run Clippy diff --git a/Makefile b/Makefile index 2967e4b9..7872ce36 100644 --- a/Makefile +++ b/Makefile @@ -6,11 +6,11 @@ ADMIN_PASSWORD ?= $(shell grep '^DOORMAN_ADMIN_PASSWORD=' .env 2>/dev/null | cut BASE_URL ?= http://localhost:$(PORT) GATEWAY_LOAD_BASE_URL ?= http://localhost:3001 -.PHONY: check test unit unitq rust-test rust-clippy rust-fmt-check web-audit web-build parity parity-reference parity-ledger parity-contracts parity-differential parity-performance release-check local-e2e test-e2e release-e2e e2e-plan system-e2e-smoke system-e2e system-e2e-soak system-e2e-plan system-e2e-check system-e2e-clean test-live-tcp smoke preflight live liveq gateway-load external-storage-test clean clean-deep +.PHONY: check test unit unitq rust-test rust-clippy rust-fmt-check web-audit web-build parity parity-reference parity-ledger parity-source-ledger parity-contracts parity-differential parity-performance release-check local-e2e test-e2e release-e2e e2e-plan system-e2e-smoke system-e2e system-e2e-soak system-e2e-plan system-e2e-check system-e2e-clean test-live-tcp smoke preflight live liveq gateway-load external-storage-test clean clean-deep check: rust-fmt-check rust-clippy test -parity: parity-reference parity-ledger parity-contracts +parity: parity-reference parity-ledger parity-source-ledger parity-contracts parity-reference: python3 scripts/check_parity_reference.py @@ -18,6 +18,9 @@ parity-reference: parity-ledger: python3 scripts/generate_test_coverage_ledger.py --check +parity-source-ledger: + python3 scripts/generate_source_coverage_ledger.py --check + parity-contracts: cargo test --manifest-path gateway-rs/Cargo.toml --locked --test parity_contracts --test openapi_parity --test auth_rate_parity @@ -82,7 +85,7 @@ web-build: npm --prefix web-client run build web-audit: - npm --prefix web-client audit --omit=dev --audit-level=high + npm --prefix web-client audit --omit=dev --audit-level=moderate smoke preflight live liveq: BASE_URL=$(BASE_URL) \ diff --git a/docker-compose.yml b/docker-compose.yml index 7590791e..9478e0b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,7 @@ services: MEM_AUTO_SAVE_FREQ: ${MEM_AUTO_SAVE_FREQ:-300} PYTHONINTMAXSTRDIGITS: ${PYTHONINTMAXSTRDIGITS:-4300} MEM_ENCRYPTION_KEY: ${MEM_ENCRYPTION_KEY:-} + LOGS_ENABLED: ${LOGS_ENABLED:-true} LOGS_DIR: /app/logs volumes: - doorman-generated:/app/data diff --git a/gateway-rs/Cargo.lock b/gateway-rs/Cargo.lock index eafc3df5..cf1c50cd 100644 --- a/gateway-rs/Cargo.lock +++ b/gateway-rs/Cargo.lock @@ -91,6 +91,45 @@ dependencies = [ "rustversion", ] +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "async-compression" version = "0.4.43" @@ -126,6 +165,29 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "axum" version = "0.8.9" @@ -134,6 +196,7 @@ checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "axum-macros", + "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -152,8 +215,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -336,6 +401,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -383,6 +450,15 @@ dependencies = [ "inout 0.2.2", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "cmov" version = "0.5.4" @@ -598,6 +674,20 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.8" @@ -694,6 +784,7 @@ dependencies = [ "prost-types", "protoc-bin-vendored", "quick-xml", + "rand 0.9.5", "redis", "regex", "reqwest", @@ -706,6 +797,7 @@ dependencies = [ "thiserror", "time", "tokio", + "tokio-rustls", "tonic", "tower", "tower-http", @@ -713,8 +805,15 @@ dependencies = [ "tracing-subscriber", "url", "uuid", + "x509-parser", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.17.0" @@ -803,6 +902,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -1329,6 +1434,16 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.103" @@ -1479,6 +1594,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1593,6 +1714,16 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1636,6 +1767,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1730,6 +1870,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "polyval" version = "0.7.3" @@ -2186,12 +2332,22 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustls" version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -2217,6 +2373,7 @@ version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2692,6 +2849,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.19" @@ -2735,6 +2904,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "webpki-roots", ] [[package]] @@ -2873,6 +3043,22 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "sha1", + "thiserror", +] + [[package]] name = "typed-builder" version = "0.22.0" @@ -3267,6 +3453,23 @@ dependencies = [ "tap", ] +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + [[package]] name = "yoke" version = "0.8.3" diff --git a/gateway-rs/Cargo.toml b/gateway-rs/Cargo.toml index b60d3173..1db97cdd 100644 --- a/gateway-rs/Cargo.toml +++ b/gateway-rs/Cargo.toml @@ -14,7 +14,7 @@ name = "doorman-gateway" path = "src/main.rs" [dependencies] -axum = { version = "0.8.9", features = ["http2", "macros"] } +axum = { version = "0.8.9", features = ["http2", "macros", "ws"] } base64 = "0.22" bytes = "1.10" futures-util = "0.3" @@ -25,6 +25,7 @@ mongodb = "3" prost = "0.14" prost-reflect = { version = "0.16", features = ["serde"] } quick-xml = "0.38" +rand = "0.9.5" redis = { version = "0.32", features = ["aio", "tokio-comp", "connection-manager"] } regex = "1" ryu = "1" @@ -37,13 +38,15 @@ sha2 = "0.10" thiserror = "2.0" time = { version = "0.3", features = ["formatting"] } tokio = { version = "1.48", features = ["full"] } +tokio-rustls = "0.26" tower = { version = "0.5", features = ["util"] } tower-http = { version = "0.6.8", features = ["catch-panic", "compression-gzip", "trace", "sensitive-headers"] } -tonic = { version = "0.14", features = ["transport", "tls-ring"] } +tonic = { version = "0.14", features = ["transport", "tls-ring", "tls-webpki-roots"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] } url = "2" uuid = { version = "1.18", features = ["v4"] } +x509-parser = "0.18" bcrypt = "0.19.3" aes-gcm = "0.11.0" hkdf = "0.12.4" diff --git a/gateway-rs/src/app.rs b/gateway-rs/src/app.rs index b02fcc32..211be59d 100644 --- a/gateway-rs/src/app.rs +++ b/gateway-rs/src/app.rs @@ -4,20 +4,25 @@ use axum::{ routing::{any, get}, }; use http::StatusCode; +use std::any::Any; use tower_http::{ catch_panic::CatchPanicLayer, - compression::{CompressionLayer, CompressionLevel, predicate::SizeAbove}, + compression::{ + CompressionLayer, CompressionLevel, + predicate::{Predicate, SizeAbove}, + }, trace::TraceLayer, }; use crate::{ middleware::{ activity::track_active_requests, - chaos::chaos_middleware, + chaos::{chaos_middleware, latency_injection}, platform_cors::{force_platform_vary, platform_cors}, request_id::request_id, response_compat::response_compat, security_headers::security_headers, + websocket_reject::reject_disabled_websockets, }, policy::PolicyErrorBody, routes::{ @@ -25,7 +30,7 @@ use crate::{ grpc::grpc_policy_then_execute, grpc_web::grpc_web_policy_then_execute, metrics::metrics, - operations::{caches, health, status}, + operations::{caches, features, health, status}, platform::platform_dispatch, rest::rest_policy_then_proxy, soap::soap_policy_then_execute, @@ -43,16 +48,29 @@ pub fn build_router(state: AppState) -> Router { // Starlette treats the legacy gateway responses as streaming and emits // gzip whenever the client accepts it, even below its configured size. // Preserve that observed public wire contract during the Rust cutover. - .compress_when(SizeAbove::new(1)); + .compress_when(SizeAbove::new(1).and( + |_: http::StatusCode, + _: http::Version, + _: &http::HeaderMap, + extensions: &http::Extensions| { + extensions + .get::() + .is_none() + }, + )); let api = Router::new() .route("/rest/{*path}", any(rest_policy_then_proxy)) .route("/graphql/{*path}", any(graphql_policy_then_execute)) .route("/soap/{*path}", any(soap_policy_then_execute)) .route("/grpc/{*path}", any(grpc_policy_then_execute)) .route("/health", any(health)) + .route("/features", get(features)) .route("/status", any(status)) .route("/caches", any(caches)) .fallback(gateway_route_not_found) + .layer(axum_middleware::from_fn( + crate::routes::platform::gateway_route_guard, + )) .layer(axum_middleware::from_fn(chaos_middleware)) .layer(axum_middleware::from_fn_with_state( state.clone(), @@ -84,7 +102,7 @@ pub fn build_router(state: AppState) -> Router { .collect::>(), ), ) - .layer(CatchPanicLayer::new()); + .layer(CatchPanicLayer::custom(handle_panic)); let platform = Router::new() .route("/", any(platform_dispatch)) .route("/{*path}", any(platform_dispatch)) @@ -119,21 +137,46 @@ pub fn build_router(state: AppState) -> Router { .collect::>(), ), ) - .layer(CatchPanicLayer::new()); + .layer(CatchPanicLayer::custom(handle_panic)); - Router::new() - .nest("/api", api) - .nest("/platform", platform) + // gRPC-Web shares the data-plane guard and response headers. + let grpc_web = Router::new() .route( "/grpc-web/{api_name}/{service}/{method}", any(grpc_web_policy_then_execute), ) + .layer(axum_middleware::from_fn( + crate::routes::platform::gateway_route_guard, + )) + .layer(axum_middleware::from_fn_with_state( + state.clone(), + security_headers, + )) + .layer(axum_middleware::from_fn(request_id)); + Router::new() + .nest("/api", api) + .nest("/platform", platform) + .merge(grpc_web) .route("/metrics", get(metrics)) - .fallback(not_found) + // Unknown paths still carry the platform response headers. + .fallback_service( + Router::new() + .fallback(not_found) + .layer(axum_middleware::from_fn_with_state( + state.clone(), + security_headers, + )) + .with_state(state.clone()), + ) + .layer(axum_middleware::from_fn(latency_injection)) .layer(axum_middleware::from_fn_with_state( state.clone(), track_active_requests, )) + .layer(axum_middleware::from_fn_with_state( + state.clone(), + reject_disabled_websockets, + )) .layer(compression) .layer(axum_middleware::from_fn(force_platform_vary)) .with_state(state) @@ -150,6 +193,17 @@ async fn gateway_route_not_found() -> Response { .into_response() } +fn handle_panic(_panic: Box) -> Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(PolicyErrorBody { + error_code: "ISE001".to_owned(), + error_message: "Internal Server Error".to_owned(), + }), + ) + .into_response() +} + async fn not_found(request: axum::extract::Request) -> Response { let request_id = request .headers() @@ -174,6 +228,22 @@ async fn not_found(request: axum::extract::Request) -> Response { mod tests { use std::io::Read; + #[tokio::test] + async fn panic_handler_matches_python_internal_error_envelope() { + use http_body_util::BodyExt; + + let response = handle_panic(Box::new("boom")); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + let body = response.into_body().collect().await.unwrap().to_bytes(); + assert_eq!( + serde_json::from_slice::(&body).unwrap(), + serde_json::json!({ + "error_code": "ISE001", + "error_message": "Internal Server Error" + }) + ); + } + use axum::{ body::{Body, to_bytes}, http::{Method, Request, header}, diff --git a/gateway-rs/src/config.rs b/gateway-rs/src/config.rs index dbddb9c8..acb0a1c5 100644 --- a/gateway-rs/src/config.rs +++ b/gateway-rs/src/config.rs @@ -9,16 +9,28 @@ pub struct Config { pub port: u16, pub connect_timeout: Duration, pub https_only: bool, + pub downstream_tls_mode: DownstreamTlsMode, + pub downstream_tls_cert_file: Option, + pub downstream_tls_key_file: Option, + pub tls_profiles_file: Option, pub content_security_policy: Option, pub compression_enabled: bool, pub compression_level: i32, pub compression_minimum_size: u16, pub strict_response_envelope: bool, + pub websockets_enabled: bool, + pub logs_enabled: bool, pub logs_dir: Option, pub security_settings_file: Option, pub shared_storage: SharedStorageConfig, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DownstreamTlsMode { + Proxy, + Native, +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct SharedStorageConfig { pub storage_mode: String, @@ -196,6 +208,31 @@ impl Config { }; let compression_minimum_size = env_parse::("COMPRESSION_MINIMUM_SIZE", 500)?.min(u64::from(u16::MAX)) as u16; + let logs_enabled = env_bool("LOGS_ENABLED", true); + let downstream_tls_mode = match env::var("DOWNSTREAM_TLS_MODE") + .unwrap_or_else(|_| "proxy".to_owned()) + .to_ascii_lowercase() + .as_str() + { + "proxy" => DownstreamTlsMode::Proxy, + "native" => DownstreamTlsMode::Native, + _ => { + return Err(ConfigError::InvalidConfiguration( + "DOWNSTREAM_TLS_MODE must be proxy or native".to_owned(), + )); + } + }; + let downstream_tls_cert_file = env_non_empty("DOWNSTREAM_TLS_CERT_FILE").map(PathBuf::from); + let downstream_tls_key_file = env_non_empty("DOWNSTREAM_TLS_KEY_FILE").map(PathBuf::from); + let tls_profiles_file = env_non_empty("TLS_PROFILES_FILE").map(PathBuf::from); + if downstream_tls_mode == DownstreamTlsMode::Native + && (downstream_tls_cert_file.is_none() || downstream_tls_key_file.is_none()) + { + return Err(ConfigError::InvalidConfiguration( + "native TLS requires DOWNSTREAM_TLS_CERT_FILE and DOWNSTREAM_TLS_KEY_FILE" + .to_owned(), + )); + } Ok(Self { host: env::var("HOST") @@ -204,6 +241,10 @@ impl Config { port: env_parse("PORT", 3001)?, connect_timeout: python_http_connect_timeout()?, https_only: env_bool("HTTPS_ONLY", false), + downstream_tls_mode, + downstream_tls_cert_file, + downstream_tls_key_file, + tls_profiles_file, content_security_policy: env::var("CONTENT_SECURITY_POLICY") .ok() .filter(|value| !value.trim().is_empty()), @@ -211,10 +252,16 @@ impl Config { compression_level, compression_minimum_size, strict_response_envelope: env_bool("STRICT_RESPONSE_ENVELOPE", false), - logs_dir: env_non_empty("LOGS_DIR").map(PathBuf::from).or_else(|| { - let path = PathBuf::from("/app/logs"); - path.exists().then_some(path) - }), + websockets_enabled: env_bool("WEBSOCKETS_ENABLED", false), + logs_enabled, + logs_dir: logs_enabled + .then(|| { + env_non_empty("LOGS_DIR").map(PathBuf::from).or_else(|| { + let path = PathBuf::from("/app/logs"); + path.exists().then_some(path) + }) + }) + .flatten(), shared_storage, security_settings_file: Some( env::var("SECURITY_SETTINGS_FILE") @@ -240,11 +287,17 @@ impl Config { port: 0, connect_timeout: Duration::from_secs(1), https_only: false, + downstream_tls_mode: DownstreamTlsMode::Proxy, + downstream_tls_cert_file: None, + downstream_tls_key_file: None, + tls_profiles_file: None, content_security_policy: None, compression_enabled: true, compression_level: 6, compression_minimum_size: 500, strict_response_envelope: false, + websockets_enabled: false, + logs_enabled: true, logs_dir: None, // Tests opt into an isolated path when exercising file persistence. security_settings_file: None, @@ -254,6 +307,15 @@ impl Config { } fn validate_runtime_environment(storage: &SharedStorageConfig) -> Result<(), ConfigError> { + // The pinned gateway parses HTTP_READ_TIMEOUT with float() at import, so an + // unparseable value stops startup rather than silently using a default. + if let Ok(value) = env::var("HTTP_READ_TIMEOUT") + && value.trim().parse::().is_err() + { + return Err(ConfigError::InvalidConfiguration( + "HTTP_READ_TIMEOUT must be a number of seconds".to_owned(), + )); + } let admin_password = env::var("DOORMAN_ADMIN_PASSWORD") .map_err(|_| ConfigError::MissingEnv("DOORMAN_ADMIN_PASSWORD"))?; if admin_password.len() < 16 { @@ -538,6 +600,49 @@ mod tests { assert_eq!(config.compression_minimum_size, 500); } + #[test] + fn logs_can_be_disabled_at_environment_level() { + let _lock = ENV_LOCK.get_or_init(|| Mutex::new(())).lock().unwrap(); + let _environment = EnvRestore::set(&[ + ("ENV", "development"), + ("MEM_OR_EXTERNAL", "MEM"), + ("THREADS", "1"), + ("DOORMAN_ADMIN_PASSWORD", "DisabledLogsPassword123!"), + ( + "JWT_SECRET_KEY", + "disabled-logs-signing-key-at-least-32-characters", + ), + ("LOGS_ENABLED", "false"), + ("LOGS_DIR", "/tmp/doorman-disabled-logs-config-test"), + ]); + + let config = Config::from_env().unwrap(); + assert!(!config.logs_enabled); + assert!(config.logs_dir.is_none()); + } + + #[test] + fn native_tls_requires_both_pem_paths() { + let _lock = ENV_LOCK.get_or_init(|| Mutex::new(())).lock().unwrap(); + let _environment = EnvRestore::set(&[ + ("ENV", "development"), + ("MEM_OR_EXTERNAL", "MEM"), + ("THREADS", "1"), + ("DOORMAN_ADMIN_PASSWORD", "NativeTlsPassword123!"), + ( + "JWT_SECRET_KEY", + "native-tls-signing-key-at-least-32-characters", + ), + ("DOWNSTREAM_TLS_MODE", "native"), + ("DOWNSTREAM_TLS_CERT_FILE", ""), + ("DOWNSTREAM_TLS_KEY_FILE", ""), + ]); + assert!(matches!( + Config::from_env(), + Err(ConfigError::InvalidConfiguration(message)) if message.contains("DOWNSTREAM_TLS_CERT_FILE") + )); + } + #[test] fn builds_python_compatible_storage_urls() { let storage = SharedStorageConfig { diff --git a/gateway-rs/src/constants.rs b/gateway-rs/src/constants.rs new file mode 100644 index 00000000..50dc2386 --- /dev/null +++ b/gateway-rs/src/constants.rs @@ -0,0 +1,66 @@ +//! Shared platform constants retained from the Python backend. + +pub struct Headers; +impl Headers { + pub const REQUEST_ID: &str = "request_id"; +} + +pub struct Defaults; +impl Defaults { + pub const PAGE: usize = 1; + pub const PAGE_SIZE: usize = 10; + pub const MAX_PAGE_SIZE_ENV: &str = "MAX_PAGE_SIZE"; + pub const MAX_PAGE_SIZE_DEFAULT: usize = 200; + pub const MAX_MULTIPART_SIZE_BYTES_ENV: &str = "MAX_MULTIPART_SIZE_BYTES"; + pub const MAX_MULTIPART_SIZE_BYTES_DEFAULT: usize = 5_242_880; +} + +pub struct Roles; +impl Roles { + pub const MANAGE_USERS: &str = "manage_users"; + pub const MANAGE_APIS: &str = "manage_apis"; + pub const MANAGE_GROUPS: &str = "manage_groups"; + pub const MANAGE_ENDPOINTS: &str = "manage_endpoints"; + pub const VIEW_LOGS: &str = "view_logs"; + pub const EXPORT_LOGS: &str = "export_logs"; + pub const MANAGE_ROLES: &str = "manage_roles"; +} + +pub struct ErrorCodes; +impl ErrorCodes { + pub const UNEXPECTED: &str = "GTW999"; + pub const HTTP_EXCEPTION: &str = "GTW998"; + pub const GRPC_GENERATION_FAILED: &str = "GTW012"; + pub const PATH_VALIDATION: &str = "GTW013"; + pub const API_NOT_FOUND: &str = "API002"; + pub const AUTH_REQUIRED: &str = "AUTH001"; + pub const REQUEST_TOO_LARGE: &str = "REQ002"; + pub const REQUEST_FILE_TYPE: &str = "REQ003"; + pub const PAGE_SIZE: &str = "PAG001"; +} + +pub struct Messages; +impl Messages { + pub const UNEXPECTED: &str = "An unexpected error occurred"; + pub const FILE_TOO_LARGE: &str = "Uploaded file too large"; + pub const ONLY_PROTO_ALLOWED: &str = "Only .proto files are allowed"; + pub const PERMISSION_MANAGE_APIS: &str = "User does not have permission to manage APIs"; + pub const GRPC_GEN_FAILED: &str = "Failed to generate gRPC code"; + pub const PAGE_TOO_LARGE: &str = "Page size exceeds maximum limit"; + pub const INVALID_PAGING: &str = "Invalid page or page size"; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn preserves_python_defaults_and_wire_constants() { + assert_eq!(Headers::REQUEST_ID, "request_id"); + assert_eq!(Defaults::PAGE_SIZE, 10); + assert_eq!(Defaults::MAX_PAGE_SIZE_DEFAULT, 200); + assert_eq!(Defaults::MAX_MULTIPART_SIZE_BYTES_DEFAULT, 5_242_880); + assert_eq!(ErrorCodes::HTTP_EXCEPTION, "GTW998"); + assert_eq!(Messages::FILE_TOO_LARGE, "Uploaded file too large"); + } +} diff --git a/gateway-rs/src/demo_seed.rs b/gateway-rs/src/demo_seed.rs new file mode 100644 index 00000000..43269054 --- /dev/null +++ b/gateway-rs/src/demo_seed.rs @@ -0,0 +1,621 @@ +use std::{ + collections::BTreeSet, + fs::{self, OpenOptions}, + io::Write, + path::Path, + time::{SystemTime, UNIX_EPOCH}, +}; + +use rand::{Rng, SeedableRng, prelude::SliceRandom, rngs::StdRng}; +use serde_json::{Value, json}; +use time::{Duration, OffsetDateTime}; +use uuid::Uuid; + +use crate::{ + observability::analytics_aggregator::global_analytics, + storage::{field_encryption::encrypt_value, runtime::SharedStorage}, +}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SeedOptions { + pub users: usize, + pub apis: usize, + pub endpoints: usize, + pub groups: usize, + pub protos: usize, + pub logs: usize, + pub seed: Option, +} + +impl Default for SeedOptions { + fn default() -> Self { + Self { + users: 60, + apis: 20, + endpoints: 6, + groups: 10, + protos: 6, + logs: 2_000, + seed: None, + } + } +} + +pub async fn run_seed( + storage: &SharedStorage, + options: &SeedOptions, +) -> Result> { + let mut rng = options + .seed + .map(StdRng::seed_from_u64) + .unwrap_or_else(StdRng::from_os_rng); + let roles = ensure_roles(storage).await?; + let apis = seed_apis(storage, options.apis, &roles, &mut rng).await?; + let api_keys = apis + .iter() + .map(|(name, version)| format!("{name}/{version}")) + .collect::>(); + let groups = seed_groups(storage, options.groups, &api_keys, &mut rng).await?; + let users = seed_users(storage, options.users, &roles, &groups, &mut rng).await?; + seed_endpoints(storage, &apis, options.endpoints, &mut rng).await?; + let credit_groups = seed_credits(storage, &mut rng).await?; + seed_user_credits(storage, &users, &credit_groups, &mut rng).await?; + seed_subscriptions(storage, &users, &api_keys, &mut rng).await?; + seed_logs(options.logs, &users, &apis, &mut rng)?; + seed_protos(options.protos, &apis, &mut rng)?; + seed_metrics(&users, &apis, &mut rng); + + let mut result = serde_json::Map::new(); + for (name, collection) in [ + ("users", "users"), + ("apis", "apis"), + ("endpoints", "endpoints"), + ("groups", "groups"), + ("roles", "roles"), + ("subscriptions", "subscriptions"), + ("credit_defs", "credit_defs"), + ("user_credits", "user_credits"), + ] { + result.insert( + name.to_owned(), + json!(storage.find_many(collection, &json!({})).await?.len()), + ); + } + Ok(Value::Object(result)) +} + +async fn ensure_roles(storage: &SharedStorage) -> Result, Box> { + let roles = [ + ( + "developer", + json!({"manage_apis": true, "manage_endpoints": true, "manage_subscriptions": true, "manage_credits": true, "view_logs": true}), + ), + ("analyst", json!({"view_logs": true, "export_logs": true})), + ("viewer", json!({"view_logs": true})), + ( + "ops", + json!({"manage_gateway": true, "view_logs": true, "export_logs": true, "manage_security": true}), + ), + ]; + for (name, extra) in roles { + if storage + .find_one("roles", &json!({"role_name": name})) + .await? + .is_none() + { + let mut value = json!({"role_name": name, "role_description": format!("{name} role")}); + if let (Some(target), Some(extra)) = (value.as_object_mut(), extra.as_object()) { + target.extend(extra.clone()); + } + storage.insert_one("roles", value).await?; + } + } + Ok(["admin", "developer", "analyst", "viewer", "ops"] + .into_iter() + .map(str::to_owned) + .collect()) +} + +async fn seed_apis( + storage: &SharedStorage, + count: usize, + roles: &[String], + rng: &mut StdRng, +) -> Result, Box> { + let bases = [ + "customers", + "orders", + "billing", + "weather", + "news", + "crypto", + "search", + "inventory", + "shipping", + "payments", + "alerts", + "metrics", + "recommendations", + ]; + let versions = ["v1", "v2", "v3"]; + let mut result = Vec::new(); + for _ in 0..count { + let name = format!("{}-{}", choose(&bases, rng), random_word(3, 6, rng)); + let version = choose(&versions, rng).to_string(); + if storage + .find_one("apis", &json!({"api_name": name, "api_version": version})) + .await? + .is_none() + { + storage + .insert_one( + "apis", + json!({ + "api_name": name, "api_version": version, + "api_description": format!("Auto API {name}/{version}"), + "api_allowed_roles": sample(roles, rng.random_range(1..=roles.len().min(3)), rng), + "api_allowed_groups": ["ALL", "admin"], + "api_servers": [format!("http://localhost:{}", 8_000 + rng.random_range(0..1_000))], + "api_type": "REST", "api_allowed_retry_count": rng.random_range(0..=3), + "api_id": Uuid::new_v4().to_string(), "api_path": format!("/{name}/{version}") + }), + ) + .await?; + } + result.push((name, version)); + } + Ok(result) +} + +async fn seed_groups( + storage: &SharedStorage, + count: usize, + api_keys: &[String], + rng: &mut StdRng, +) -> Result, Box> { + let mut result = Vec::new(); + for index in 0..count { + let name = format!("team-{}-{index}", random_word(3, 6, rng)); + if storage + .find_one("groups", &json!({"group_name": name})) + .await? + .is_none() + { + let access = if api_keys.is_empty() { + Vec::new() + } else { + let maximum = (api_keys.len() / 3).max(1); + sample(api_keys, rng.random_range(1..=maximum), rng) + }; + storage.insert_one("groups", json!({"group_name": name, "group_description": format!("Auto group {name}"), "api_access": access})).await?; + } + result.push(name); + } + for name in ["ALL", "admin"] { + if storage + .find_one("groups", &json!({"group_name": name})) + .await? + .is_none() + { + storage.insert_one("groups", json!({"group_name": name, "group_description": format!("{name} group"), "api_access": []})).await?; + } + if !result.iter().any(|item| item == name) { + result.push(name.to_owned()); + } + } + Ok(result) +} + +async fn seed_users( + storage: &SharedStorage, + count: usize, + roles: &[String], + groups: &[String], + rng: &mut StdRng, +) -> Result, Box> { + let firsts = [ + "alex", "casey", "morgan", "sam", "taylor", "riley", "jamie", "jordan", "drew", "quinn", + "kyle", "parker", "blake", "devon", + ]; + let lasts = [ + "lee", "kim", "patel", "garcia", "nguyen", "williams", "brown", "davis", "miller", + "wilson", "moore", "taylor", "thomas", + ]; + let domains = ["example.com", "acme.io", "contoso.net", "demo.dev"]; + let mut result = Vec::new(); + for index in 0..count { + let username = format!("{}.{}_{}", choose(&firsts, rng), choose(&lasts, rng), index); + if storage + .find_one("users", &json!({"username": username})) + .await? + .is_none() + { + let password = bcrypt::hash(random_password(rng), bcrypt::DEFAULT_COST)?; + let group_count = rng.random_range(1..=groups.len().min(3)); + storage.insert_one("users", json!({ + "username": username, "email": format!("{}@{}", username.replace('.', "_"), choose(&domains, rng)), + "password": password, "role": choose(roles, rng), "groups": sample(groups, group_count, rng), + "rate_limit_duration": rng.random_range(100..=10_000), "rate_limit_duration_type": choose(&["minute", "hour", "day"], rng), + "throttle_duration": rng.random_range(1_000..=100_000), "throttle_duration_type": choose(&["second", "minute"], rng), + "throttle_wait_duration": rng.random_range(100..=10_000), "throttle_wait_duration_type": choose(&["seconds", "minutes"], rng), + "custom_attributes": {"dept": choose(&["sales", "eng", "support", "ops"], rng)}, "active": true, + "ui_access": rng.random_bool(0.5) + })).await?; + } + result.push(username); + } + Ok(result) +} + +async fn seed_endpoints( + storage: &SharedStorage, + apis: &[(String, String)], + per_api: usize, + rng: &mut StdRng, +) -> Result<(), Box> { + let methods = ["GET", "POST", "PUT", "DELETE", "PATCH"]; + let uris = [ + "/status", + "/health", + "/items", + "/items/{id}", + "/search", + "/reports", + "/export", + "/metrics", + "/list", + "/detail/{id}", + ]; + for (name, version) in apis { + let api = storage + .find_one("apis", &json!({"api_name": name, "api_version": version})) + .await?; + let api_id = api + .as_ref() + .and_then(|value| value.get("api_id")) + .and_then(Value::as_str) + .unwrap_or_default(); + let mut created = BTreeSet::new(); + for _ in 0..per_api { + let method = choose(&methods, rng); + let uri = choose(&uris, rng); + if !created.insert((method, uri)) || storage.find_one("endpoints", &json!({"api_name": name, "api_version": version, "endpoint_method": method, "endpoint_uri": uri})).await?.is_some() { continue; } + storage.insert_one("endpoints", json!({"api_name": name, "api_version": version, "endpoint_method": method, "endpoint_uri": uri, "endpoint_description": format!("{method} {uri} for {name}"), "api_id": api_id, "endpoint_id": Uuid::new_v4().to_string()})).await?; + } + } + Ok(()) +} + +async fn seed_credits( + storage: &SharedStorage, + rng: &mut StdRng, +) -> Result, Box> { + let groups = [ + "ai-basic", + "ai-pro", + "maps-basic", + "maps-pro", + "news-tier", + "weather-tier", + ]; + let catalog = [ + json!({"tier_name":"basic","credits":100,"input_limit":100,"output_limit":100,"reset_frequency":"monthly"}), + json!({"tier_name":"pro","credits":1000,"input_limit":500,"output_limit":500,"reset_frequency":"monthly"}), + json!({"tier_name":"enterprise","credits":10000,"input_limit":2000,"output_limit":2000,"reset_frequency":"monthly"}), + ]; + for group in groups { + if storage + .find_one("credit_defs", &json!({"api_credit_group": group})) + .await? + .is_none() + { + let tiers = sample(&catalog, rng.random_range(1..=3), rng); + storage.insert_one("credit_defs", json!({"api_credit_group": group, "api_key": encrypt_value(Some(&Uuid::new_v4().simple().to_string())), "api_key_header": choose(&["x-api-key", "authorization", "x-token"], rng), "credit_tiers": tiers})).await?; + } + } + Ok(groups.into_iter().map(str::to_owned).collect()) +} + +async fn seed_user_credits( + storage: &SharedStorage, + users: &[String], + groups: &[String], + rng: &mut StdRng, +) -> Result<(), Box> { + let selected = sample(users, (users.len() / 2).max(1).min(users.len()), rng); + for username in selected { + let mut credits = serde_json::Map::new(); + for group in sample(groups, rng.random_range(1..=groups.len().min(3)), rng) { + let days = rng.random_range(1..=30); + credits.insert(group, json!({"tier_name": choose(&["basic", "pro", "enterprise"], rng), "available_credits": rng.random_range(10..=10_000), "reset_date": date_after(days), "user_api_key": encrypt_value(Some(&Uuid::new_v4().simple().to_string()))})); + } + let value = json!({"users_credits": credits}); + if storage + .find_one("user_credits", &json!({"username": username})) + .await? + .is_some() + { + storage + .update_one("user_credits", &json!({"username": username}), &value) + .await?; + } else { + storage + .insert_one( + "user_credits", + json!({"username": username, "users_credits": credits}), + ) + .await?; + } + } + Ok(()) +} + +async fn seed_subscriptions( + storage: &SharedStorage, + users: &[String], + api_keys: &[String], + rng: &mut StdRng, +) -> Result<(), Box> { + for username in users { + let subscriptions = if api_keys.is_empty() { + Vec::new() + } else { + sample(api_keys, rng.random_range(1..=api_keys.len().min(5)), rng) + }; + if storage + .find_one("subscriptions", &json!({"username": username})) + .await? + .is_some() + { + storage + .update_one( + "subscriptions", + &json!({"username": username}), + &json!({"apis": subscriptions}), + ) + .await?; + } else { + storage + .insert_one( + "subscriptions", + json!({"username": username, "apis": subscriptions}), + ) + .await?; + } + } + Ok(()) +} + +fn seed_logs( + count: usize, + users: &[String], + apis: &[(String, String)], + rng: &mut StdRng, +) -> Result<(), Box> { + let directory = std::env::var_os("LOGS_DIR") + .map(std::path::PathBuf::from) + .unwrap_or_else(|| "logs".into()); + fs::create_dir_all(&directory)?; + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(directory.join("doorman.log"))?; + for _ in 0..count { + let (api, version) = apis + .get(rng.random_range(0..apis.len().max(1))) + .cloned() + .unwrap_or_else(|| ("demo".to_owned(), "v1".to_owned())); + let user = users + .get(rng.random_range(0..users.len().max(1))) + .map(String::as_str) + .unwrap_or("admin"); + writeln!( + file, + "{} - doorman.gateway - INFO - {} | Username: {user} | From: 127.0.0.1:{} | Endpoint: {} /{api}/{version}{} | Total time: {}ms", + timestamp(), + Uuid::new_v4(), + rng.random_range(10_000..=65_000), + choose(&["GET", "POST", "PUT", "DELETE", "PATCH"], rng), + choose( + &[ + "/status", + "/list", + "/items", + "/items/123", + "/search?q=test", + "/export", + "/metrics" + ], + rng + ), + rng.random_range(5..=500) + )?; + } + Ok(()) +} + +fn seed_protos( + count: usize, + apis: &[(String, String)], + rng: &mut StdRng, +) -> Result<(), Box> { + fs::create_dir_all("proto")?; + fs::create_dir_all("generated")?; + for (name, version) in sample(apis, count.min(apis.len()), rng) { + let key = format!("{name}_{version}").replace('-', "_"); + let service = name.split('-').map(capitalize).collect::(); + let content = format!( + "syntax = \"proto3\";\n\npackage {key};\n\nservice {service}Service {{\n rpc GetStatus (StatusRequest) returns (StatusReply) {{}}\n}}\n\nmessage StatusRequest {{\n string id = 1;\n}}\n\nmessage StatusReply {{\n string status = 1;\n string message = 2;\n}}\n" + ); + fs::write(Path::new("proto").join(format!("{key}.proto")), content)?; + } + Ok(()) +} + +fn seed_metrics(users: &[String], apis: &[(String, String)], rng: &mut StdRng) { + // Mirrors Python's seed_metrics(minutes=400): backfill 400 historical + // one-minute buckets (oldest first) instead of recording everything into + // the current minute, so a freshly seeded instance shows a real + // multi-hour timeseries. + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + for minutes_ago in (1..=400).rev() { + let timestamp = now.saturating_sub(minutes_ago * 60); + for _ in 0..rng.random_range(0..=50) { + let status = *choose(&[200_u16, 200, 200, 201, 204, 400, 401, 403, 404, 500], rng); + let api = apis + .get(rng.random_range(0..apis.len().max(1))) + .map(|item| format!("rest:{}", item.0)); + let user = users + .get(rng.random_range(0..users.len().max(1))) + .map(String::as_str); + global_analytics().record_request_at( + timestamp, + api.as_deref(), + user, + None, + status, + rng.random_range(10.0..400.0), + 0, + 0, + ); + } + } +} + +fn choose<'a, T>(values: &'a [T], rng: &mut StdRng) -> &'a T { + &values[rng.random_range(0..values.len())] +} + +fn sample(values: &[T], count: usize, rng: &mut StdRng) -> Vec { + let mut values = values.to_vec(); + values.shuffle(rng); + values.truncate(count.min(values.len())); + values +} + +fn random_word(minimum: usize, maximum: usize, rng: &mut StdRng) -> String { + (0..rng.random_range(minimum..=maximum)) + .map(|_| char::from(rng.random_range(b'a'..=b'z'))) + .collect() +} + +fn random_password(rng: &mut StdRng) -> String { + let mut bytes = Vec::new(); + bytes.push(*choose(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ", rng)); + bytes.extend((0..8).map(|_| *choose(b"abcdefghijklmnopqrstuvwxyz", rng))); + bytes.extend((0..4).map(|_| *choose(b"0123456789", rng))); + bytes.push(*choose(b"!@#$%^&*()-_=+[]{};:,.<>?/", rng)); + bytes.extend((0..6).map(|_| { + *choose( + b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + rng, + ) + })); + bytes.shuffle(rng); + String::from_utf8(bytes).expect("ASCII password") +} + +fn date_after(days: i64) -> String { + let date = (OffsetDateTime::now_utc() + Duration::days(days)).date(); + format!( + "{:04}-{:02}-{:02}", + date.year(), + date.month() as u8, + date.day() + ) +} + +fn timestamp() -> String { + let seconds = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64; + OffsetDateTime::from_unix_timestamp(seconds) + .unwrap_or(OffsetDateTime::UNIX_EPOCH) + .to_string() +} + +fn capitalize(value: &str) -> String { + let mut characters = value.chars(); + characters + .next() + .map(char::to_uppercase) + .into_iter() + .flatten() + .chain(characters) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn defaults_match_the_python_seed_command() { + assert_eq!( + SeedOptions::default(), + SeedOptions { + users: 60, + apis: 20, + endpoints: 6, + groups: 10, + protos: 6, + logs: 2_000, + seed: None + } + ); + } + + #[test] + fn seed_metrics_backfills_historical_minute_buckets_like_python() { + let aggregator = crate::observability::analytics_aggregator::AnalyticsAggregator::new(); + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + // Mirror seed_metrics' own backfill loop against an isolated aggregator + // (not the process-global one) so the test is hermetic. + for minutes_ago in (1..=400).rev() { + let timestamp = now.saturating_sub(minutes_ago * 60); + aggregator.record_request_at( + timestamp, + Some("rest:demo"), + Some("admin"), + None, + 200, + 42.0, + 0, + 0, + ); + } + let series = aggregator.get_timeseries(); + assert_eq!(series.len(), 400, "one bucket per historical minute"); + let mut timestamps: Vec = series.iter().map(|point| point.timestamp).collect(); + let deduped_len = { + timestamps.dedup(); + timestamps.len() + }; + assert_eq!( + deduped_len, 400, + "each bucket has a distinct minute timestamp" + ); + assert!( + timestamps.windows(2).all(|pair| pair[0] < pair[1]), + "strictly increasing" + ); + } + + #[test] + fn seeded_generators_are_reproducible() { + let mut first = StdRng::seed_from_u64(17); + let mut second = StdRng::seed_from_u64(17); + assert_eq!( + random_word(4, 10, &mut first), + random_word(4, 10, &mut second) + ); + assert_eq!(random_password(&mut first), random_password(&mut second)); + } +} diff --git a/gateway-rs/src/error_codes.rs b/gateway-rs/src/error_codes.rs new file mode 100644 index 00000000..df28dd1c --- /dev/null +++ b/gateway-rs/src/error_codes.rs @@ -0,0 +1,183 @@ +//! Central error-code registry retained from the Python API. + +pub struct ErrorCode; + +impl ErrorCode { + pub const AUTH_MISSING_CREDENTIALS: &str = "AUTH001"; + pub const AUTH_INVALID_CREDENTIALS: &str = "AUTH002"; + pub const AUTH_TOKEN_INVALID: &str = "AUTH003"; + pub const AUTH_TOKEN_MISSING: &str = "AUTH004"; + pub const AUTH_TOKEN_EXPIRED: &str = "AUTH005"; + pub const AUTH_USER_INACTIVE: &str = "AUTH007"; + pub const AUTH_UNEXPECTED_ERROR: &str = "AUTH900"; + pub const USR_ALREADY_EXISTS: &str = "USR001"; + pub const USR_NOT_FOUND: &str = "USR002"; + pub const USR_DELETE_FAILED: &str = "USR003"; + pub const USR_UPDATE_FAILED: &str = "USR004"; + pub const USR_WEAK_PASSWORD: &str = "USR005"; + pub const USR_UNAUTHORIZED_ACCESS: &str = "USR006"; + pub const USR_INVALID_OPERATION: &str = "USR007"; + pub const USR_PERMISSION_DENIED: &str = "USR008"; + pub const USR_USERNAME_INVALID: &str = "USR013"; + pub const USR_EMAIL_INVALID: &str = "USR015"; + pub const USR_TOO_MANY_ATTRIBUTES: &str = "USR016"; + pub const API_ALREADY_EXISTS: &str = "API001"; + pub const API_CREATE_FAILED: &str = "API002"; + pub const API_NOT_FOUND: &str = "API003"; + pub const API_UPDATE_NAME_FORBIDDEN: &str = "API005"; + pub const API_NO_DATA_TO_UPDATE: &str = "API006"; + pub const API_PERMISSION_DENIED: &str = "API007"; + pub const API_INVALID_REQUEST: &str = "API008"; + pub const API_PROTO_INVALID: &str = "API009"; + pub const API_PUBLIC_CREDITS_CONFLICT: &str = "API013"; + pub const END_ALREADY_EXISTS: &str = "END001"; + pub const END_CREATE_FAILED: &str = "END002"; + pub const END_NOT_FOUND: &str = "END003"; + pub const END_UPDATE_FAILED: &str = "END004"; + pub const END_DELETE_FAILED: &str = "END005"; + pub const END_INVALID_TYPE: &str = "END006"; + pub const END_VALIDATION_FAILED: &str = "END007"; + pub const END_SERVERS_REQUIRED: &str = "END008"; + pub const END_CIRCULAR_DEPENDENCY: &str = "END009"; + pub const END_PERMISSION_DENIED: &str = "END010"; + pub const END_INVALID_REQUEST: &str = "END011"; + pub const END_MISSING_PROTO: &str = "END012"; + pub const END_PATH_CONFLICT: &str = "END013"; + pub const END_METHOD_CONFLICT: &str = "END014"; + pub const END_SERVER_INVALID: &str = "END015"; + pub const END_TIMEOUT_INVALID: &str = "END016"; + pub const END_RETRIES_INVALID: &str = "END017"; + pub const END_HEADERS_INVALID: &str = "END018"; + pub const END_BODY_TRANSFORM_INVALID: &str = "END019"; + pub const END_RESPONSE_TRANSFORM_INVALID: &str = "END020"; + pub const END_LOAD_BALANCER_INVALID: &str = "END021"; + pub const END_CIRCUIT_BREAKER_INVALID: &str = "END022"; + pub const END_RATE_LIMIT_INVALID: &str = "END023"; + pub const ROLE_ALREADY_EXISTS: &str = "ROLE001"; + pub const ROLE_CREATE_FAILED: &str = "ROLE002"; + pub const ROLE_NOT_FOUND: &str = "ROLE004"; + pub const ROLE_NAME_IMMUTABLE: &str = "ROLE005"; + pub const ROLE_UPDATE_FAILED: &str = "ROLE006"; + pub const ROLE_NO_DATA_TO_UPDATE: &str = "ROLE007"; + pub const ROLE_DELETE_FAILED: &str = "ROLE008"; + pub const ROLE_PERMISSION_DENIED: &str = "ROLE009"; + pub const ROLE_INVALID_REQUEST: &str = "ROLE010"; + pub const ROLE_IN_USE: &str = "ROLE011"; + pub const ROLE_PERMISSION_INVALID: &str = "ROLE013"; + pub const ROLE_NAME_INVALID: &str = "ROLE014"; + pub const ROLE_RESERVED_NAME: &str = "ROLE015"; + pub const ROLE_DESCRIPTION_REQUIRED: &str = "ROLE016"; + pub const GRP_ALREADY_EXISTS: &str = "GRP001"; + pub const GRP_CREATE_FAILED: &str = "GRP002"; + pub const GRP_NOT_FOUND: &str = "GRP003"; + pub const GRP_UPDATE_FAILED: &str = "GRP004"; + pub const GRP_DELETE_FAILED: &str = "GRP005"; + pub const GRP_NAME_IMMUTABLE: &str = "GRP006"; + pub const GRP_NO_DATA_TO_UPDATE: &str = "GRP007"; + pub const GRP_PERMISSION_DENIED: &str = "GRP008"; + pub const GRP_INVALID_REQUEST: &str = "GRP009"; + pub const GRP_IN_USE: &str = "GRP010"; + pub const SUB_INVALID_API: &str = "SUB003"; + pub const SUB_ALREADY_EXISTS: &str = "SUB004"; + pub const SUB_NOT_FOUND: &str = "SUB005"; + pub const SUB_CREATE_FAILED: &str = "SUB006"; + pub const SUB_PERMISSION_DENIED: &str = "SUB007"; + pub const SUB_INVALID_REQUEST: &str = "SUB008"; + pub const SUB_DELETE_FAILED: &str = "SUB009"; + pub const RTG_ALREADY_EXISTS: &str = "RTG001"; + pub const RTG_CREATE_FAILED: &str = "RTG002"; + pub const RTG_NOT_FOUND: &str = "RTG004"; + pub const RTG_UPDATE_FAILED: &str = "RTG005"; + pub const RTG_DELETE_FAILED: &str = "RTG006"; + pub const RTG_NO_DATA_TO_UPDATE: &str = "RTG007"; + pub const RTG_NAME_IMMUTABLE: &str = "RTG008"; + pub const RTG_PERMISSION_DENIED: &str = "RTG009"; + pub const RTG_INVALID_REQUEST: &str = "RTG010"; + pub const RTG_PRIORITY_CONFLICT: &str = "RTG011"; + pub const RTG_PATH_INVALID: &str = "RTG012"; + pub const RTG_CONDITION_INVALID: &str = "RTG013"; + pub const CRD_GROUP_EXISTS: &str = "CRD001"; + pub const CRD_CREATE_FAILED: &str = "CRD002"; + pub const CRD_GROUP_IMMUTABLE: &str = "CRD003"; + pub const CRD_NOT_FOUND: &str = "CRD004"; + pub const CRD_UPDATE_FAILED: &str = "CRD005"; + pub const CRD_NO_DATA_TO_UPDATE: &str = "CRD006"; + pub const CRD_DELETE_NOT_FOUND: &str = "CRD007"; + pub const CRD_DELETE_FAILED: &str = "CRD008"; + pub const CRD_GROUP_NAME_REQUIRED: &str = "CRD009"; + pub const CRD_API_KEY_REQUIRED: &str = "CRD010"; + pub const CRD_DATABASE_ERROR: &str = "CRD011"; + pub const CRD_DATABASE_ERROR_UPDATE: &str = "CRD012"; + pub const CRD_DATABASE_ERROR_DELETE: &str = "CRD013"; + pub const CRD_USERNAME_MISMATCH: &str = "CRD014"; + pub const CRD_USER_CREDITS_ERROR: &str = "CRD015"; + pub const CRD_GET_ALL_ERROR: &str = "CRD016"; + pub const CRD_USER_NOT_FOUND: &str = "CRD017"; + pub const CRD_GET_USER_ERROR: &str = "CRD018"; + pub const CRD_LIST_ERROR: &str = "CRD020"; + pub const CRD_FETCH_ERROR: &str = "CRD021"; + pub const CRD_RETRIEVE_ERROR: &str = "CRD022"; + pub const GTW_UPSTREAM_ERROR: &str = "GTW001"; + pub const GTW_TIMEOUT: &str = "GTW002"; + pub const GTW_NO_AVAILABLE_SERVERS: &str = "GTW003"; + pub const GTW_INVALID_REQUEST: &str = "GTW004"; + pub const GTW_RATE_LIMIT_EXCEEDED: &str = "GTW005"; + pub const GTW_SERVICE_UNAVAILABLE: &str = "GTW006"; + pub const GTW_AUTHENTICATION_REQUIRED: &str = "GTW007"; + pub const GTW_SUBSCRIPTION_REQUIRED: &str = "GTW008"; + pub const GTW_CIRCUIT_BREAKER_OPEN: &str = "GTW010"; + pub const GTW_INVALID_ENDPOINT: &str = "GTW011"; + pub const GTW_PROTO_DECODE_ERROR: &str = "GTW013"; + pub const GTW_UNEXPECTED_ERROR: &str = "GTW999"; + pub const CFG_PERMISSION_DENIED: &str = "CFG001"; + pub const CFG_API_PERMISSION_DENIED: &str = "CFG002"; + pub const CFG_ENDPOINT_PERMISSION_DENIED: &str = "CFG003"; + pub const CFG_GROUP_PERMISSION_DENIED: &str = "CFG004"; + pub const CFG_ROLE_PERMISSION_DENIED: &str = "CFG005"; + pub const CFG_ROUTING_PERMISSION_DENIED: &str = "CFG006"; + pub const CFG_SUBSCRIPTION_PERMISSION_DENIED: &str = "CFG007"; + pub const CFG_NOT_FOUND: &str = "CFG404"; + pub const SEC_PERMISSION_DENIED: &str = "SEC001"; + pub const SEC_UPDATE_FAILED: &str = "SEC002"; + pub const SEC_INVALID_IP: &str = "SEC003"; + pub const SEC_INVALID_SETTING: &str = "SEC004"; + pub const SEC_PROTECTED_SETTING: &str = "SEC005"; + pub const LOG_PERMISSION_DENIED: &str = "LOG001"; + pub const LOG_INVALID_LEVEL: &str = "LOG002"; + pub const LOG_UPDATE_FAILED: &str = "LOG003"; + pub const LOG_QUERY_FAILED: &str = "LOG004"; + pub const LOG_EXPORT_FAILED: &str = "LOG005"; + pub const MON_PERMISSION_DENIED: &str = "MON001"; + pub const MON_QUERY_FAILED: &str = "MON002"; + pub const MON_UNEXPECTED_ERROR: &str = "MON003"; + pub const MEM_PERMISSION_DENIED: &str = "MEM001"; + pub const MEM_DUMP_FAILED: &str = "MEM002"; + pub const MEM_OPERATION_FAILED: &str = "MEM003"; + pub const DEMO_ALREADY_SEEDED: &str = "DEMO001"; + pub const DEMO_SEED_FAILED: &str = "DEMO999"; + pub const TLS_PERMISSION_DENIED: &str = "TLS001"; + pub const TLS_UNEXPECTED_ERROR: &str = "TLS999"; + pub const GEN_INVALID_REQUEST: &str = "GEN001"; + pub const GEN_VALIDATION_ERROR: &str = "GEN002"; + pub const ISE_INTERNAL_ERROR: &str = "ISE001"; + pub const REQ_BODY_TOO_LARGE: &str = "REQ001"; + pub const VAL_INVALID_JSON: &str = "VAL001"; + pub const JWT_DECODE_ERROR: &str = "JWT001"; + pub const RATE_LIMIT_EXCEEDED: &str = "RATE_LIMIT_EXCEEDED"; +} + +pub type ErrorCodes = ErrorCode; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn preserves_representative_python_error_code_values() { + assert_eq!(ErrorCode::AUTH_MISSING_CREDENTIALS, "AUTH001"); + assert_eq!(ErrorCode::END_RATE_LIMIT_INVALID, "END023"); + assert_eq!(ErrorCode::CRD_RETRIEVE_ERROR, "CRD022"); + assert_eq!(ErrorCodes::GTW_UNEXPECTED_ERROR, "GTW999"); + assert_eq!(ErrorCode::RATE_LIMIT_EXCEEDED, "RATE_LIMIT_EXCEEDED"); + } +} diff --git a/gateway-rs/src/gateway/circuit_breaker.rs b/gateway-rs/src/gateway/circuit_breaker.rs index 53178626..7c3ae0b6 100644 --- a/gateway-rs/src/gateway/circuit_breaker.rs +++ b/gateway-rs/src/gateway/circuit_breaker.rs @@ -89,11 +89,12 @@ fn enabled() -> bool { } fn threshold() -> u64 { + // The pinned int() parse keeps zero and negative thresholds, which open the + // circuit on the first failure. env::var("CIRCUIT_BREAKER_THRESHOLD") .ok() - .and_then(|value| value.parse().ok()) - .unwrap_or(5) - .max(1) + .and_then(|value| value.trim().parse::().ok()) + .map_or(5, |value| u64::try_from(value.max(1)).unwrap_or(1)) } fn open_duration() -> Duration { diff --git a/gateway-rs/src/gateway/transforms.rs b/gateway-rs/src/gateway/transforms.rs index 05bc9c9b..c425f9d9 100644 --- a/gateway-rs/src/gateway/transforms.rs +++ b/gateway-rs/src/gateway/transforms.rs @@ -38,7 +38,11 @@ pub fn transform_response( let status = direction .get("status_map") .and_then(|mapping| mapping.get(status.as_u16().to_string())) - .and_then(Value::as_u64) + .and_then(|value| { + value + .as_u64() + .or_else(|| value.as_str().and_then(|value| value.parse().ok())) + }) .and_then(|value| u16::try_from(value).ok()) .and_then(|value| StatusCode::from_u16(value).ok()) .unwrap_or(status); @@ -71,7 +75,7 @@ fn transform_headers(mut headers: HeaderMap, config: Option<&Value>) -> HeaderMa for (name, value) in add { let (Ok(name), Ok(value)) = ( HeaderName::try_from(name), - HeaderValue::from_str(value.as_str().unwrap_or(&value.to_string())), + HeaderValue::from_str(&python_string(value)), ) else { continue; }; @@ -134,15 +138,22 @@ fn transform_query(query: Option<&str>, config: Option<&Value>) -> String { if let Some(add) = config.get("add").and_then(Value::as_object) { for (name, value) in add { pairs.retain(|(existing, _)| existing != name); - pairs.push(( - name.clone(), - value.as_str().unwrap_or(&value.to_string()).to_owned(), - )); + pairs.push((name.clone(), python_string(value))); } } encode_query(&pairs) } +fn python_string(value: &Value) -> String { + match value { + Value::String(value) => value.clone(), + Value::Bool(true) => "True".to_owned(), + Value::Bool(false) => "False".to_owned(), + Value::Null => "None".to_owned(), + value => value.to_string(), + } +} + fn encode_query(pairs: &[(String, String)]) -> String { let mut serializer = url::form_urlencoded::Serializer::new(String::new()); serializer.extend_pairs(pairs.iter().map(|(key, value)| (key, value))); @@ -156,29 +167,32 @@ struct PathPart { } fn path_parts(path: &str) -> Option> { - let path = path.trim().strip_prefix("$.")?; + // Python: only "$." prefixed paths; each segment is either `word[digits]` or a plain key. + let path = path.strip_prefix("$.")?; let mut parts = Vec::new(); for raw in path.split('.') { if raw.is_empty() { continue; } - let (field, index) = if let Some((field, raw_index)) = raw.split_once('[') { - let index = raw_index.strip_suffix(']')?.parse().ok()?; - (field, Some(index)) - } else { - (raw, None) - }; - if field.is_empty() - || !field - .chars() - .all(|character| character.is_ascii_alphanumeric() || character == '_') - { - return None; - } - parts.push(PathPart { - field: field.to_owned(), - index, + let indexed = raw.strip_suffix(']').and_then(|head| { + let (field, digits) = head.split_once('[')?; + let word = !field.is_empty() + && field + .chars() + .all(|character| character.is_alphanumeric() || character == '_'); + let numeric = !digits.is_empty() && digits.chars().all(|c| c.is_ascii_digit()); + (word && numeric).then_some((field, digits)) }); + match indexed { + Some((field, digits)) => parts.push(PathPart { + field: field.to_owned(), + index: Some(digits.parse().ok()?), + }), + None => parts.push(PathPart { + field: raw.to_owned(), + index: None, + }), + } } (!parts.is_empty()).then_some(parts) } @@ -296,6 +310,25 @@ mod tests { Some(&config), ); assert_eq!(status, StatusCode::BAD_GATEWAY); + + let config = serde_json::json!({"request": { + "headers": {"add": {"x-enabled": true, "x-empty": null}}, + "query": {"add": {"enabled": false}} + }}); + let (headers, _, query) = + transform_request(HeaderMap::new(), Vec::new(), None, Some(&config)); + assert_eq!(headers["x-enabled"], "True"); + assert_eq!(headers["x-empty"], "None"); + assert_eq!(query, "enabled=False"); + + let config = serde_json::json!({"response": {"status_map": {"500": "502"}}}); + let (_, _, status) = transform_response( + HeaderMap::new(), + Vec::new(), + StatusCode::INTERNAL_SERVER_ERROR, + Some(&config), + ); + assert_eq!(status, StatusCode::BAD_GATEWAY); } #[test] @@ -317,4 +350,25 @@ mod tests { assert_eq!(value["items"][2]["created"], true); assert_eq!(value["tags"], serde_json::json!(["old", "new"])); } + + #[test] + fn jsonpath_segments_accept_any_plain_key_like_python() { + let config = serde_json::json!({"request": {"body": { + "remove": ["$.a-b"], + "set": {"$.first-name": "x", "$.p q.r": 1}, + "rename": {"$.old key": "$.new-key"} + }}}); + let (_, body, _) = transform_request( + HeaderMap::new(), + br#"{"a-b":1,"old key":"v"}"#.to_vec(), + None, + Some(&config), + ); + let value: Value = serde_json::from_slice(&body).unwrap(); + assert!(value.get("a-b").is_none()); + assert_eq!(value["first-name"], "x"); + assert_eq!(value["p q"]["r"], 1); + assert_eq!(value["new-key"], "v"); + assert!(value.get("old key").is_none()); + } } diff --git a/gateway-rs/src/hot_reload.rs b/gateway-rs/src/hot_reload.rs index 13f65feb..03e4d5d1 100644 --- a/gateway-rs/src/hot_reload.rs +++ b/gateway-rs/src/hot_reload.rs @@ -110,6 +110,14 @@ impl HotReloadConfig { .values .write() .map_err(|_| "hot-reload configuration lock is poisoned".to_owned())?; + // Python applies LOG_LEVEL immediately via a registered callback + // (`logging.getLogger().setLevel(new_value)`); apply it to the live + // tracing subscriber here instead of only recording it as reloaded. + if let Some(Value::String(level)) = loaded.get("LOG_LEVEL") + && values.get("LOG_LEVEL") != loaded.get("LOG_LEVEL") + { + crate::observability::logging::set_log_level(level); + } *values = loaded; Ok(()) } @@ -218,6 +226,36 @@ fn parse_env_value(value: &str) -> Value { mod tests { use super::*; + #[test] + fn log_level_changes_are_applied_without_erroring_the_reload() { + // The dynamic-apply path runs even when no tracing subscriber has been + // initialized in this test binary (`set_log_level` then safely no-ops); + // what this guards is that reload() itself never fails because of it, + // and that the new value is recorded either way. + // SAFETY: test-only; no other test in this crate reads LOG_LEVEL. + unsafe { + env::set_var("LOG_LEVEL", "debug"); + } + let config = HotReloadConfig::new(None); + assert_eq!( + config.dump().get("LOG_LEVEL").and_then(Value::as_str), + Some("debug") + ); + // SAFETY: test-only; no other test in this crate reads LOG_LEVEL. + unsafe { + env::set_var("LOG_LEVEL", "warn"); + } + assert!(config.reload().is_ok()); + assert_eq!( + config.dump().get("LOG_LEVEL").and_then(Value::as_str), + Some("warn") + ); + // SAFETY: test-only. + unsafe { + env::remove_var("LOG_LEVEL"); + } + } + #[test] fn rejected_reload_retains_last_valid_runtime_settings() { let directory = diff --git a/gateway-rs/src/lib.rs b/gateway-rs/src/lib.rs index 180f7864..6c2faef7 100644 --- a/gateway-rs/src/lib.rs +++ b/gateway-rs/src/lib.rs @@ -1,6 +1,9 @@ pub mod app; pub mod config; +pub mod constants; +pub mod demo_seed; pub mod error; +pub mod error_codes; pub mod gateway; pub mod hot_reload; pub mod middleware; @@ -12,6 +15,7 @@ mod python_scalar; pub mod routes; pub mod state; pub mod storage; +pub mod tls; pub mod validation; pub use app::build_router; diff --git a/gateway-rs/src/main.rs b/gateway-rs/src/main.rs index 5f898e17..f9db4185 100644 --- a/gateway-rs/src/main.rs +++ b/gateway-rs/src/main.rs @@ -1,24 +1,45 @@ use std::{ - env, - path::PathBuf, + env, fs, + path::{Path, PathBuf}, + process::{Command, Stdio}, sync::{Arc, atomic::Ordering}, + thread, time::Duration, }; use doorman_gateway::{ AppState, Config, build_router, + demo_seed::{SeedOptions, run_seed}, hot_reload::HotReloadConfig, observability::analytics_aggregator::global_analytics, routes::platform::backfill_grpc_descriptors, state::{GatewayRuntime, MemoryAutosaveConfig}, storage::{runtime::SharedStorage, security_settings, snapshot}, + tls::{TlsConnectionInfo, TlsListener}, }; use tokio::net::TcpListener; use tracing::{error, info, warn}; #[tokio::main] async fn main() -> Result<(), Box> { + // Reqwest, Tonic, and the native listener enable different Rustls feature sets. + // Select one provider before any of them construct TLS configuration. + let _ = tokio_rustls::rustls::crypto::ring::default_provider().install_default(); doorman_gateway::observability::init(); + + match env::args().nth(1).as_deref() { + Some("start") => return start_process(), + Some("stop") => return stop_process(), + Some("restart") => { + stop_process()?; + thread::sleep(Duration::from_secs(1)); + return start_process(); + } + Some("seed") => return seed_command(env::args().skip(2)).await, + Some("run") | None => {} + Some(command) => return Err(format!("unknown command: {command}").into()), + } + restore_metrics(); let config = Config::from_env()?; @@ -91,16 +112,41 @@ async fn main() -> Result<(), Box> { } spawn_sighup_reload(state.hot_reload.clone()); - let app = build_router(state); + let tls_config = state.reload_tls_from_storage().await?; + let app = build_router(state.clone()); let listener = TcpListener::bind(&bind_addr).await?; + let reload_state = state.clone(); + let reload_task = tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(15)); + interval.tick().await; + loop { + interval.tick().await; + if let Err(error) = reload_state.reload_tls_from_storage().await { + warn!(%error, "TLS reload rejected; retaining active configuration"); + } + } + }); + info!(address = %bind_addr, "Doorman Rust gateway listening"); - axum::serve( - listener, - app.into_make_service_with_connect_info::(), - ) - .with_graceful_shutdown(shutdown_signal()) - .await?; + if let Some(tls_config) = tls_config { + let listener = TlsListener::new(listener, tls_config); + state.set_tls_listener_handle(listener.reload_handle()); + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(shutdown_signal()) + .await?; + } else { + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(shutdown_signal()) + .await?; + } + reload_task.abort(); // Axum has now drained active requests. Stop writers before the final dump // so an older autosave cannot replace the state committed during draining. @@ -130,6 +176,160 @@ async fn main() -> Result<(), Box> { Ok(()) } +async fn seed_command( + arguments: impl Iterator, +) -> Result<(), Box> { + let arguments = arguments.collect::>(); + if arguments + .iter() + .any(|argument| argument == "--help" || argument == "-h") + { + print_seed_help(); + return Ok(()); + } + let options = parse_seed_options(arguments.into_iter())?; + println!("Starting demo seed with:"); + println!(" Users: {}", options.users); + println!(" APIs: {}", options.apis); + println!(" Endpoints per API: {}", options.endpoints); + println!(" Groups: {}", options.groups); + println!(" Protos: {}", options.protos); + println!(" Logs: {}", options.logs); + if let Some(seed) = options.seed { + println!(" Random Seed: {seed}"); + } + println!(); + + let config = Config::from_env()?; + let storage = SharedStorage::connect(&config.shared_storage).await?; + storage.initialize_core().await?; + let result = run_seed(&storage, &options).await?; + println!("\n✓ Seeding completed successfully!"); + println!("Result: {result}"); + Ok(()) +} + +fn parse_seed_options( + arguments: impl Iterator, +) -> Result> { + let mut options = SeedOptions::default(); + let mut arguments = arguments.peekable(); + while let Some(argument) = arguments.next() { + let (name, inline_value) = argument + .split_once('=') + .map_or((argument.as_str(), None), |(name, value)| { + (name, Some(value.to_owned())) + }); + let value = inline_value + .or_else(|| arguments.next()) + .ok_or_else(|| format!("{name} requires an integer value"))?; + match name { + "--users" => options.users = value.parse()?, + "--apis" => options.apis = value.parse()?, + "--endpoints" => options.endpoints = value.parse()?, + "--groups" => options.groups = value.parse()?, + "--protos" => options.protos = value.parse()?, + "--logs" => options.logs = value.parse()?, + "--seed" => options.seed = Some(value.parse()?), + _ => return Err(format!("unknown seed option: {name}").into()), + } + } + Ok(options) +} + +fn print_seed_help() { + println!( + "Seed the database with demo data\n\n --users N Number of users (default: 60)\n --apis N Number of APIs (default: 20)\n --endpoints N Endpoints per API (default: 6)\n --groups N Number of groups (default: 10)\n --protos N Number of proto files (default: 6)\n --logs N Number of log entries (default: 2000)\n --seed N Random seed for reproducibility" + ); +} + +fn pid_file_path() -> PathBuf { + env::var_os("PID_FILE") + .or_else(|| env::var_os("DOORMAN_PID_FILE")) + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("doorman.pid")) +} + +fn start_process() -> Result<(), Box> { + let pid_file = pid_file_path(); + if pid_file.exists() { + info!(path = %pid_file.display(), "doorman is already running"); + return Ok(()); + } + + let executable = env::current_exe()?; + let mut command = Command::new(executable); + command + .arg("run") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + command.process_group(0); + } + let child = command.spawn()?; + fs::write(&pid_file, child.id().to_string())?; + info!(pid = child.id(), "starting doorman"); + Ok(()) +} + +fn stop_process() -> Result<(), Box> { + let pid_file = pid_file_path(); + if !pid_file.exists() { + info!("no running instance found"); + return Ok(()); + } + let pid = read_pid(&pid_file)?; + + #[cfg(unix)] + stop_unix_process_group(pid); + #[cfg(windows)] + { + let _ = Command::new("taskkill") + .args(["/F", "/PID", &pid.to_string()]) + .status(); + } + + if pid_file.exists() { + fs::remove_file(&pid_file)?; + } + info!(pid, "stopping doorman"); + Ok(()) +} + +fn read_pid(path: &Path) -> Result> { + let pid = fs::read_to_string(path)?.trim().parse::()?; + if pid == 0 { + return Err("PID file must contain a positive process ID".into()); + } + Ok(pid) +} + +#[cfg(unix)] +fn stop_unix_process_group(pid: u32) { + let group = format!("-{pid}"); + let status = Command::new("kill").args(["-TERM", "--", &group]).status(); + if !status.is_ok_and(|status| status.success()) { + return; + } + + let deadline = std::time::Instant::now() + Duration::from_secs(15); + while std::time::Instant::now() < deadline { + let running = Command::new("kill") + .args(["-0", "--", &pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok_and(|status| status.success()); + if !running { + break; + } + thread::sleep(Duration::from_millis(500)); + } +} + #[cfg(unix)] fn spawn_sighup_reload(config: Arc) { tokio::spawn(async move { @@ -323,3 +523,63 @@ fn spawn_metrics_autosave(runtime: Arc) { } }); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pid_files_require_a_positive_integer() { + let directory = env::temp_dir().join(format!("doorman-pid-test-{}", uuid::Uuid::new_v4())); + fs::create_dir_all(&directory).unwrap(); + let path = directory.join("doorman.pid"); + + fs::write(&path, "1234\n").unwrap(); + assert_eq!(read_pid(&path).unwrap(), 1234); + fs::write(&path, "0").unwrap(); + assert!(read_pid(&path).is_err()); + fs::write(&path, "invalid").unwrap(); + assert!(read_pid(&path).is_err()); + + fs::remove_dir_all(directory).unwrap(); + } + + #[test] + fn seed_options_match_python_cli_names_and_defaults() { + assert_eq!( + parse_seed_options(std::iter::empty()).unwrap(), + SeedOptions::default() + ); + assert_eq!( + parse_seed_options( + [ + "--users=2", + "--apis", + "3", + "--endpoints", + "4", + "--groups", + "5", + "--protos", + "6", + "--logs", + "7", + "--seed", + "8", + ] + .into_iter() + .map(str::to_owned), + ) + .unwrap(), + SeedOptions { + users: 2, + apis: 3, + endpoints: 4, + groups: 5, + protos: 6, + logs: 7, + seed: Some(8), + } + ); + } +} diff --git a/gateway-rs/src/middleware/activity.rs b/gateway-rs/src/middleware/activity.rs index c9266075..b5350e29 100644 --- a/gateway-rs/src/middleware/activity.rs +++ b/gateway-rs/src/middleware/activity.rs @@ -79,6 +79,18 @@ pub async fn track_active_requests( .extensions() .get::>() .map(|value| value.0.ip().to_string()); + let user_agent = request + .headers() + .get(axum::http::header::USER_AGENT) + .and_then(|value| value.to_str().ok()) + .map(str::to_owned); + // Python's SecurityAuditMiddleware._should_audit: every modification + // method anywhere, plus every /platform/* request (its own comment notes + // the narrower vault/auth/tiers checks are already subsumed by this + // catch-all, since those paths all start with /platform/ too). + let should_audit_platform_request = + matches!(method.as_str(), "POST" | "PUT" | "DELETE" | "PATCH") + || path.starts_with("/platform/"); let record_analytics = path.starts_with("/api/") || path.starts_with("/grpc-web/"); let bytes_in = estimated_request_size(&request); let is_test = test_request(request.headers()); @@ -106,9 +118,25 @@ pub async fn track_active_requests( .active_requests .fetch_sub(1, Ordering::Relaxed); observe_request(&state.runtime, elapsed, status); - let fallback_api = api_key(&path); + // Only fall back to a path-derived API key for a response the gateway + // actually handled. A bare 404 means the route never resolved to a real + // API (context.api was never set), so recording analytics for it would + // fabricate a data point Python never produces -- its equivalent crashes + // deep in the service before any metrics are recorded for an unknown API. + let fallback_api = (status != 404).then(|| api_key(&path)).flatten(); let effective_api = context.api.as_deref().or(fallback_api.as_deref()); + if should_audit_platform_request { + crate::observability::audit::platform_request( + context.username.as_deref().unwrap_or("anonymous"), + &method, + &path, + status, + elapsed.as_secs_f64() * 1000.0, + user_agent.as_deref(), + ); + } + if record_analytics { state .runtime @@ -137,8 +165,11 @@ pub async fn track_active_requests( .as_secs() / 60 * 60; - if let Err(error) = storage - .record_gateway_metric(GatewayMetric { + // Analytics are best-effort: never hold the response on a slow or + // unreachable store. + let recorded = tokio::time::timeout( + std::time::Duration::from_millis(500), + storage.record_gateway_metric(GatewayMetric { minute_start, status, duration_micros: elapsed.as_micros().min(u128::from(u64::MAX)) as u64, @@ -148,15 +179,23 @@ pub async fn track_active_requests( username: context.username.as_deref(), endpoint: context.endpoint.as_deref().or(Some(path.as_str())), is_test, - }) - .await - { + }), + ) + .await + .unwrap_or_else(|_| { + Err(crate::storage::runtime::StorageError::InvalidDocument( + "analytics store timed out".to_owned(), + )) + }); + if let Err(error) = recorded { tracing::warn!(error = %error, "failed to persist gateway analytics bucket"); } } } - if let Some(logs_dir) = state.config.logs_dir.as_deref() { + if state.config.logs_enabled + && let Some(logs_dir) = state.config.logs_dir.as_deref() + { let endpoint = context.endpoint.as_deref().unwrap_or(&path); let upstream = context .upstream diff --git a/gateway-rs/src/middleware/body_limit.rs b/gateway-rs/src/middleware/body_limit.rs index c4fb26e7..5f86d204 100644 --- a/gateway-rs/src/middleware/body_limit.rs +++ b/gateway-rs/src/middleware/body_limit.rs @@ -1,5 +1,8 @@ use std::env; +#[derive(Clone, Copy, Debug)] +pub struct NoCompression; + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct BodyLimits { pub default: usize, @@ -64,9 +67,10 @@ fn env_bool(name: &str, default: bool) -> bool { .unwrap_or(default) } +/// A negative limit (accepted by the pinned `int()` parse) rejects every body. fn env_usize(name: &str, default: usize) -> usize { env::var(name) .ok() - .and_then(|value| value.parse().ok()) - .unwrap_or(default) + .and_then(|value| value.trim().parse::().ok()) + .map_or(default, |value| usize::try_from(value).unwrap_or(0)) } diff --git a/gateway-rs/src/middleware/chaos.rs b/gateway-rs/src/middleware/chaos.rs index 41b9dded..013274ea 100644 --- a/gateway-rs/src/middleware/chaos.rs +++ b/gateway-rs/src/middleware/chaos.rs @@ -4,6 +4,7 @@ use axum::{ response::{IntoResponse, Response}, }; use http::StatusCode; +use std::env; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::time::Duration; @@ -36,3 +37,78 @@ pub async fn chaos_middleware(req: Request, next: Next) -> Response { next.run(req).await } + +pub async fn latency_injection(req: Request, next: Next) -> Response { + let enabled = env::var("ENABLE_LATENCY_INJECTION") + .ok() + .is_some_and(|value| value.eq_ignore_ascii_case("true")); + if let Some(delay) = injected_latency_ms( + enabled, + req.headers() + .get("x-doorman-latency") + // Starlette decodes header bytes as latin-1. + .map(|value| { + value + .as_bytes() + .iter() + .map(|b| char::from(*b)) + .collect::() + }) + .as_deref(), + ) { + tokio::time::sleep(Duration::from_millis(delay)).await; + } + next.run(req).await +} + +fn injected_latency_ms(enabled: bool, value: Option<&str>) -> Option { + if !enabled { + return None; + } + let text = crate::python_scalar::strip(value?); + let delay = match crate::python_scalar::parse_integer(text) { + Some(parsed) => parsed.clamp(0, 5_000), + // Python ints are unbounded; only magnitude beyond i128 needs the sign. + None if is_oversized_integer(text) => { + if text.starts_with('-') { + 0 + } else { + 5_000 + } + } + None => return None, + }; + (delay > 0).then_some(delay as u64) +} + +fn is_oversized_integer(text: &str) -> bool { + let digits = text.strip_prefix(['+', '-']).unwrap_or(text); + digits.len() > 38 && digits.chars().all(|c| c.is_ascii_digit()) && digits.len() <= 4_300 +} + +#[cfg(test)] +mod tests { + use super::injected_latency_ms; + + #[test] + fn legacy_header_latency_matches_python_bounds_and_parsing() { + assert_eq!(injected_latency_ms(false, Some("100")), None); + assert_eq!(injected_latency_ms(true, None), None); + assert_eq!(injected_latency_ms(true, Some("bad")), None); + assert_eq!(injected_latency_ms(true, Some("-1")), None); + assert_eq!(injected_latency_ms(true, Some("0")), None); + assert_eq!(injected_latency_ms(true, Some(" 125 ")), Some(125)); + assert_eq!(injected_latency_ms(true, Some("9000")), Some(5_000)); + assert_eq!(injected_latency_ms(true, Some("+1_0")), Some(10)); + assert_eq!(injected_latency_ms(true, Some("12")), Some(12)); + assert_eq!(injected_latency_ms(true, Some("1__0")), None); + assert_eq!( + injected_latency_ms(true, Some(&"9".repeat(60))), + Some(5_000) + ); + assert_eq!( + injected_latency_ms(true, Some(&format!("-{}", "9".repeat(60)))), + None + ); + } +} diff --git a/gateway-rs/src/middleware/mod.rs b/gateway-rs/src/middleware/mod.rs index eb7ef98b..f2cbd8e6 100644 --- a/gateway-rs/src/middleware/mod.rs +++ b/gateway-rs/src/middleware/mod.rs @@ -8,3 +8,4 @@ pub mod platform_cors; pub mod request_id; pub mod response_compat; pub mod security_headers; +pub mod websocket_reject; diff --git a/gateway-rs/src/middleware/security_headers.rs b/gateway-rs/src/middleware/security_headers.rs index 80fb1963..818298dc 100644 --- a/gateway-rs/src/middleware/security_headers.rs +++ b/gateway-rs/src/middleware/security_headers.rs @@ -12,32 +12,31 @@ pub async fn security_headers( request: Request, next: Next, ) -> Response { - let docs = matches!(request.uri().path(), "/platform/docs" | "/platform/redoc"); + let docs = matches!( + request.uri().path(), + "/docs" | "/redoc" | "/platform/docs" | "/platform/redoc" + ); let mut response = next.run(request).await; let headers = response.headers_mut(); insert_default(headers, "x-content-type-options", "nosniff"); - insert_default( - headers, - "x-frame-options", - if docs { "SAMEORIGIN" } else { "DENY" }, - ); + insert_default(headers, "x-frame-options", "DENY"); insert_default(headers, "referrer-policy", "no-referrer"); insert_default( headers, "permissions-policy", "geolocation=(), microphone=(), camera=()", ); + let custom_csp = state.config.content_security_policy.as_deref(); let default_csp = if docs { - "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https://cdn.jsdelivr.net; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self'; frame-ancestors 'self'; base-uri 'self';" + "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https://cdn.jsdelivr.net; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self'; frame-ancestors *; base-uri 'self';" } else { "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; img-src 'self' data:; connect-src 'self';" }; - let csp = state - .config - .content_security_policy - .as_deref() - .unwrap_or(default_csp); + if docs && custom_csp.is_none() { + headers.remove("x-frame-options"); + } + let csp = custom_csp.unwrap_or(default_csp); insert_value_default(headers, "content-security-policy", csp); if state.config.https_only { insert_default( diff --git a/gateway-rs/src/middleware/websocket_reject.rs b/gateway-rs/src/middleware/websocket_reject.rs new file mode 100644 index 00000000..0aa247d1 --- /dev/null +++ b/gateway-rs/src/middleware/websocket_reject.rs @@ -0,0 +1,73 @@ +use axum::{ + extract::{ + FromRequestParts, Request, State, + ws::{CloseFrame, Message, WebSocketUpgrade}, + }, + middleware::Next, + response::{IntoResponse, Response}, +}; +use http::header; + +use crate::state::AppState; + +pub async fn reject_disabled_websockets( + State(state): State, + request: Request, + next: Next, +) -> Response { + if state.config.websockets_enabled || !is_websocket_upgrade(&request) { + return next.run(request).await; + } + + let (mut parts, body) = request.into_parts(); + match WebSocketUpgrade::from_request_parts(&mut parts, &state).await { + Ok(upgrade) => upgrade + .on_upgrade(|mut socket| async move { + let _ = socket + .send(Message::Close(Some(CloseFrame { + code: 1008, + reason: "".into(), + }))) + .await; + }) + .into_response(), + Err(_) => next.run(Request::from_parts(parts, body)).await, + } +} + +fn is_websocket_upgrade(request: &Request) -> bool { + request + .headers() + .get(header::UPGRADE) + .and_then(|value| value.to_str().ok()) + .is_some_and(|value| value.eq_ignore_ascii_case("websocket")) + && request + .headers() + .get(header::CONNECTION) + .and_then(|value| value.to_str().ok()) + .is_some_and(|value| { + value + .split(',') + .any(|token| token.trim().eq_ignore_ascii_case("upgrade")) + }) +} + +#[cfg(test)] +mod tests { + use axum::body::Body; + + use super::*; + + #[test] + fn recognizes_websocket_upgrade_headers_case_insensitively() { + let request = Request::builder() + .header(header::CONNECTION, "keep-alive, Upgrade") + .header(header::UPGRADE, "WebSocket") + .body(Body::empty()) + .unwrap(); + assert!(is_websocket_upgrade(&request)); + + let request = Request::builder().body(Body::empty()).unwrap(); + assert!(!is_websocket_upgrade(&request)); + } +} diff --git a/gateway-rs/src/observability/analytics_aggregator.rs b/gateway-rs/src/observability/analytics_aggregator.rs index 32320e47..39724ee1 100644 --- a/gateway-rs/src/observability/analytics_aggregator.rs +++ b/gateway-rs/src/observability/analytics_aggregator.rs @@ -16,6 +16,43 @@ pub struct AggregatedPoint { pub latency_ms: f64, pub bytes_in: u64, pub bytes_out: u64, + /// Bounded ring buffer of raw per-request latencies for this minute bucket, + /// mirroring Python's `MinuteBucket.latencies` deque. Not persisted to disk, + /// matching Python's `to_dict`/`from_dict`, which omit it. + #[serde(skip)] + pub latencies_ms: VecDeque, +} + +/// Mirrors Python's `os.getenv('METRICS_PCT_SAMPLES', '500')` cap on the +/// per-bucket raw-latency sample deque. +fn metrics_pct_samples() -> usize { + std::env::var("METRICS_PCT_SAMPLES") + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(500) +} + +impl AggregatedPoint { + /// Python's `snapshot()` percentile formula: sort the retained samples and + /// index at `max(0, int(0.95 * len) - 1)`. + pub fn p95_ms(&self) -> f64 { + if self.latencies_ms.is_empty() { + return 0.0; + } + let mut sorted: Vec = self.latencies_ms.iter().copied().collect(); + sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); + let index = ((0.95 * sorted.len() as f64) as usize).saturating_sub(1); + sorted[index.min(sorted.len() - 1)] + } + + fn push_latency_sample(&mut self, duration_ms: f64) { + let cap = metrics_pct_samples(); + self.latencies_ms.push_back(duration_ms); + while self.latencies_ms.len() > cap { + self.latencies_ms.pop_front(); + } + } } #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] @@ -25,6 +62,17 @@ pub struct EntityCounter { pub error_count: u64, } +#[derive(Debug, Clone, Default, PartialEq)] +pub struct EndpointEntity { + pub name: String, + pub count: u64, + pub error_count: u64, + pub avg_ms: f64, + /// (p50, p75, p90, p95, p99) over the range's pooled raw-latency samples, + /// using Python's `PercentileMetrics.calculate` sorted-index formula. + pub percentiles: (f64, f64, f64, f64, f64), +} + #[derive(Debug, Serialize, Deserialize, Default)] struct PythonMetricFile { #[serde(default)] @@ -83,8 +131,35 @@ struct PythonMetricBucket { endpoint_metrics: HashMap, } +/// Per-minute (count, error_count) for every entity name seen in that minute, +/// mirroring Python's `MinuteBucket.api_counts`/`user_counts`/per-endpoint +/// metrics. Kept in lockstep with `points` (same timestamps, same pruning) so +/// a time-scoped top-N query can be reconstructed for any [start, end] window +/// instead of only a process-global all-time total. +#[derive(Debug, Clone, Default)] +struct MinuteEntities { + timestamp: u64, + api: HashMap, + user: HashMap, + endpoint: HashMap, +} + +/// Per-endpoint, per-minute totals plus a capped raw-latency sample deque, so +/// a range-scoped query can compute both a real `avg_ms` (`total_ms / count`) +/// and real percentiles, matching Python's `EndpointMetrics.total_ms/count` +/// and `EndpointMetrics.get_percentiles()` instead of the fixed placeholders +/// the route used to return. +#[derive(Debug, Clone, Default)] +struct EndpointBucket { + count: u64, + errors: u64, + total_ms: f64, + latencies_ms: VecDeque, +} + pub struct AnalyticsAggregator { points: RwLock>, + entity_minutes: RwLock>, api_counters: RwLock>, api_error_counters: RwLock>, user_counters: RwLock>, @@ -97,7 +172,8 @@ pub struct AnalyticsAggregator { impl AnalyticsAggregator { pub fn new() -> Self { Self { - points: RwLock::new(VecDeque::with_capacity(1440)), + points: RwLock::new(VecDeque::with_capacity(43_200)), + entity_minutes: RwLock::new(VecDeque::with_capacity(43_200)), api_counters: RwLock::new(HashMap::new()), api_error_counters: RwLock::new(HashMap::new()), user_counters: RwLock::new(HashMap::new()), @@ -123,7 +199,38 @@ impl AnalyticsAggregator { .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs(); - let minute_ts = (now / 60) * 60; + self.record_request_at( + now, + api_name, + username, + endpoint, + status, + duration_ms, + bytes_in, + bytes_out, + ); + } + + /// Like `record_request`, but files the sample under an explicit unix + /// timestamp rather than "now". Used by the demo seed to backfill + /// historical minute buckets the way Python's `seed_metrics` does, so a + /// freshly seeded instance shows a real multi-hour timeseries instead of + /// one spike in the current minute. Callers must invoke this in + /// non-decreasing `timestamp_secs` order, matching the append-only + /// bucket layout `record_request` relies on. + #[allow(clippy::too_many_arguments)] + pub fn record_request_at( + &self, + timestamp_secs: u64, + api_name: Option<&str>, + username: Option<&str>, + endpoint: Option<&str>, + status: u16, + duration_ms: f64, + bytes_in: u64, + bytes_out: u64, + ) { + let minute_ts = (timestamp_secs / 60) * 60; let is_error = status >= 400; increment(&self.status_counters, status, true); @@ -153,20 +260,175 @@ impl AnalyticsAggregator { last.bytes_out += bytes_out; last.latency_ms = ((last.latency_ms * previous as f64) + duration_ms) / last.requests as f64; - return; + last.push_latency_sample(duration_ms); + } else { + if points.len() >= 43_200 { + points.pop_front(); + } + let mut point = AggregatedPoint { + timestamp: minute_ts, + requests: 1, + errors: u64::from(is_error), + latency_ms: duration_ms, + bytes_in, + bytes_out, + latencies_ms: VecDeque::new(), + }; + point.push_latency_sample(duration_ms); + points.push_back(point); } - if points.len() >= 1440 { - points.pop_front(); + } + + if let Ok(mut minutes) = self.entity_minutes.write() { + let bucket = if minutes + .back() + .is_some_and(|last| last.timestamp == minute_ts) + { + minutes.back_mut().unwrap() + } else { + if minutes.len() >= 43_200 { + minutes.pop_front(); + } + minutes.push_back(MinuteEntities { + timestamp: minute_ts, + ..Default::default() + }); + minutes.back_mut().unwrap() + }; + if let Some(api) = api_name { + let entry = bucket.api.entry(api.to_owned()).or_default(); + entry.0 += 1; + entry.1 += u64::from(is_error); + } + if let Some(user) = username { + let entry = bucket.user.entry(user.to_owned()).or_default(); + entry.0 += 1; + entry.1 += u64::from(is_error); + } + if let Some(endpoint) = endpoint { + let entry = bucket.endpoint.entry(endpoint.to_owned()).or_default(); + entry.count += 1; + entry.errors += u64::from(is_error); + entry.total_ms += duration_ms; + let cap = metrics_pct_samples(); + entry.latencies_ms.push_back(duration_ms); + while entry.latencies_ms.len() > cap { + entry.latencies_ms.pop_front(); + } + } + } + } + + /// Time-scoped equivalent of `get_top_apis`/`get_top_users`/`get_top_endpoints`, + /// reconstructing totals from only the minute buckets in `[start_ts, end_ts]` + /// instead of the process-global all-time counters. Mirrors Python's + /// `enhanced_metrics_store.get_snapshot(start_ts, end_ts)`-backed top lists. + fn top_entities_in_range( + &self, + start_ts: u64, + end_ts: u64, + limit: usize, + select: impl Fn(&MinuteEntities) -> &HashMap, + ) -> Vec { + let mut totals: HashMap = HashMap::new(); + if let Ok(minutes) = self.entity_minutes.read() { + for bucket in minutes + .iter() + .filter(|bucket| bucket.timestamp >= start_ts && bucket.timestamp <= end_ts) + { + for (name, (count, errors)) in select(bucket) { + let entry = totals.entry(name.clone()).or_default(); + entry.0 += count; + entry.1 += errors; + } } - points.push_back(AggregatedPoint { - timestamp: minute_ts, - requests: 1, - errors: u64::from(is_error), - latency_ms: duration_ms, - bytes_in, - bytes_out, - }); } + let mut items = totals + .into_iter() + .map(|(name, (count, error_count))| EntityCounter { + name, + count, + error_count, + }) + .collect::>(); + items.sort_by(|left, right| { + right + .count + .cmp(&left.count) + .then_with(|| left.name.cmp(&right.name)) + }); + items.truncate(limit); + items + } + + pub fn get_top_apis_in_range( + &self, + start_ts: u64, + end_ts: u64, + limit: usize, + ) -> Vec { + self.top_entities_in_range(start_ts, end_ts, limit, |bucket| &bucket.api) + } + + pub fn get_top_users_in_range( + &self, + start_ts: u64, + end_ts: u64, + limit: usize, + ) -> Vec { + self.top_entities_in_range(start_ts, end_ts, limit, |bucket| &bucket.user) + } + + /// Like `get_top_apis_in_range`/`get_top_users_in_range`, but also + /// reconstructs a real `avg_ms` per endpoint from the range's pooled + /// `total_ms`, matching Python's `EndpointMetrics.total_ms / count` + /// (`enhanced_metrics_util.py`'s `top_endpoints` builder) instead of the + /// fixed `0.0` the route previously returned. + pub fn get_top_endpoints_in_range( + &self, + start_ts: u64, + end_ts: u64, + limit: usize, + ) -> Vec { + let mut totals: HashMap = HashMap::new(); + if let Ok(minutes) = self.entity_minutes.read() { + for bucket in minutes + .iter() + .filter(|bucket| bucket.timestamp >= start_ts && bucket.timestamp <= end_ts) + { + for (name, sample) in &bucket.endpoint { + let entry = totals.entry(name.clone()).or_default(); + entry.count += sample.count; + entry.errors += sample.errors; + entry.total_ms += sample.total_ms; + entry + .latencies_ms + .extend(sample.latencies_ms.iter().copied()); + } + } + } + let mut items = totals + .into_iter() + .map(|(name, bucket)| EndpointEntity { + name, + count: bucket.count, + error_count: bucket.errors, + avg_ms: if bucket.count == 0 { + 0.0 + } else { + bucket.total_ms / bucket.count as f64 + }, + percentiles: pooled_percentiles(bucket.latencies_ms.iter().copied()), + }) + .collect::>(); + items.sort_by(|left, right| { + right + .count + .cmp(&left.count) + .then_with(|| left.name.cmp(&right.name)) + }); + items.truncate(limit); + items } pub fn get_timeseries(&self) -> Vec { @@ -177,10 +439,18 @@ impl AnalyticsAggregator { } pub fn get_timeseries_range(&self, start_ts: u64, end_ts: u64) -> Vec { - self.get_timeseries() - .into_iter() - .filter(|point| point.timestamp >= start_ts && point.timestamp <= end_ts) - .collect() + let interval = match end_ts.saturating_sub(start_ts) { + seconds if seconds <= 86_400 => 300, + seconds if seconds <= 604_800 => 3_600, + _ => 86_400, + }; + aggregate_points( + self.get_timeseries() + .into_iter() + .filter(|point| point.timestamp >= start_ts && point.timestamp <= end_ts) + .collect(), + interval, + ) } pub fn get_status_distribution(&self) -> HashMap { @@ -303,9 +573,10 @@ impl AnalyticsAggregator { }, bytes_in: bucket.bytes_in, bytes_out: bucket.bytes_out, + latencies_ms: VecDeque::new(), }) .collect::>(); - while points.len() > 1440 { + while points.len() > 43_200 { points.pop_front(); } @@ -329,12 +600,57 @@ impl AnalyticsAggregator { } } +fn aggregate_points(points: Vec, interval: u64) -> Vec { + let mut buckets = std::collections::BTreeMap::::new(); + for point in points { + let timestamp = (point.timestamp / interval) * interval; + let bucket = buckets.entry(timestamp).or_insert_with(|| AggregatedPoint { + timestamp, + ..AggregatedPoint::default() + }); + let previous_requests = bucket.requests; + bucket.requests = bucket.requests.saturating_add(point.requests); + bucket.errors = bucket.errors.saturating_add(point.errors); + bucket.bytes_in = bucket.bytes_in.saturating_add(point.bytes_in); + bucket.bytes_out = bucket.bytes_out.saturating_add(point.bytes_out); + if bucket.requests > 0 { + bucket.latency_ms = ((bucket.latency_ms * previous_requests as f64) + + (point.latency_ms * point.requests as f64)) + / bucket.requests as f64; + } + for sample in point.latencies_ms { + bucket.push_latency_sample(sample); + } + } + buckets.into_values().collect() +} + impl Default for AnalyticsAggregator { fn default() -> Self { Self::new() } } +/// Python's `PercentileMetrics.calculate`: sort the pooled samples and index +/// each percentile at `max(0, int(p * n) - 1)`. Shared by the range-scoped +/// per-endpoint percentiles here and `routes::platform::analytics_percentiles`. +pub fn pooled_percentiles(samples: impl Iterator) -> (f64, f64, f64, f64, f64) { + let mut values: Vec = samples.collect(); + if values.is_empty() { + return (0.0, 0.0, 0.0, 0.0, 0.0); + } + values.sort_by(|left, right| left.total_cmp(right)); + let n = values.len(); + let percentile = |fraction: f64| values[((fraction * n as f64) as usize).saturating_sub(1)]; + ( + percentile(0.50), + percentile(0.75), + percentile(0.90), + percentile(0.95), + percentile(0.99), + ) +} + fn increment(map: &RwLock>, key: K, enabled: bool) where K: Eq + std::hash::Hash, @@ -407,6 +723,92 @@ pub fn global_analytics() -> &'static Arc { mod tests { use super::*; + /// `METRICS_PCT_SAMPLES` is process-global; Rust's default test harness + /// runs tests in parallel threads within one process, so any test that + /// sets it (or is sensitive to its ambient value while pushing several + /// samples) must serialize against every other such test to avoid a + /// cross-test race. + static METRICS_PCT_SAMPLES_LOCK: std::sync::OnceLock> = + std::sync::OnceLock::new(); + + #[test] + fn top_entities_in_range_reconstructs_from_only_the_selected_minutes() { + let aggregator = AnalyticsAggregator::new(); + // Minute 0: alice hits `api-a` twice (one error). + aggregator.record_request_at(0, Some("api-a"), Some("alice"), Some("/x"), 200, 1.0, 0, 0); + aggregator.record_request_at(0, Some("api-a"), Some("alice"), Some("/x"), 500, 1.0, 0, 0); + // Minute 120 (outside the queried window below): bob hits `api-b` three times. + aggregator.record_request_at(120, Some("api-b"), Some("bob"), Some("/y"), 200, 1.0, 0, 0); + aggregator.record_request_at(120, Some("api-b"), Some("bob"), Some("/y"), 200, 1.0, 0, 0); + aggregator.record_request_at(120, Some("api-b"), Some("bob"), Some("/y"), 200, 1.0, 0, 0); + + // Query only minute 0's window: api-b/bob must not appear at all. + let top_apis = aggregator.get_top_apis_in_range(0, 59, 10); + assert_eq!(top_apis.len(), 1); + assert_eq!(top_apis[0].name, "api-a"); + assert_eq!(top_apis[0].count, 2); + assert_eq!(top_apis[0].error_count, 1); + + let top_users = aggregator.get_top_users_in_range(0, 59, 10); + assert_eq!( + top_users, + vec![EntityCounter { + name: "alice".to_owned(), + count: 2, + error_count: 1 + }] + ); + + // Querying the full range picks up both minutes, with api-b (3) ranked above api-a (2). + let both = aggregator.get_top_apis_in_range(0, 200, 10); + assert_eq!(both.len(), 2); + assert_eq!(both[0].name, "api-b"); + assert_eq!(both[0].count, 3); + assert_eq!(both[1].name, "api-a"); + } + + #[test] + fn get_top_endpoints_in_range_computes_a_real_avg_ms_like_python() { + let aggregator = AnalyticsAggregator::new(); + aggregator.record_request_at(0, None, None, Some("/orders"), 200, 10.0, 0, 0); + aggregator.record_request_at(0, None, None, Some("/orders"), 500, 30.0, 0, 0); + aggregator.record_request_at(120, None, None, Some("/orders"), 200, 1000.0, 0, 0); + + let endpoints = aggregator.get_top_endpoints_in_range(0, 59, 10); + assert_eq!(endpoints.len(), 1); + assert_eq!(endpoints[0].name, "/orders"); + assert_eq!(endpoints[0].count, 2); + assert_eq!(endpoints[0].error_count, 1); + // (10 + 30) / 2 = 20, not diluted by the out-of-range 1000ms sample. + assert_eq!(endpoints[0].avg_ms, 20.0); + } + + #[test] + fn get_top_endpoints_in_range_computes_real_percentiles_like_python() { + // Sensitive to the ambient METRICS_PCT_SAMPLES cap (pushes samples + // past a small cap set by another test would otherwise evict some). + let _guard = METRICS_PCT_SAMPLES_LOCK + .get_or_init(|| std::sync::Mutex::new(())) + .lock(); + let aggregator = AnalyticsAggregator::new(); + for latency in [10.0, 20.0, 30.0, 40.0, 100.0] { + aggregator.record_request_at(0, None, None, Some("/orders"), 200, latency, 0, 0); + } + // Out-of-range sample must not pollute the pooled percentiles. + aggregator.record_request_at(120, None, None, Some("/orders"), 200, 99999.0, 0, 0); + + let endpoints = aggregator.get_top_endpoints_in_range(0, 59, 10); + assert_eq!(endpoints.len(), 1); + // Sorted: [10, 20, 30, 40, 100], n=5. + // p50: k=max(0,int(0.5*5)-1)=1 -> 20 + // p95: k=max(0,int(0.95*5)-1)=3 -> 40 + // p99: k=max(0,int(0.99*5)-1)=3 -> 40 + let (p50, _p75, _p90, p95, p99) = endpoints[0].percentiles; + assert_eq!(p50, 20.0); + assert_eq!(p95, 40.0); + assert_eq!(p99, 40.0); + } + #[test] fn persists_and_restores_python_compatible_metrics() { let directory = @@ -441,7 +843,24 @@ mod tests { assert!(original.get_top_endpoints(10).is_empty()); original.load_from_file(&path).unwrap(); - assert_eq!(original.get_timeseries(), before_points); + // Raw latency samples are intentionally not persisted, matching Python's + // MinuteBucket.to_dict()/from_dict(), which omit the `latencies` deque. + let restored: Vec = original + .get_timeseries() + .into_iter() + .map(|mut point| { + point.latencies_ms.clear(); + point + }) + .collect(); + let expected: Vec = before_points + .into_iter() + .map(|mut point| { + point.latencies_ms.clear(); + point + }) + .collect(); + assert_eq!(restored, expected); assert_eq!(original.get_status_distribution(), before_statuses); assert_eq!(original.get_top_apis(10), before_apis); assert_eq!(original.get_top_users(10), before_users); @@ -449,4 +868,93 @@ mod tests { fs::remove_dir_all(directory).unwrap(); } + + #[test] + fn range_queries_use_python_aggregation_levels() { + let points = vec![ + AggregatedPoint { + timestamp: 301, + requests: 2, + errors: 1, + latency_ms: 10.0, + bytes_in: 3, + bytes_out: 4, + latencies_ms: VecDeque::new(), + }, + AggregatedPoint { + timestamp: 599, + requests: 1, + errors: 0, + latency_ms: 40.0, + bytes_in: 5, + bytes_out: 6, + latencies_ms: VecDeque::new(), + }, + AggregatedPoint { + timestamp: 601, + requests: 1, + errors: 1, + latency_ms: 20.0, + bytes_in: 7, + bytes_out: 8, + latencies_ms: VecDeque::new(), + }, + ]; + let five_minute = aggregate_points(points.clone(), 300); + assert_eq!(five_minute.len(), 2); + assert_eq!(five_minute[0].timestamp, 300); + assert_eq!(five_minute[0].requests, 3); + assert_eq!(five_minute[0].errors, 1); + assert_eq!(five_minute[0].latency_ms, 20.0); + assert_eq!(five_minute[0].bytes_in, 8); + assert_eq!(aggregate_points(points.clone(), 3_600).len(), 1); + assert_eq!(aggregate_points(points, 86_400).len(), 1); + } + + #[test] + fn p95_matches_python_sorted_index_formula() { + // Python: arr.sort(); k = max(0, int(0.95 * len(arr)) - 1); p95 = arr[k] + let mut point = AggregatedPoint::default(); + for ms in [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] { + point.push_latency_sample(ms); + } + // len=10 -> k = max(0, int(9.5)-1) = 8 -> sorted[8] = 90.0 + assert_eq!(point.p95_ms(), 90.0); + + let empty = AggregatedPoint::default(); + assert_eq!(empty.p95_ms(), 0.0); + + let mut single = AggregatedPoint::default(); + single.push_latency_sample(42.0); + assert_eq!(single.p95_ms(), 42.0); + } + + #[test] + fn latency_sample_deque_is_capped_like_python_metrics_pct_samples() { + let _guard = METRICS_PCT_SAMPLES_LOCK + .get_or_init(|| std::sync::Mutex::new(())) + .lock(); + unsafe { + std::env::set_var("METRICS_PCT_SAMPLES", "3"); + } + let mut point = AggregatedPoint::default(); + for ms in [1.0, 2.0, 3.0, 4.0, 5.0] { + point.push_latency_sample(ms); + } + assert_eq!(point.latencies_ms, VecDeque::from([3.0, 4.0, 5.0])); + unsafe { + std::env::remove_var("METRICS_PCT_SAMPLES"); + } + } + + #[test] + fn record_request_tracks_raw_latency_samples_for_percentiles() { + let aggregator = AnalyticsAggregator::new(); + aggregator.record_request(Some("api"), Some("user"), Some("/e"), 200, 10.0, 0, 0); + aggregator.record_request(Some("api"), Some("user"), Some("/e"), 200, 90.0, 0, 0); + let points = aggregator.get_timeseries(); + assert_eq!(points.len(), 1); + assert_eq!(points[0].latencies_ms.len(), 2); + assert_eq!(points[0].p95_ms(), 10.0); // k = max(0, int(0.95*2)-1) = 0 -> sorted[0] + } } diff --git a/gateway-rs/src/observability/analytics_models.rs b/gateway-rs/src/observability/analytics_models.rs new file mode 100644 index 00000000..3733b87f --- /dev/null +++ b/gateway-rs/src/observability/analytics_models.rs @@ -0,0 +1,634 @@ +use std::collections::{BTreeMap, BTreeSet, VecDeque}; + +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value, json}; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum AggregationLevel { + #[serde(rename = "minute")] + Minute, + #[serde(rename = "5minute")] + FiveMinute, + #[serde(rename = "hour")] + Hour, + #[serde(rename = "day")] + Day, +} + +impl AggregationLevel { + pub const fn as_str(self) -> &'static str { + match self { + Self::Minute => "minute", + Self::FiveMinute => "5minute", + Self::Hour => "hour", + Self::Day => "day", + } + } + + fn parse(value: &str) -> Result { + match value { + "minute" => Ok(Self::Minute), + "5minute" => Ok(Self::FiveMinute), + "hour" => Ok(Self::Hour), + "day" => Ok(Self::Day), + _ => Err(format!("{value:?} is not a valid AggregationLevel")), + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum MetricType { + #[serde(rename = "request_count")] + RequestCount, + #[serde(rename = "error_rate")] + ErrorRate, + #[serde(rename = "response_time")] + ResponseTime, + #[serde(rename = "bandwidth")] + Bandwidth, + #[serde(rename = "status_code")] + StatusCode, + #[serde(rename = "latency_percentile")] + LatencyPercentile, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize)] +pub struct PercentileMetrics { + pub p50: f64, + pub p75: f64, + pub p90: f64, + pub p95: f64, + pub p99: f64, + pub min: f64, + pub max: f64, +} + +impl PercentileMetrics { + pub fn calculate(latencies: &[f64]) -> Self { + if latencies.is_empty() { + return Self::default(); + } + let mut sorted = latencies.to_vec(); + sorted.sort_by(f64::total_cmp); + let percentile = |value: f64| { + let index = ((value * sorted.len() as f64) as usize) + .saturating_sub(1) + .min(sorted.len() - 1); + sorted[index] + }; + Self { + p50: percentile(0.50), + p75: percentile(0.75), + p90: percentile(0.90), + p95: percentile(0.95), + p99: percentile(0.99), + min: sorted[0], + max: sorted[sorted.len() - 1], + } + } + + pub fn to_dict(&self) -> Value { + serde_json::to_value(self).expect("percentiles are JSON representable") + } + + fn from_value(value: &Value) -> Self { + Self { + p50: float_field(value, "p50"), + p75: float_field(value, "p75"), + p90: float_field(value, "p90"), + p95: float_field(value, "p95"), + p99: float_field(value, "p99"), + min: float_field(value, "min"), + max: float_field(value, "max"), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct EndpointMetrics { + pub endpoint_uri: String, + pub method: String, + pub count: i64, + pub error_count: i64, + pub total_ms: f64, + pub latencies: VecDeque, + pub status_counts: BTreeMap, +} + +impl EndpointMetrics { + pub fn new(endpoint_uri: impl Into, method: impl Into) -> Self { + Self { + endpoint_uri: endpoint_uri.into(), + method: method.into(), + count: 0, + error_count: 0, + total_ms: 0.0, + latencies: VecDeque::new(), + status_counts: BTreeMap::new(), + } + } + + pub fn add(&mut self, milliseconds: f64, status: i64, max_samples: usize) { + self.count += 1; + self.error_count += i64::from(status >= 400); + self.total_ms += milliseconds; + *self.status_counts.entry(status).or_default() += 1; + push_bounded(&mut self.latencies, milliseconds, max_samples); + } + + pub fn get_percentiles(&self) -> PercentileMetrics { + PercentileMetrics::calculate(&self.latencies.iter().copied().collect::>()) + } + + pub fn to_dict(&mut self) -> Value { + let percentiles = self.get_percentiles(); + json!({ + "endpoint_uri": self.endpoint_uri, + "method": self.method, + "count": self.count, + "error_count": self.error_count, + "error_rate": ratio(self.error_count, self.count), + "avg_ms": if self.count > 0 { self.total_ms / self.count as f64 } else { 0.0 }, + "percentiles": percentiles.to_dict(), + "status_counts": integer_map(&self.status_counts), + }) + } + + pub fn from_dict(value: &Value) -> Result { + let count = int_field(value, "count")?; + Ok(Self { + endpoint_uri: string_field(value, "endpoint_uri"), + method: string_field(value, "method"), + count, + error_count: int_field(value, "error_count")?, + total_ms: float_field(value, "avg_ms") * count as f64, + latencies: VecDeque::new(), + status_counts: int_map(value.get("status_counts"))?, + }) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct EnhancedMinuteBucket { + pub start_ts: i64, + pub count: i64, + pub error_count: i64, + pub total_ms: f64, + pub bytes_in: i64, + pub bytes_out: i64, + pub upstream_timeouts: i64, + pub retries: i64, + pub status_counts: BTreeMap, + pub api_counts: BTreeMap, + pub api_error_counts: BTreeMap, + pub user_counts: BTreeMap, + pub latencies: VecDeque, + pub endpoint_metrics: BTreeMap, + pub unique_users: BTreeSet, + pub request_sizes: VecDeque, + pub response_sizes: VecDeque, +} + +impl EnhancedMinuteBucket { + pub fn new(start_ts: i64) -> Self { + Self { + start_ts, + count: 0, + error_count: 0, + total_ms: 0.0, + bytes_in: 0, + bytes_out: 0, + upstream_timeouts: 0, + retries: 0, + status_counts: BTreeMap::new(), + api_counts: BTreeMap::new(), + api_error_counts: BTreeMap::new(), + user_counts: BTreeMap::new(), + latencies: VecDeque::new(), + endpoint_metrics: BTreeMap::new(), + unique_users: BTreeSet::new(), + request_sizes: VecDeque::new(), + response_sizes: VecDeque::new(), + } + } + + #[allow(clippy::too_many_arguments)] + pub fn add_request( + &mut self, + milliseconds: f64, + status: i64, + username: Option<&str>, + api_key: Option<&str>, + endpoint_uri: Option<&str>, + method: Option<&str>, + bytes_in: i64, + bytes_out: i64, + max_samples: usize, + ) { + self.count += 1; + self.error_count += i64::from(status >= 400); + self.total_ms += milliseconds; + self.bytes_in += bytes_in; + self.bytes_out += bytes_out; + *self.status_counts.entry(status).or_default() += 1; + if let Some(api_key) = api_key.filter(|value| !value.is_empty()) { + *self.api_counts.entry(api_key.to_owned()).or_default() += 1; + if status >= 400 { + *self.api_error_counts.entry(api_key.to_owned()).or_default() += 1; + } + } + if let Some(username) = username.filter(|value| !value.is_empty()) { + *self.user_counts.entry(username.to_owned()).or_default() += 1; + self.unique_users.insert(username.to_owned()); + } + push_bounded(&mut self.latencies, milliseconds, max_samples); + if let (Some(endpoint_uri), Some(method)) = ( + endpoint_uri.filter(|value| !value.is_empty()), + method.filter(|value| !value.is_empty()), + ) { + self.endpoint_metrics + .entry(format!("{method}:{endpoint_uri}")) + .or_insert_with(|| EndpointMetrics::new(endpoint_uri, method)) + .add(milliseconds, status, max_samples); + } + if bytes_in > 0 { + push_bounded(&mut self.request_sizes, bytes_in, max_samples); + } + if bytes_out > 0 { + push_bounded(&mut self.response_sizes, bytes_out, max_samples); + } + } + + pub fn get_percentiles(&self) -> PercentileMetrics { + PercentileMetrics::calculate(&self.latencies.iter().copied().collect::>()) + } + + pub fn get_unique_user_count(&self) -> usize { + self.unique_users.len() + } + + pub fn get_top_endpoints(&mut self, limit: usize) -> Vec { + let mut endpoints = self + .endpoint_metrics + .values_mut() + .map(EndpointMetrics::to_dict) + .collect::>(); + endpoints.sort_by(|left, right| { + int_value(right.get("count")).cmp(&int_value(left.get("count"))) + }); + endpoints.truncate(limit); + endpoints + } + + pub fn to_dict(&mut self) -> Value { + let percentiles = self.get_percentiles(); + let endpoints = self + .endpoint_metrics + .iter_mut() + .map(|(key, value)| (key.clone(), value.to_dict())) + .collect::>(); + let average_response_size = if self.response_sizes.is_empty() { + 0.0 + } else { + self.response_sizes.iter().sum::() as f64 / self.response_sizes.len() as f64 + }; + json!({ + "start_ts": self.start_ts, "count": self.count, "error_count": self.error_count, + "total_ms": self.total_ms, "bytes_in": self.bytes_in, "bytes_out": self.bytes_out, + "upstream_timeouts": self.upstream_timeouts, "retries": self.retries, + "status_counts": integer_map(&self.status_counts), "api_counts": self.api_counts, + "api_error_counts": self.api_error_counts, "user_counts": self.user_counts, + "percentiles": percentiles.to_dict(), "unique_users": self.get_unique_user_count(), + "unique_users_list": self.unique_users, "endpoint_metrics": endpoints, + "avg_response_size": average_response_size, + }) + } + + pub fn from_dict(value: &Value) -> Result { + let mut bucket = Self::new(int_field(value, "start_ts")?); + bucket.count = int_field(value, "count")?; + bucket.error_count = int_field(value, "error_count")?; + bucket.total_ms = float_field(value, "total_ms"); + bucket.bytes_in = int_field(value, "bytes_in")?; + bucket.bytes_out = int_field(value, "bytes_out")?; + bucket.upstream_timeouts = int_field(value, "upstream_timeouts")?; + bucket.retries = int_field(value, "retries")?; + bucket.status_counts = int_map(value.get("status_counts"))?; + bucket.api_counts = string_int_map(value.get("api_counts"))?; + bucket.api_error_counts = string_int_map(value.get("api_error_counts"))?; + bucket.user_counts = string_int_map(value.get("user_counts"))?; + if let Some(endpoints) = value.get("endpoint_metrics").and_then(Value::as_object) { + for (key, endpoint) in endpoints { + if let Ok(endpoint) = EndpointMetrics::from_dict(endpoint) { + bucket.endpoint_metrics.insert(key.clone(), endpoint); + } + } + } + bucket.unique_users = value + .get("unique_users_list") + .and_then(Value::as_array) + .map(|items| items.iter().map(py_string).collect()) + .unwrap_or_else(|| bucket.user_counts.keys().cloned().collect()); + Ok(bucket) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct AggregatedMetrics { + pub start_ts: i64, + pub end_ts: i64, + pub level: AggregationLevel, + pub count: i64, + pub error_count: i64, + pub total_ms: f64, + pub bytes_in: i64, + pub bytes_out: i64, + pub unique_users: i64, + pub status_counts: BTreeMap, + pub api_counts: BTreeMap, + pub percentiles: Option, + pub unique_users_set: BTreeSet, +} + +impl AggregatedMetrics { + pub fn to_dict(&self) -> Value { + json!({ + "start_ts": self.start_ts, "end_ts": self.end_ts, "level": self.level, + "count": self.count, "error_count": self.error_count, + "error_rate": ratio(self.error_count, self.count), + "avg_ms": if self.count > 0 { self.total_ms / self.count as f64 } else { 0.0 }, + "bytes_in": self.bytes_in, "bytes_out": self.bytes_out, + "unique_users": self.unique_users, "status_counts": integer_map(&self.status_counts), + "api_counts": self.api_counts, + "percentiles": self.percentiles.as_ref().map(PercentileMetrics::to_dict), + "unique_users_list": self.unique_users_set, + }) + } + + pub fn from_dict(value: &Value) -> Result { + let level = AggregationLevel::parse( + value + .get("level") + .and_then(Value::as_str) + .unwrap_or("minute"), + )?; + Ok(Self { + start_ts: int_field(value, "start_ts")?, + end_ts: int_field(value, "end_ts")?, + level, + count: int_field(value, "count")?, + error_count: int_field(value, "error_count")?, + total_ms: float_field(value, "total_ms"), + bytes_in: int_field(value, "bytes_in")?, + bytes_out: int_field(value, "bytes_out")?, + unique_users: int_field(value, "unique_users")?, + status_counts: int_map(value.get("status_counts"))?, + api_counts: string_int_map(value.get("api_counts"))?, + percentiles: value + .get("percentiles") + .filter(|value| !value.is_null()) + .map(PercentileMetrics::from_value), + unique_users_set: value + .get("unique_users_list") + .and_then(Value::as_array) + .map(|items| items.iter().map(py_string).collect()) + .unwrap_or_default(), + }) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct AnalyticsSnapshot { + pub start_ts: i64, + pub end_ts: i64, + pub total_requests: i64, + pub total_errors: i64, + pub error_rate: f64, + pub avg_response_ms: f64, + pub percentiles: PercentileMetrics, + pub total_bytes_in: i64, + pub total_bytes_out: i64, + pub unique_users: i64, + pub series: Vec, + pub top_apis: Vec<(String, i64)>, + pub top_users: Vec<(String, i64)>, + pub top_endpoints: Vec, + pub status_distribution: BTreeMap, +} + +impl AnalyticsSnapshot { + pub fn to_dict(&self) -> Value { + json!({ + "start_ts": self.start_ts, + "end_ts": self.end_ts, + "summary": { + "total_requests": self.total_requests, "total_errors": self.total_errors, + "error_rate": self.error_rate, "avg_response_ms": self.avg_response_ms, + "percentiles": self.percentiles.to_dict(), "total_bytes_in": self.total_bytes_in, + "total_bytes_out": self.total_bytes_out, "unique_users": self.unique_users, + }, + "series": self.series, + "top_apis": self.top_apis.iter().map(|(api, count)| json!({"api": api, "count": count})).collect::>(), + "top_users": self.top_users.iter().map(|(user, count)| json!({"user": user, "count": count})).collect::>(), + "top_endpoints": self.top_endpoints, + "status_distribution": self.status_distribution, + }) + } +} + +fn ratio(numerator: i64, denominator: i64) -> f64 { + if denominator > 0 { + numerator as f64 / denominator as f64 + } else { + 0.0 + } +} + +fn push_bounded(values: &mut VecDeque, value: T, maximum: usize) { + values.push_back(value); + while values.len() > maximum { + values.pop_front(); + } +} + +fn integer_map(values: &BTreeMap) -> Value { + Value::Object( + values + .iter() + .map(|(key, value)| (key.to_string(), json!(value))) + .collect(), + ) +} + +fn int_map(value: Option<&Value>) -> Result, String> { + value + .and_then(Value::as_object) + .into_iter() + .flatten() + .map(|(key, value)| { + Ok(( + key.parse::().map_err(|error| error.to_string())?, + int_value(Some(value)), + )) + }) + .collect() +} + +fn string_int_map(value: Option<&Value>) -> Result, String> { + Ok(value + .and_then(Value::as_object) + .into_iter() + .flatten() + .map(|(key, value)| (key.clone(), int_value(Some(value)))) + .collect()) +} + +fn int_field(value: &Value, key: &str) -> Result { + Ok(value.get(key).map_or(0, |value| int_value(Some(value)))) +} + +fn int_value(value: Option<&Value>) -> i64 { + value + .and_then(|value| { + value + .as_i64() + .or_else(|| value.as_str().and_then(|value| value.parse::().ok())) + }) + .unwrap_or(0) +} + +fn float_field(value: &Value, key: &str) -> f64 { + value + .get(key) + .and_then(|value| { + value + .as_f64() + .or_else(|| value.as_str().and_then(|value| value.parse::().ok())) + }) + .unwrap_or(0.0) +} + +fn string_field(value: &Value, key: &str) -> String { + value.get(key).map(py_string).unwrap_or_default() +} + +fn py_string(value: &Value) -> String { + match value { + Value::Null => "None".to_owned(), + Value::Bool(value) => if *value { "True" } else { "False" }.to_owned(), + Value::String(value) => value.clone(), + _ => value.to_string(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn percentile_selection_matches_python_floor_index_algorithm() { + assert_eq!( + PercentileMetrics::calculate(&[]), + PercentileMetrics::default() + ); + let metrics = PercentileMetrics::calculate(&[50.0, 10.0, 40.0, 20.0, 30.0]); + assert_eq!(metrics.p50, 20.0); + assert_eq!(metrics.p75, 30.0); + assert_eq!(metrics.p90, 40.0); + assert_eq!(metrics.p95, 40.0); + assert_eq!(metrics.p99, 40.0); + assert_eq!((metrics.min, metrics.max), (10.0, 50.0)); + } + + #[test] + fn endpoint_and_minute_bucket_tracking_round_trip_like_python() { + let mut bucket = EnhancedMinuteBucket::new(1_700_000_000); + for milliseconds in [10.0, 20.0, 30.0] { + bucket.add_request( + milliseconds, + 200, + Some("alice"), + Some("rest:demo"), + Some("/items"), + Some("GET"), + 5, + 10, + 2, + ); + } + bucket.add_request( + 40.0, + 500, + Some("bob"), + Some("rest:demo"), + Some("/other"), + Some("POST"), + 0, + 20, + 2, + ); + assert_eq!(bucket.count, 4); + assert_eq!(bucket.error_count, 1); + assert_eq!( + bucket.latencies.iter().copied().collect::>(), + vec![30.0, 40.0] + ); + assert_eq!(bucket.get_unique_user_count(), 2); + assert_eq!(bucket.get_top_endpoints(1)[0]["count"], 3); + let value = bucket.to_dict(); + assert_eq!(value["avg_response_size"], 15.0); + let restored = EnhancedMinuteBucket::from_dict(&value).unwrap(); + assert_eq!(restored.count, 4); + assert_eq!( + restored.unique_users, + BTreeSet::from(["alice".to_owned(), "bob".to_owned()]) + ); + assert_eq!(restored.endpoint_metrics["GET:/items"].total_ms, 60.0); + assert!(restored.latencies.is_empty()); + } + + #[test] + fn aggregated_metrics_and_snapshot_shapes_match_python() { + let aggregated = AggregatedMetrics::from_dict(&json!({ + "start_ts": "100", "end_ts": 200, "level": "5minute", "count": 4, + "error_count": 1, "total_ms": 50.0, "bytes_in": 8, "bytes_out": 9, + "unique_users": 2, "status_counts": {"200": 3, "500": 1}, + "api_counts": {"rest:demo": 4}, "percentiles": {"p95": 20}, + "unique_users_list": ["alice", "bob"] + })) + .unwrap(); + let value = aggregated.to_dict(); + assert_eq!(value["level"], "5minute"); + assert_eq!(value["error_rate"], 0.25); + assert_eq!(value["avg_ms"], 12.5); + assert_eq!(value["percentiles"]["p95"], 20.0); + + let snapshot = AnalyticsSnapshot { + start_ts: 100, + end_ts: 200, + total_requests: 4, + total_errors: 1, + error_rate: 0.25, + avg_response_ms: 12.5, + percentiles: PercentileMetrics::default(), + total_bytes_in: 8, + total_bytes_out: 9, + unique_users: 2, + series: vec![json!({"count": 4})], + top_apis: vec![("rest:demo".to_owned(), 4)], + top_users: vec![("alice".to_owned(), 3)], + top_endpoints: vec![json!({"endpoint_uri": "/items"})], + status_distribution: BTreeMap::from([("200".to_owned(), 3)]), + }; + let snapshot = snapshot.to_dict(); + assert_eq!(snapshot["summary"]["total_requests"], 4); + assert_eq!( + snapshot["top_apis"][0], + json!({"api": "rest:demo", "count": 4}) + ); + assert_eq!( + snapshot["top_users"][0], + json!({"user": "alice", "count": 3}) + ); + } +} diff --git a/gateway-rs/src/observability/audit.rs b/gateway-rs/src/observability/audit.rs index df7afc82..73deee6f 100644 --- a/gateway-rs/src/observability/audit.rs +++ b/gateway-rs/src/observability/audit.rs @@ -28,6 +28,19 @@ static SENSITIVE_LOG_AUTH_SCHEME: LazyLock = LazyLock::new(|| { regex::Regex::new(r"(?i)\b(bearer|basic)\s+[A-Za-z0-9._~+/-]+={0,2}") .expect("sensitive authorization scheme expression is valid") }); +static SENSITIVE_VALUE: LazyLock> = LazyLock::new(|| { + [ + r"^eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$", + r"(?i)^Bearer\s+", + r"(?i)^Basic\s+[a-zA-Z0-9+/=]+$", + r"^sk-[a-zA-Z0-9]{32,}$", + r"^[a-fA-F0-9]{32,}$", + r"(?s)^-----BEGIN[A-Z\s]+PRIVATE KEY-----", + ] + .into_iter() + .map(|pattern| regex::Regex::new(pattern).expect("sensitive value expression is valid")) + .collect() +}); /// Sanitize a free-form log message before it reaches a non-structured sink. /// Structured audit records should use `redacted_headers` or `redacted_value` instead. @@ -56,6 +69,9 @@ pub fn redact_record(value: &mut serde_json::Value) { } } serde_json::Value::Array(values) => values.iter_mut().for_each(redact_record), + serde_json::Value::String(raw) if is_sensitive_value(raw) => { + *raw = REDACTED.to_owned(); + } _ => {} } } @@ -107,6 +123,34 @@ pub fn management_mutation(actor: &str, action: &str, target: &str, status: &str ); } +/// Mirrors Python's `SecurityAuditMiddleware`: a generic audit event for +/// every modification request and every `/platform/*` request (including +/// reads), independent of the specific named `management_mutation` events +/// application code already emits for successful mutations -- Python's own +/// middleware runs unconditionally alongside those, so this is intentionally +/// additional coverage, not a replacement. +pub fn platform_request( + actor: &str, + method: &str, + path: &str, + status_code: u16, + duration_ms: f64, + user_agent: Option<&str>, +) { + tracing::info!( + actor = %redacted_value("actor", actor), + action = %format!("{method} {path}"), + target = "platform", + status = if status_code < 400 { "success" } else { "failure" }, + method, + path, + status_code, + duration_ms, + user_agent, + "platform audit event" + ); +} + pub fn global_ip_deny(target: &str, reason: &str, source_ip: Option<&str>) { tracing::info!( action = "ip.global_deny", @@ -123,23 +167,35 @@ pub fn config_export(actor: &str, section: Option<&str>) { } fn is_sensitive_name(name: &str) -> bool { - let normalized = name.to_ascii_lowercase().replace('_', "-"); + let normalized = name.to_ascii_lowercase().replace('-', "_"); + if matches!( + normalized.as_str(), + "api_authorization_field_swap" | "authorization_field_swap" + ) { + return false; + } matches!( normalized.as_str(), - "authorization" | "proxy-authorization" | "cookie" | "set-cookie" + "authorization" | "proxy_authorization" | "cookie" | "set_cookie" ) || normalized.contains("password") || normalized.contains("secret") || normalized.contains("token") - || normalized.contains("api-key") + || normalized.contains("key") || normalized.contains("apikey") - || normalized.contains("credential") + || normalized.contains("auth") +} + +fn is_sensitive_value(value: &str) -> bool { + SENSITIVE_VALUE + .iter() + .any(|pattern| pattern.is_match(value)) } #[cfg(test)] mod tests { use http::{HeaderMap, HeaderValue, header}; - use super::{REDACTED, redacted_headers, redacted_log_message, redacted_value}; + use super::{REDACTED, redact_record, redacted_headers, redacted_log_message, redacted_value}; #[test] fn audit_header_redaction_never_exposes_credentials() { @@ -188,4 +244,84 @@ mod tests { assert!(redacted.contains(expected), "{redacted}"); } } + + #[test] + fn structured_records_match_python_key_and_value_redaction() { + let mut record = serde_json::json!({ + "signing_key": "secret", + "api_authorization_field_swap": "x-auth", + "nested": ["Bearer secret", "safe"] + }); + redact_record(&mut record); + assert_eq!(record["signing_key"], REDACTED); + assert_eq!(record["api_authorization_field_swap"], "x-auth"); + assert_eq!(record["nested"][0], REDACTED); + assert_eq!(record["nested"][1], "safe"); + } + + #[test] + fn platform_request_emits_pythons_middleware_shaped_audit_event() { + use std::{ + io, + sync::{Arc, Mutex}, + }; + + use tracing_subscriber::fmt::MakeWriter; + + #[derive(Clone, Default)] + struct CapturedLog(Arc>>); + impl io::Write for CapturedLog { + fn write(&mut self, buffer: &[u8]) -> io::Result { + self.0.lock().unwrap().extend_from_slice(buffer); + Ok(buffer.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + impl<'writer> MakeWriter<'writer> for CapturedLog { + type Writer = Self; + fn make_writer(&'writer self) -> Self::Writer { + self.clone() + } + } + + let capture = CapturedLog::default(); + let subscriber = tracing_subscriber::fmt() + .json() + .with_writer(capture.clone()) + .finish(); + tracing::subscriber::with_default(subscriber, || { + super::platform_request( + "alice", + "GET", + "/platform/user", + 200, + 12.5, + Some("test-agent"), + ); + }); + let output = String::from_utf8(capture.0.lock().unwrap().clone()).unwrap(); + let event: serde_json::Value = + serde_json::from_str(output.lines().next().unwrap()).unwrap(); + assert_eq!(event["fields"]["actor"], "alice"); + assert_eq!(event["fields"]["action"], "GET /platform/user"); + assert_eq!(event["fields"]["target"], "platform"); + assert_eq!(event["fields"]["status"], "success"); + assert_eq!(event["fields"]["status_code"], 200); + + // A failing status (>= 400) maps to Python's status='failure'. + let capture = CapturedLog::default(); + let subscriber = tracing_subscriber::fmt() + .json() + .with_writer(capture.clone()) + .finish(); + tracing::subscriber::with_default(subscriber, || { + super::platform_request("bob", "DELETE", "/platform/user/bob", 403, 1.0, None); + }); + let output = String::from_utf8(capture.0.lock().unwrap().clone()).unwrap(); + let event: serde_json::Value = + serde_json::from_str(output.lines().next().unwrap()).unwrap(); + assert_eq!(event["fields"]["status"], "failure"); + } } diff --git a/gateway-rs/src/observability/email.rs b/gateway-rs/src/observability/email.rs new file mode 100644 index 00000000..7c05f1f7 --- /dev/null +++ b/gateway-rs/src/observability/email.rs @@ -0,0 +1,27 @@ +use serde_json::Value; +use tracing::info; + +/// Log the legacy mock-email payload and report successful delivery. +pub async fn send_email( + to_email: &str, + subject: &str, + body: &str, + metadata: Option<&Value>, +) -> bool { + info!(to = to_email, subject, "📧 [MOCK EMAIL]"); + info!(body, "mock email body"); + if let Some(metadata) = metadata { + info!(metadata = %metadata, "mock email metadata"); + } + true +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn mock_email_always_succeeds() { + assert!(send_email("user@example.com", "Subject", "Body", None).await); + } +} diff --git a/gateway-rs/src/observability/logging.rs b/gateway-rs/src/observability/logging.rs index 6203cc1a..9d8d8852 100644 --- a/gateway-rs/src/observability/logging.rs +++ b/gateway-rs/src/observability/logging.rs @@ -1,20 +1,65 @@ -use std::fmt; +use std::{ + collections::VecDeque, + env, fmt, + sync::{LazyLock, Mutex}, +}; use tracing::{ Event, Subscriber, field::{Field, Visit}, }; use tracing_subscriber::{ - EnvFilter, + EnvFilter, Registry, fmt::{ FmtContext, format::{FormatEvent, FormatFields, Writer}, }, + layer::SubscriberExt, registry::LookupSpan, + reload, + util::SubscriberInitExt, }; use crate::observability::audit::redact_record; +const MEMORY_LOG_CAPACITY: usize = 50_000; +static MEMORY_LOGS: LazyLock>> = + LazyLock::new(|| Mutex::new(VecDeque::with_capacity(MEMORY_LOG_CAPACITY))); + +pub fn memory_log_snapshot() -> Vec { + if !stored_logs_enabled() { + return Vec::new(); + } + MEMORY_LOGS + .lock() + .map(|logs| logs.iter().cloned().collect()) + .unwrap_or_default() +} + +fn remember_log(entry: &str) { + if !stored_logs_enabled() { + return; + } + if let Ok(mut logs) = MEMORY_LOGS.lock() { + if logs.len() == MEMORY_LOG_CAPACITY { + logs.pop_front(); + } + logs.push_back(entry.to_owned()); + } +} + +fn stored_logs_enabled() -> bool { + env::var("LOGS_ENABLED") + .ok() + .map(|value| { + matches!( + value.to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) + .unwrap_or(true) +} + /// Formats complete tracing events as JSON only after recursively removing secrets. /// /// The formatter is deliberately applied at the sink boundary so a future @@ -46,6 +91,18 @@ where }); redact_record(&mut record); let encoded = serde_json::to_string(&record).map_err(|_| fmt::Error)?; + let memory_time = record + .get("timestamp") + .and_then(serde_json::Value::as_str) + .and_then(|timestamp| timestamp.get(..19)) + .unwrap_or_default(); + let memory_record = serde_json::json!({ + "time": memory_time, + "name": event.metadata().target(), + "level": event.metadata().level().to_string(), + "message": record.pointer("/fields/message").cloned().unwrap_or(serde_json::Value::String(String::new())), + }); + remember_log(&serde_json::to_string(&memory_record).map_err(|_| fmt::Error)?); writer.write_str(&encoded)?; writer.write_char('\n') } @@ -90,15 +147,52 @@ impl Visit for JsonFieldVisitor { } } +/// Handle to the live `EnvFilter`, letting `hot_reload` apply Python's dynamic +/// `LOG_LEVEL` behavior (`logging.getLogger().setLevel(new_value)`) without a +/// process restart, instead of only reporting the change as applied. +static RELOAD_HANDLE: Mutex>> = Mutex::new(None); + +fn default_filter() -> EnvFilter { + EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new("doorman_gateway=info,tower_http=info")) +} + pub fn init() { - let filter = EnvFilter::try_from_default_env() - .unwrap_or_else(|_| EnvFilter::new("doorman_gateway=info,tower_http=info")); - let _ = tracing_subscriber::fmt() - .with_env_filter(filter) - .event_format(RedactingJsonEvent) + let (filter, handle) = reload::Layer::new(default_filter()); + if let Ok(mut slot) = RELOAD_HANDLE.lock() { + *slot = Some(handle); + } + let _ = tracing_subscriber::registry() + .with(filter) + .with( + tracing_subscriber::fmt::layer() + .event_format(RedactingJsonEvent) + .with_writer(std::io::stdout), + ) .try_init(); } +/// Applies a new `LOG_LEVEL` (or full `EnvFilter` directive string) to the +/// running subscriber. Returns `false` when no subscriber has been +/// initialized yet (e.g. in unit tests) or the directive is invalid, in +/// which case the caller should not report the change as applied. +pub fn set_log_level(directive: &str) -> bool { + let Ok(slot) = RELOAD_HANDLE.lock() else { + return false; + }; + let Some(handle) = slot.as_ref() else { + return false; + }; + reload_handle_with(handle, directive) +} + +fn reload_handle_with(handle: &reload::Handle, directive: &str) -> bool { + let Ok(new_filter) = EnvFilter::builder().parse(directive) else { + return false; + }; + handle.reload(new_filter).is_ok() +} + #[cfg(test)] mod tests { use std::{ @@ -106,9 +200,9 @@ mod tests { sync::{Arc, Mutex}, }; - use tracing_subscriber::fmt::MakeWriter; + use tracing_subscriber::{fmt::MakeWriter, layer::SubscriberExt}; - use super::RedactingJsonEvent; + use super::{RedactingJsonEvent, default_filter, memory_log_snapshot, reload_handle_with}; #[derive(Clone, Default)] struct CapturedLog(Arc>>); @@ -158,5 +252,40 @@ mod tests { event["fields"]["message"], "Authorization: [REDACTED]; Cookie: [REDACTED]; Set-Cookie: [REDACTED]; X-API-Key: [REDACTED]; X-CSRF-Token: [REDACTED]; access_token=[REDACTED]; refresh_token=[REDACTED]; Basic [REDACTED]" ); + assert!(memory_log_snapshot().iter().any(|line| { + serde_json::from_str::(line) + .is_ok_and(|entry| entry["message"] == event["fields"]["message"]) + })); + } + + #[test] + fn dynamic_log_level_reload_takes_effect_without_a_restart() { + // Mirrors Python's hot_config callback: `logging.getLogger().setLevel(new_value)` + // applies immediately, rather than only being reported as reloaded. + let (filter, handle) = tracing_subscriber::reload::Layer::new(default_filter()); + let capture = CapturedLog::default(); + let subscriber = tracing_subscriber::registry().with(filter).with( + tracing_subscriber::fmt::layer() + .event_format(RedactingJsonEvent) + .with_writer(capture.clone()), + ); + + tracing::subscriber::with_default(subscriber, || { + tracing::debug!("before raising the level"); + assert!(reload_handle_with(&handle, "debug")); + tracing::debug!("after raising the level"); + }); + + let output = String::from_utf8(capture.0.lock().unwrap().clone()).unwrap(); + assert!( + !output.contains("before raising the level"), + "debug should be filtered out at the default info level: {output}" + ); + assert!( + output.contains("after raising the level"), + "debug should pass through once the filter is reloaded to debug: {output}" + ); + + assert!(!reload_handle_with(&handle, "((not a valid filter")); } } diff --git a/gateway-rs/src/observability/metrics.rs b/gateway-rs/src/observability/metrics.rs index 2e1cec87..efecbdb3 100644 --- a/gateway-rs/src/observability/metrics.rs +++ b/gateway-rs/src/observability/metrics.rs @@ -94,6 +94,26 @@ pub fn render(runtime: &GatewayRuntime) -> String { "doorman_active_requests {}", runtime.active_requests.load(Ordering::Relaxed) ); + output.push_str("# HELP doorman_tls_reload_total TLS configuration reload outcomes\n# TYPE doorman_tls_reload_total counter\n"); + let _ = writeln!( + output, + "doorman_tls_reload_total{{result=\"success\"}} {}", + runtime.tls_reload_success_total.load(Ordering::Relaxed) + ); + let _ = writeln!( + output, + "doorman_tls_reload_total{{result=\"failure\"}} {}", + runtime.tls_reload_failure_total.load(Ordering::Relaxed) + ); + output.push_str("# HELP doorman_tls_certificate_expiry_timestamp_seconds Certificate expiry as Unix time\n# TYPE doorman_tls_certificate_expiry_timestamp_seconds gauge\n"); + if let Ok(expiries) = runtime.tls_certificate_expiries.lock() { + for (profile, expiry) in expiries.iter() { + let _ = writeln!( + output, + "doorman_tls_certificate_expiry_timestamp_seconds{{profile=\"{profile}\"}} {expiry}" + ); + } + } output } diff --git a/gateway-rs/src/observability/mod.rs b/gateway-rs/src/observability/mod.rs index 0228385c..3b045483 100644 --- a/gateway-rs/src/observability/mod.rs +++ b/gateway-rs/src/observability/mod.rs @@ -1,5 +1,7 @@ pub mod analytics_aggregator; +pub mod analytics_models; pub mod audit; +pub mod email; pub mod logging; pub mod metrics; diff --git a/gateway-rs/src/platform_contract.rs b/gateway-rs/src/platform_contract.rs index 1758c062..7d175e78 100644 --- a/gateway-rs/src/platform_contract.rs +++ b/gateway-rs/src/platform_contract.rs @@ -46,9 +46,10 @@ const BOOL_FIELDS: &[&str] = &[ "api_graphql_subscriptions", "api_grpc_web_enabled", "api_is_crud", - "enforce_admin_subscription", ]; +const RUST_EXTENSION_BOOL_FIELDS: &[&str] = &["enforce_admin_subscription"]; + const INT_FIELDS: &[&str] = &["api_allowed_retry_count", "api_graphql_max_depth"]; const OBJECT_FIELDS: &[&str] = &[ @@ -59,6 +60,9 @@ const OBJECT_FIELDS: &[&str] = &[ ]; const RUST_EXTENSION_FIELDS: &[&str] = &[ + "api_client_tls_policy", + "api_upstream_tls_profile", + "api_upstream_tls_profiles", "api_wsdl_content", "api_openapi_schema", "api_graphql_schema", @@ -106,7 +110,15 @@ pub fn normalize_create_api(payload: &Value) -> Result> { pub fn normalize_update_api(payload: &Value) -> Result> { let mut normalized = normalize_api_fields(payload, true)?; if let Some(values) = normalized.as_object_mut() { - values.retain(|_, value| !value.is_null()); + values.retain(|field, value| { + !value.is_null() + || matches!( + field.as_str(), + "api_client_tls_policy" + | "api_upstream_tls_profile" + | "api_upstream_tls_profiles" + ) + }); } Ok(normalized) } @@ -177,6 +189,9 @@ fn normalize_api_fields(payload: &Value, update: bool) -> Result, key: Option, public_key: Option, + public_key_path: Option, verification_key: Option, - active: Option, } pub fn extract_token(headers: &HeaderMap) -> Option { @@ -63,8 +63,12 @@ pub fn verify_request_token( let algorithm = key.algorithm(); let mut validation = Validation::new(algorithm); validation.validate_exp = true; + // python-jose applies no clock leeway; an expired token is rejected at once. + validation.leeway = 0; validation.set_issuer(&[config.jwt_issuer.as_str()]); validation.set_audience(&[config.jwt_audience.as_str()]); + // A configured issuer/audience must be present, not merely valid if present. + validation.set_required_spec_claims(&["exp", "iss", "aud"]); let data = decode::(&token, &key.decoding_key()?, &validation) .map_err(|_| unauthorized("Unauthorized"))?; @@ -107,6 +111,7 @@ fn extract_cookie(headers: &HeaderMap, name: &str) -> Option { #[derive(Clone, Debug)] struct VerificationKey { + kid: Option, algorithm: String, secret: String, rsa: bool, @@ -137,6 +142,7 @@ fn select_key(config: &SharedStorageConfig, kid: Option<&str>) -> Option) -> Option return None, }; - configs + let keys = configs .into_iter() .filter_map(|value| serde_json::from_value::(value).ok()) - .filter(|config| config.active.unwrap_or(true)) - .find_map(|config| { - if let Some(expected) = kid { - if config.kid.as_deref() != Some(expected) { - return None; - } - } + .filter_map(|config| { let algorithm = config.algorithm.unwrap_or_else(|| "HS256".to_owned()); let secret = if algorithm.eq_ignore_ascii_case("RS256") { - config.public_key.or(config.verification_key) + config + .public_key_path + .and_then(|path| std::fs::read_to_string(path).ok()) + .or(config.public_key) + .or(config.verification_key) } else { config.secret.or(config.key).or(config.verification_key) }?; Some(VerificationKey { + kid: config.kid, rsa: algorithm.eq_ignore_ascii_case("RS256"), algorithm, secret, }) }) + .collect::>(); + if let Some(kid) = kid { + return keys.into_iter().find(|key| key.kid.as_deref() == Some(kid)); + } + if keys.len() == 1 { + return keys.into_iter().next(); + } + keys.into_iter() + .find(|key| key.kid.as_deref() == Some("legacy-key")) } pub fn unauthorized(message: &str) -> PolicyFailure { @@ -224,4 +238,59 @@ mod tests { headers.insert(header::AUTHORIZATION, HeaderValue::from_static("raw-token")); assert_eq!(extract_token(&headers), Some("raw-token".to_owned())); } + + #[test] + fn tokens_missing_issuer_or_audience_are_rejected() { + let config = SharedStorageConfig { + jwt_secret: Some("unit-test-secret".to_owned()), + ..SharedStorageConfig::default() + }; + let exp = jsonwebtoken::get_current_timestamp() + 600; + let full = serde_json::json!({ + "sub": "user", "jti": "id", "exp": exp, + "iss": config.jwt_issuer, "aud": config.jwt_audience, + }); + let verify = |claims: &serde_json::Value| { + let token = jsonwebtoken::encode( + &jsonwebtoken::Header::default(), + claims, + &jsonwebtoken::EncodingKey::from_secret(b"unit-test-secret"), + ) + .unwrap(); + let mut headers = HeaderMap::new(); + headers.insert( + header::AUTHORIZATION, + HeaderValue::from_str(&format!("Bearer {token}")).unwrap(), + ); + verify_request_token(&headers, &config) + }; + assert!(verify(&full).is_ok()); + for claim in ["iss", "aud"] { + let mut partial = full.clone(); + partial.as_object_mut().unwrap().remove(claim); + assert!(verify(&partial).is_err(), "token without {claim} accepted"); + } + } + + #[test] + fn configured_verification_key_selection_matches_legacy_kid_rules() { + let keys = r#"[ + {"kid":"one","algorithm":"HS256","secret":"first","active":false}, + {"kid":"two","algorithm":"HS256","secret":"second"} + ]"#; + assert!(select_configured_key(keys, None).is_none()); + assert_eq!( + select_configured_key(keys, Some("one")).unwrap().secret, + "first" + ); + + let legacy = r#"[ + {"kid":"one","algorithm":"HS256","secret":"first"}, + {"kid":"legacy-key","algorithm":"HS256","secret":"legacy"} + ]"#; + assert_eq!( + select_configured_key(legacy, None).unwrap().secret, + "legacy" + ); + } } diff --git a/gateway-rs/src/policy/bandwidth.rs b/gateway-rs/src/policy/bandwidth.rs index 5bfff3d8..d10e543b 100644 --- a/gateway-rs/src/policy/bandwidth.rs +++ b/gateway-rs/src/policy/bandwidth.rs @@ -1,13 +1,26 @@ use http::StatusCode; use serde_json::Value; -use super::{PolicyFailure, PolicyStage, rate_limit::duration_to_seconds}; +use super::{PolicyFailure, PolicyStage}; use crate::storage::{ cache::WindowCounter, models::{bool_field, string_field, u64_field}, redis::bandwidth_key, }; +/// Python `_window_to_seconds`: strips every trailing `s`; unknown or empty windows are one day. +pub fn window_seconds(window: &str) -> u64 { + match window.to_ascii_lowercase().trim_end_matches('s') { + "second" => 1, + "minute" => 60, + "hour" => 3600, + "day" => 86400, + "week" => 604800, + "month" => 2592000, + _ => 86400, + } +} + pub fn enforce_pre_request_limit( username: &str, user: &Value, @@ -25,7 +38,7 @@ pub fn enforce_pre_request_limit( return Ok(()); } let window = string_field(user, "bandwidth_limit_window").unwrap_or("day"); - let seconds = duration_to_seconds(window); + let seconds = window_seconds(window); let bucket = (now_seconds / seconds) * seconds; let used = counter.get(&bandwidth_key(username, seconds, bucket), now_seconds); if used >= limit || used + content_length > limit { @@ -45,6 +58,22 @@ mod tests { use super::*; use serde_json::json; + #[test] + fn window_mapping_matches_python_defaults() { + for (window, seconds) in [ + ("second", 1), + ("Minutes", 60), + ("hourss", 3600), + ("week", 604800), + ("month", 2592000), + ("year", 86400), + ("fortnight", 86400), + ("", 86400), + ] { + assert_eq!(window_seconds(window), seconds, "{window}"); + } + } + #[test] fn rejects_over_limit_request_body() { let user = json!({ diff --git a/gateway-rs/src/policy/credits.rs b/gateway-rs/src/policy/credits.rs index 7e8fe46e..a2bf8c34 100644 --- a/gateway-rs/src/policy/credits.rs +++ b/gateway-rs/src/policy/credits.rs @@ -3,7 +3,10 @@ use serde_json::Value; use time::{OffsetDateTime, format_description::well_known::Rfc3339}; use super::{PolicyFailure, PolicyStage}; -use crate::storage::models::{bool_field_default, object_field, string_field, u64_field}; +use crate::storage::{ + field_encryption::decrypt_value, + models::{bool_field_default, object_field, string_field, u64_field}, +}; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct CreditDecision { @@ -18,8 +21,10 @@ pub fn credit_header_values( now: OffsetDateTime, ) -> Option<(String, Vec)> { let header = string_field(definition, "api_key_header")?.to_owned(); - let old = string_field(definition, "api_key").map(str::to_owned); - let new = string_field(definition, "api_key_new").map(str::to_owned); + let old_encrypted = string_field(definition, "api_key"); + let old = decrypt_value(old_encrypted).or_else(|| old_encrypted.map(str::to_owned)); + let new_encrypted = string_field(definition, "api_key_new"); + let new = decrypt_value(new_encrypted).or_else(|| new_encrypted.map(str::to_owned)); let expires = string_field(definition, "api_key_rotation_expires") .and_then(|value| OffsetDateTime::parse(value, &Rfc3339).ok()); let values = match (old, new, expires) { @@ -83,7 +88,7 @@ pub fn evaluate_credits( .and_then(|doc| object_field(doc, "users_credits")) .and_then(|credits| credits.get(group)) .and_then(|credit| string_field(credit, "user_api_key")) - .map(str::to_owned); + .and_then(|value| decrypt_value(Some(value)).or_else(|| Some(value.to_owned()))); Ok(CreditDecision { required: true, diff --git a/gateway-rs/src/policy/evaluator.rs b/gateway-rs/src/policy/evaluator.rs index 1ca4b433..6b330585 100644 --- a/gateway-rs/src/policy/evaluator.rs +++ b/gateway-rs/src/policy/evaluator.rs @@ -30,6 +30,10 @@ use crate::{ redis::{bandwidth_key, rate_limit_key, throttle_key}, runtime::SharedStorage, }, + tls::{ + policy::{ClientTlsFailure, ClientTlsPolicy}, + profiles::{TlsProfiles, select_upstream_profile}, + }, }; #[derive(Clone, Debug)] @@ -40,14 +44,27 @@ pub struct PolicyRequest { pub direct_ip: Option, pub now_millis: u64, pub content_length: u64, + pub peer_certificates: Vec>, + pub native_tls: bool, + pub is_preflight: bool, + /// The pinned group check resolves a gRPC API under `X-API-Version` + /// defaulting to `v0`, so a request without the header fails it. + pub group_version_missing: bool, + /// The pinned REST route matches the endpoint before authenticating + /// (GTW003, even when the API has no endpoints at all). + pub route_endpoint_check: bool, + /// Only the pinned REST and SOAP gateways require a registered endpoint; + /// GraphQL and gRPC forward any call for an existing API. + pub endpoint_required: bool, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Default)] pub struct PolicyRuntime { pub rate_counter: WindowCounter, pub rate_bucket_counter: TokenBucketCounter, pub throttle_counter: WindowCounter, pub bandwidth_counter: WindowCounter, + pub tls_profiles: std::sync::Arc, } pub fn evaluate_rest_policy( @@ -55,6 +72,26 @@ pub fn evaluate_rest_policy( request: &PolicyRequest, storage_config: &SharedStorageConfig, runtime: &PolicyRuntime, +) -> Result, PolicyFailure> { + match evaluate_rest_policy_staged(documents, request, storage_config, runtime)? { + Some(decision) if decision.deferred_failure.is_some() => { + Err(decision.deferred_failure.expect("checked above")) + } + other => Ok(other), + } +} + +/// Evaluates in the pinned order: IP policy and CORS preflight, then the +/// route-level identity checks for private APIs (authentication, subscription, +/// group, user limits), and only then the service-level checks (API active, +/// endpoint existence, credits). A service-level failure is returned as +/// `deferred_failure` so the caller can first apply the tier limits that the +/// pinned middleware enforces ahead of every route. +pub fn evaluate_rest_policy_staged( + documents: &mut PolicyDocuments, + request: &PolicyRequest, + storage_config: &SharedStorageConfig, + runtime: &PolicyRuntime, ) -> Result, PolicyFailure> { let Some(route) = resolve_rest_path(&request.path, &request.headers) else { return Ok(None); @@ -62,14 +99,6 @@ pub fn evaluate_rest_policy( let Some(api) = find_api(&documents.apis, &route.api_name, &route.api_version).cloned() else { return Ok(None); }; - if bool_field(&api, "active") == Some(false) { - return Err(PolicyFailure::new( - PolicyStage::Resolution, - StatusCode::FORBIDDEN, - "GTW012", - "API is disabled", - )); - } let settings = documents .settings .iter() @@ -106,18 +135,153 @@ pub fn evaluate_rest_policy( } else { request.method.as_str() }; - if !endpoint_exists(&documents.endpoints, &api, method, &route.endpoint_uri) { - return Err(PolicyFailure::new( + if request.route_endpoint_check + && !endpoint_exists(&documents.endpoints, &api, method, &route.endpoint_uri) + { + return Ok(Some(PolicyDecision { + deferred_failure: Some(PolicyFailure::new( + PolicyStage::Resolution, + StatusCode::NOT_FOUND, + "GTW003", + "Endpoint does not exist for the requested API", + )), + ..Default::default() + })); + } + let api_public = bool_field(&api, "api_public").unwrap_or(false); + let api_auth_required = bool_field(&api, "api_auth_required").unwrap_or(true); + let mut authenticated: Option<(String, Option)> = None; + if !api_public && api_auth_required { + let claims = verify_request_token(&request.headers, storage_config)?; + let username = claims.sub.as_deref().unwrap_or_default(); + if is_revoked(&documents.revocations, username, claims.jti.as_deref()) { + // auth_required's broad except re-raises every failure as a bare Unauthorized. + return Err(super::auth::unauthorized("Unauthorized")); + } + let user = documents + .users + .iter() + .find(|item| string_field(item, "username") == Some(username)) + .cloned() + .ok_or_else(|| { + PolicyFailure::new( + PolicyStage::Authentication, + StatusCode::NOT_FOUND, + "User not found", + "User not found", + ) + })?; + if bool_field(&user, "active") == Some(false) { + return Err(super::auth::unauthorized("Unauthorized")); + } + + let enforce_admin_sub = std::env::var("ENFORCE_ADMIN_SUBSCRIPTION") + .map(|v| v.eq_ignore_ascii_case("true")) + .unwrap_or_else(|_| { + api.get("enforce_admin_subscription") + .and_then(Value::as_bool) + .unwrap_or(false) + }); + + enforce_subscription( + &format!("{}/{}", route.api_name, route.api_version), + &user, + &documents.roles, + &documents.subscriptions, + enforce_admin_sub, + )?; + if request.group_version_missing { + return Err(PolicyFailure::new( + PolicyStage::Group, + StatusCode::NOT_FOUND, + "API not found", + "API not found", + )); + } + enforce_group_access(&api, &user)?; + enforce_rate_limit( + username, + &user, + &runtime.rate_counter, + &runtime.rate_bucket_counter, + request.now_millis, + )?; + let throttle = enforce_throttle( + username, + &user, + &runtime.throttle_counter, + request.now_millis, + )?; + enforce_allowed_roles(&api, &user)?; + enforce_pre_request_limit( + username, + &user, + &runtime.bandwidth_counter, + request.now_millis / 1000, + request.content_length, + )?; + authenticated = Some((username.to_owned(), throttle.delay_ms)); + } + + let deferred = if bool_field(&api, "active") == Some(false) { + Some(PolicyFailure::new( + PolicyStage::Resolution, + StatusCode::FORBIDDEN, + "GTW012", + "API is disabled", + )) + } else if request.endpoint_required + && !documents + .endpoints + .iter() + .any(|endpoint| endpoint_belongs_to_api(endpoint, &api)) + { + Some(PolicyFailure::new( + PolicyStage::Resolution, + StatusCode::NOT_FOUND, + "GTW002", + "No endpoints found for the requested API", + )) + } else if request.endpoint_required + && !endpoint_exists(&documents.endpoints, &api, method, &route.endpoint_uri) + { + Some(PolicyFailure::new( PolicyStage::Resolution, StatusCode::NOT_FOUND, "GTW003", "Endpoint does not exist for the requested API", - )); + )) + } else { + None + }; + let username = authenticated.as_ref().map(|(name, _)| name.clone()); + let tier_username = username.clone().or_else(|| { + verify_request_token(&request.headers, storage_config) + .ok() + .and_then(|claims| claims.sub) + }); + if let Some(failure) = deferred { + return Ok(Some(PolicyDecision { + username, + tier_username, + tier_rate_limit_enabled: !storage_config.skip_tier_rate_limit, + deferred_failure: Some(failure), + ..Default::default() + })); } let endpoint = find_endpoint(&documents.endpoints, &api, method, &route.endpoint_uri).cloned(); + if !request.is_preflight { + let policy = + ClientTlsPolicy::from_documents(&api, endpoint.as_ref()).map_err(client_tls_failure)?; + policy + .enforce( + &request.peer_certificates, + &runtime.tls_profiles, + request.native_tls, + ) + .map_err(client_tls_failure)?; + } - let api_public = bool_field(&api, "api_public").unwrap_or(false); - let api_auth_required = bool_field(&api, "api_auth_required").unwrap_or(true); let endpoint_id = endpoint .as_ref() .and_then(|item| string_field(item, "endpoint_id")); @@ -202,86 +366,22 @@ pub fn evaluate_rest_policy( ..Default::default() }; - if !api_public && api_auth_required { - let claims = verify_request_token(&request.headers, storage_config)?; - let username = claims.sub.as_deref().unwrap_or_default(); - if is_revoked(&documents.revocations, username, claims.jti.as_deref()) { - return Err(super::auth::unauthorized("Token has been revoked")); - } - let user = documents - .users - .iter() - .find(|item| string_field(item, "username") == Some(username)) - .cloned() - .ok_or_else(|| { - PolicyFailure::new( - PolicyStage::Authentication, - StatusCode::NOT_FOUND, - "User not found", - "User not found", - ) - })?; - if bool_field(&user, "active") == Some(false) { - return Err(super::auth::unauthorized("User is inactive")); - } - - let enforce_admin_sub = std::env::var("ENFORCE_ADMIN_SUBSCRIPTION") - .map(|v| v.eq_ignore_ascii_case("true") || v == "1") - .unwrap_or_else(|_| { - api.get("enforce_admin_subscription") - .and_then(Value::as_bool) - .unwrap_or(true) - }); - - enforce_subscription( - &format!("{}/{}", route.api_name, route.api_version), - &user, - &documents.roles, - &documents.subscriptions, - enforce_admin_sub, - )?; - enforce_group_access(&api, &user)?; - enforce_rate_limit( - username, - &user, - &runtime.rate_counter, - &runtime.rate_bucket_counter, - request.now_millis, - )?; - let throttle = enforce_throttle( - username, - &user, - &runtime.throttle_counter, - request.now_millis, - )?; - enforce_allowed_roles(&api, &user)?; - enforce_pre_request_limit( - username, - &user, - &runtime.bandwidth_counter, - request.now_millis / 1000, - request.content_length, - )?; + if let Some((username, throttle_delay_ms)) = authenticated { let credit = evaluate_credits( &api, - Some(username), + Some(&username), &documents.credit_defs, &documents.user_credits, )?; - decision.username = Some(username.to_owned()); - decision.throttle_delay_ms = throttle.delay_ms; + decision.throttle_delay_ms = throttle_delay_ms; decision.credit_required = credit.required; decision.credit_group = string_field(&api, "api_credit_group").map(str::to_owned); decision.credit_header_name = credit.header_name; decision.credit_header_value = credit.header_value; decision.user_credit_header_value = credit.user_header_value; + decision.username = Some(username); } - - decision.tier_username = decision.username.clone().or_else(|| { - verify_request_token(&request.headers, storage_config) - .ok() - .and_then(|claims| claims.sub) - }); + decision.tier_username = tier_username; let client_key = request .headers @@ -296,6 +396,33 @@ pub fn evaluate_rest_policy( &route.endpoint_uri, client_key, ) { + let profile = + select_upstream_profile(&api, endpoint.as_ref(), &upstream.url).map_err(|_| { + PolicyFailure::new( + PolicyStage::Resolution, + StatusCode::SERVICE_UNAVAILABLE, + "TLS004", + "Upstream TLS binding is invalid", + ) + })?; + if let Some(id) = profile.as_deref() { + if !matches!( + url::Url::parse(&upstream.url) + .ok() + .map(|url| url.scheme().to_owned()) + .as_deref(), + Some("https" | "grpcs") + ) || !runtime.tls_profiles.upstreams.contains_key(id) + { + return Err(PolicyFailure::new( + PolicyStage::Resolution, + StatusCode::SERVICE_UNAVAILABLE, + "TLS004", + "Upstream TLS profile is unavailable", + )); + } + } + decision.upstream_tls_profile_id = profile; decision.upstream = Some(upstream.url); decision.routing_key = Some(upstream.key); decision.routing_servers = upstream.servers; @@ -305,6 +432,27 @@ pub fn evaluate_rest_policy( Ok(Some(decision)) } +fn client_tls_failure(failure: ClientTlsFailure) -> PolicyFailure { + let (status, code, message) = match failure { + ClientTlsFailure::Missing => ( + StatusCode::UNAUTHORIZED, + "TLS001", + "Client certificate required", + ), + ClientTlsFailure::Invalid => ( + StatusCode::FORBIDDEN, + "TLS002", + "Client certificate is not authorized", + ), + ClientTlsFailure::Configuration => ( + StatusCode::SERVICE_UNAVAILABLE, + "TLS003", + "Client TLS policy is unavailable", + ), + }; + PolicyFailure::new(PolicyStage::Authentication, status, code, message) +} + fn default_http_read_timeout_ms() -> u64 { env::var("HTTP_READ_TIMEOUT") .ok() @@ -349,9 +497,9 @@ pub async fn evaluate_shared_effects( })?; let rate_enabled = bool_field_default(user, "rate_limit_enabled", false) - || user.get("rate_limit_duration").is_some(); + || super::rate_limit::truthy_count(user, "rate_limit_duration").is_some(); if rate_enabled { - let limit = u64_field(user, "rate_limit_duration").unwrap_or(60); + let limit = super::rate_limit::truthy_count(user, "rate_limit_duration").unwrap_or(60); let window = super::rate_limit::duration_to_seconds( string_field(user, "rate_limit_duration_type").unwrap_or("minute"), ) @@ -369,17 +517,18 @@ pub async fn evaluate_shared_effects( } let throttle_enabled = bool_field_default(user, "throttle_enabled", false) - || user.get("throttle_duration").is_some() - || user.get("throttle_queue_limit").is_some(); + || super::rate_limit::truthy_count(user, "throttle_duration").is_some() + || super::rate_limit::truthy_count(user, "throttle_queue_limit").is_some(); if throttle_enabled { - let limit = u64_field(user, "throttle_duration").unwrap_or(10); + let limit = super::rate_limit::truthy_count(user, "throttle_duration").unwrap_or(10); let window = super::rate_limit::duration_to_seconds( string_field(user, "throttle_duration_type").unwrap_or("second"), ) .max(1); let key = throttle_key(username, request.now_millis / (window * 1000)); let count = shared_counter(storage, &key, window, mutate).await?; - let queue_limit = u64_field(user, "throttle_queue_limit").unwrap_or(10); + let queue_limit = + super::rate_limit::truthy_count(user, "throttle_queue_limit").unwrap_or(10); let excess = count.saturating_sub(limit); if queue_limit > 0 && (count > queue_limit || excess > queue_limit) { return Err(PolicyFailure::new( @@ -397,21 +546,20 @@ pub async fn evaluate_shared_effects( if bool_field(user, "bandwidth_limit_enabled") != Some(false) { if let Some(limit) = u64_field(user, "bandwidth_limit_bytes").filter(|limit| *limit > 0) { - let window = super::rate_limit::duration_to_seconds( + let window = super::bandwidth::window_seconds( string_field(user, "bandwidth_limit_window").unwrap_or("day"), - ) - .max(1); + ); let now_seconds = request.now_millis / 1000; let bucket = (now_seconds / window) * window; let key = bandwidth_key(username, window, bucket); - let total = storage + let used = storage .current_counter(&key) .await - .map_err(storage_failure)? - .saturating_add(request.content_length); + .map_err(storage_failure)?; + let total = used.saturating_add(request.content_length); decision.bandwidth_key = Some(key); decision.bandwidth_ttl_seconds = Some(window); - if total > limit { + if used >= limit || total > limit { return Err(PolicyFailure::new( PolicyStage::Bandwidth, StatusCode::TOO_MANY_REQUESTS, @@ -503,8 +651,7 @@ fn storage_failure(error: crate::storage::runtime::StorageError) -> PolicyFailur fn endpoint_exists(endpoints: &[Value], api: &Value, method: &str, endpoint_uri: &str) -> bool { endpoints.iter().any(|endpoint| { - let same_api = string_field(endpoint, "api_name") == string_field(api, "api_name") - && string_field(endpoint, "api_version") == string_field(api, "api_version"); + let same_api = endpoint_belongs_to_api(endpoint, api); let same_method = string_field(endpoint, "endpoint_method") .is_some_and(|actual| actual.eq_ignore_ascii_case(method)); let uri = @@ -515,6 +662,19 @@ fn endpoint_exists(endpoints: &[Value], api: &Value, method: &str, endpoint_uri: }) } +fn endpoint_belongs_to_api(endpoint: &Value, api: &Value) -> bool { + match ( + string_field(endpoint, "api_id"), + string_field(api, "api_id"), + ) { + (Some(endpoint_id), Some(api_id)) => endpoint_id == api_id, + _ => { + string_field(endpoint, "api_name") == string_field(api, "api_name") + && string_field(endpoint, "api_version") == string_field(api, "api_version") + } + } +} + fn optional_string_list(value: &Value, field: &str) -> Option> { value.get(field).and_then(Value::as_array).map(|items| { items @@ -526,6 +686,10 @@ fn optional_string_list(value: &Value, field: &str) -> Option> { } pub(crate) fn is_revoked(revocations: &[Value], username: &str, jti: Option<&str>) -> bool { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); revocations .iter() .any(|revocation| match string_field(revocation, "type") { @@ -536,6 +700,10 @@ pub(crate) fn is_revoked(revocations: &[Value], username: &str, jti: Option<&str Some("jti") => { string_field(revocation, "username") == Some(username) && jti.is_some_and(|jti| string_field(revocation, "jti") == Some(jti)) + && revocation + .get("expires_at") + .and_then(Value::as_u64) + .is_some_and(|expires_at| expires_at > now) } _ => false, }) @@ -547,6 +715,29 @@ mod tests { use http::HeaderValue; use serde_json::json; + #[test] + fn expired_or_malformed_jti_revocations_do_not_block_requests() { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + assert!(!is_revoked( + &[json!({"type":"jti", "username":"alice", "jti":"old", "expires_at": now})], + "alice", + Some("old") + )); + assert!(!is_revoked( + &[json!({"type":"jti", "username":"alice", "jti":"missing"})], + "alice", + Some("missing") + )); + assert!(is_revoked( + &[json!({"type":"jti", "username":"alice", "jti":"live", "expires_at": now + 60})], + "alice", + Some("live") + )); + } + #[test] fn mixed_settings_records_cannot_override_api_proxy_policy() { let mut documents = PolicyDocuments { @@ -573,6 +764,12 @@ mod tests { direct_ip: Some("10.0.0.2".parse().unwrap()), now_millis: 0, content_length: 0, + peer_certificates: Vec::new(), + native_tls: false, + is_preflight: false, + group_version_missing: false, + route_endpoint_check: false, + endpoint_required: true, }; let failure = evaluate_rest_policy( &mut documents, @@ -623,6 +820,12 @@ mod tests { direct_ip: None, now_millis: 0, content_length: 0, + peer_certificates: Vec::new(), + native_tls: false, + is_preflight: false, + group_version_missing: false, + route_endpoint_check: false, + endpoint_required: true, }; let failure = evaluate_rest_policy( &mut documents, @@ -634,6 +837,45 @@ mod tests { assert_eq!(failure.error_code, "GTW003"); } + #[test] + fn returns_no_endpoints_for_api_without_registered_endpoints() { + let mut documents = PolicyDocuments { + apis: vec![json!({ + "api_id": "api-1", + "api_name": "demo", + "api_version": "v1", + "api_public": true, + })], + ..Default::default() + }; + let request = PolicyRequest { + method: Method::GET, + path: "/api/rest/demo/v1/missing".to_owned(), + headers: HeaderMap::new(), + direct_ip: None, + now_millis: 0, + content_length: 0, + peer_certificates: Vec::new(), + native_tls: false, + is_preflight: false, + group_version_missing: false, + route_endpoint_check: false, + endpoint_required: true, + }; + let failure = evaluate_rest_policy( + &mut documents, + &request, + &SharedStorageConfig::default(), + &PolicyRuntime::default(), + ) + .unwrap_err(); + assert_eq!(failure.error_code, "GTW002"); + assert_eq!( + failure.error_message, + "No endpoints found for the requested API" + ); + } + #[test] fn records_client_key_upstream_selection() { let mut headers = HeaderMap::new(); @@ -665,6 +907,12 @@ mod tests { direct_ip: None, now_millis: 0, content_length: 0, + peer_certificates: Vec::new(), + native_tls: false, + is_preflight: false, + group_version_missing: false, + route_endpoint_check: false, + endpoint_required: true, }; let decision = evaluate_rest_policy( &mut documents, diff --git a/gateway-rs/src/policy/geo.rs b/gateway-rs/src/policy/geo.rs new file mode 100644 index 00000000..e9950f49 --- /dev/null +++ b/gateway-rs/src/policy/geo.rs @@ -0,0 +1,284 @@ +//! Placeholder geographic lookup and country policy from the Python backend. + +use std::{ + collections::{HashMap, HashSet}, + sync::{Arc, Mutex}, + time::{Duration, Instant}, +}; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct GeoLocation { + pub ip: String, + pub country_code: Option, + pub country_name: Option, + pub region: Option, + pub city: Option, + pub latitude: Option, + pub longitude: Option, + pub timezone: Option, +} + +#[derive(Clone, Debug)] +struct Expiring { + value: T, + expires_at: Instant, +} + +#[derive(Clone, Debug, Default)] +struct GeoState { + cache: HashMap>, + blocked: HashSet, + allowed: HashSet, + rate_limits: HashMap, + requests: HashMap>, +} + +#[derive(Clone, Debug)] +pub struct GeoLookup { + state: Arc>, + cache_ttl: Duration, +} + +impl Default for GeoLookup { + fn default() -> Self { + Self { + state: Arc::new(Mutex::new(GeoState::default())), + cache_ttl: Duration::from_secs(86_400), + } + } +} + +impl GeoLookup { + pub fn lookup_ip(&self, ip: &str) -> GeoLocation { + let mut state = self.state.lock().expect("geo state mutex poisoned"); + if state + .cache + .get(ip) + .is_some_and(|entry| entry.expires_at <= Instant::now()) + { + state.cache.remove(ip); + } + if let Some(entry) = state.cache.get(ip) { + return entry.value.clone(); + } + let geo = GeoLocation { + ip: ip.to_owned(), + country_code: Some("UNKNOWN".to_owned()), + ..GeoLocation::default() + }; + state.cache.insert( + ip.to_owned(), + Expiring { + value: geo.clone(), + expires_at: Instant::now() + self.cache_ttl, + }, + ); + geo + } + + pub fn cache_geo_data(&self, ip: &str, mut geo: GeoLocation) { + geo.ip = ip.to_owned(); + // Python serializes zero coordinates as an empty string, which reloads + // as None. Preserve that pinned edge case. + if geo.latitude == Some(0.0) { + geo.latitude = None; + } + if geo.longitude == Some(0.0) { + geo.longitude = None; + } + self.state + .lock() + .expect("geo state mutex poisoned") + .cache + .insert( + ip.to_owned(), + Expiring { + value: geo, + expires_at: Instant::now() + self.cache_ttl, + }, + ); + } + + pub fn is_country_blocked(&self, country_code: &str) -> bool { + self.state + .lock() + .expect("geo state mutex poisoned") + .blocked + .contains(country_code) + } + + pub fn is_country_allowed(&self, country_code: &str) -> bool { + let state = self.state.lock().expect("geo state mutex poisoned"); + state.allowed.is_empty() || state.allowed.contains(country_code) + } + + pub fn block_country(&self, country_code: &str) -> bool { + self.state + .lock() + .expect("geo state mutex poisoned") + .blocked + .insert(country_code.to_owned()); + true + } + + pub fn unblock_country(&self, country_code: &str) -> bool { + self.state + .lock() + .expect("geo state mutex poisoned") + .blocked + .remove(country_code); + true + } + + pub fn add_to_allowlist(&self, country_code: &str) -> bool { + self.state + .lock() + .expect("geo state mutex poisoned") + .allowed + .insert(country_code.to_owned()); + true + } + + pub fn remove_from_allowlist(&self, country_code: &str) -> bool { + self.state + .lock() + .expect("geo state mutex poisoned") + .allowed + .remove(country_code); + true + } + + pub fn get_country_rate_limit(&self, country_code: &str) -> Option { + self.state + .lock() + .expect("geo state mutex poisoned") + .rate_limits + .get(country_code) + .copied() + } + + pub fn set_country_rate_limit(&self, country_code: &str, limit: i64) -> bool { + self.state + .lock() + .expect("geo state mutex poisoned") + .rate_limits + .insert(country_code.to_owned(), limit); + true + } + + pub fn check_geographic_access(&self, ip: &str) -> (bool, Option) { + let geo = self.lookup_ip(ip); + let Some(country) = geo + .country_code + .as_deref() + .filter(|country| *country != "UNKNOWN") + else { + return (true, None); + }; + if self.is_country_blocked(country) { + return (false, Some(format!("Country {country} is blocked"))); + } + if !self.is_country_allowed(country) { + return ( + false, + Some(format!("Country {country} is not in allowlist")), + ); + } + (true, None) + } + + pub fn track_country_request(&self, country_code: &str) { + let mut state = self.state.lock().expect("geo state mutex poisoned"); + let now = Instant::now(); + let entry = state + .requests + .entry(country_code.to_owned()) + .or_insert(Expiring { + value: 0, + expires_at: now + Duration::from_secs(86_400), + }); + if entry.expires_at <= now { + entry.value = 0; + } + entry.value = entry.value.saturating_add(1); + entry.expires_at = now + Duration::from_secs(86_400); + } + + pub fn get_geographic_distribution(&self) -> Vec<(String, u64)> { + let now = Instant::now(); + let mut state = self.state.lock().expect("geo state mutex poisoned"); + state.requests.retain(|_, entry| entry.expires_at > now); + let mut values = state + .requests + .iter() + .map(|(country, entry)| (country.clone(), entry.value)) + .collect::>(); + values.sort_by(|left, right| right.1.cmp(&left.1)); + values + } + + pub fn get_blocked_countries(&self) -> HashSet { + self.state + .lock() + .expect("geo state mutex poisoned") + .blocked + .clone() + } + + pub fn get_allowed_countries(&self) -> HashSet { + self.state + .lock() + .expect("geo state mutex poisoned") + .allowed + .clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unknown_locations_fail_open_like_python() { + let lookup = GeoLookup::default(); + assert_eq!( + lookup.lookup_ip("203.0.113.2").country_code.as_deref(), + Some("UNKNOWN") + ); + assert_eq!(lookup.check_geographic_access("203.0.113.2"), (true, None)); + } + + #[test] + fn country_lists_limits_and_distribution_match_python() { + let lookup = GeoLookup::default(); + lookup.cache_geo_data( + "203.0.113.3", + GeoLocation { + country_code: Some("CA".to_owned()), + ..GeoLocation::default() + }, + ); + lookup.block_country("CA"); + assert_eq!( + lookup.check_geographic_access("203.0.113.3"), + (false, Some("Country CA is blocked".to_owned())) + ); + lookup.unblock_country("CA"); + lookup.add_to_allowlist("US"); + assert_eq!( + lookup.check_geographic_access("203.0.113.3"), + (false, Some("Country CA is not in allowlist".to_owned())) + ); + assert!(lookup.set_country_rate_limit("CA", 25)); + assert_eq!(lookup.get_country_rate_limit("CA"), Some(25)); + lookup.track_country_request("CA"); + lookup.track_country_request("CA"); + lookup.track_country_request("US"); + assert_eq!( + lookup.get_geographic_distribution()[0], + ("CA".to_owned(), 2) + ); + } +} diff --git a/gateway-rs/src/policy/ip.rs b/gateway-rs/src/policy/ip.rs index 37455fad..b7535665 100644 --- a/gateway-rs/src/policy/ip.rs +++ b/gateway-rs/src/policy/ip.rs @@ -133,7 +133,18 @@ pub fn effective_client_ip( } fn is_loopback(ip: IpAddr) -> bool { - ip.is_loopback() + if ip.is_loopback() { + return true; + } + std::env::var("DOORMAN_IN_DOCKER") + .ok() + .is_some_and(|value| { + matches!( + value.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" + ) + }) + && matches!(ip, IpAddr::V4(ip) if ip == Ipv4Addr::new(192, 168, 65, 1) || ip == Ipv4Addr::new(172, 17, 0, 1)) } fn ip_in_list(ip: IpAddr, patterns: &[String]) -> bool { diff --git a/gateway-rs/src/policy/mod.rs b/gateway-rs/src/policy/mod.rs index 3695296d..fb21f28f 100644 --- a/gateway-rs/src/policy/mod.rs +++ b/gateway-rs/src/policy/mod.rs @@ -6,11 +6,14 @@ pub mod auth; pub mod bandwidth; pub mod credits; pub mod evaluator; +pub mod geo; pub mod groups; pub mod ip; pub mod quota; pub mod rate_limit; +pub mod rate_limit_models; pub mod roles; +pub mod simulator; pub mod subscription; pub mod throttle; pub mod tier; @@ -67,6 +70,9 @@ impl PolicyFailure { #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct PolicyDecision { + /// A service-level failure (API disabled, endpoint missing) to report after + /// the tier limits the pinned middleware applies first. + pub deferred_failure: Option, pub route: Option, pub api_id: Option, pub api_name: Option, @@ -76,6 +82,7 @@ pub struct PolicyDecision { pub tier_rate_limit_enabled: bool, pub tier_limit_status: Option, pub upstream: Option, + pub upstream_tls_profile_id: Option, pub upstream_path: Option, pub allowed_headers: Vec, pub throttle_delay_ms: Option, diff --git a/gateway-rs/src/policy/quota.rs b/gateway-rs/src/policy/quota.rs index ed5830df..9e993958 100644 --- a/gateway-rs/src/policy/quota.rs +++ b/gateway-rs/src/policy/quota.rs @@ -16,13 +16,13 @@ pub struct QuotaPolicy { /// Derived usage state used by the quota API and enforcement callers. /// Thresholds deliberately match the Python tracker: warning begins at 80% /// and critical at 95%, including the exhausted boundary. -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] +#[derive(Debug, Clone, PartialEq, serde::Serialize)] pub struct QuotaUsageStatus { pub allowed: bool, pub current_usage: u64, pub limit: u64, pub remaining: u64, - pub percentage_used: u64, + pub percentage_used: f64, pub is_warning: bool, pub is_critical: bool, pub is_exhausted: bool, @@ -31,9 +31,9 @@ pub struct QuotaUsageStatus { pub fn quota_usage_status(current_usage: u64, limit: u64) -> QuotaUsageStatus { let remaining = limit.saturating_sub(current_usage); let percentage_used = if limit == 0 { - 0 + 0.0 } else { - current_usage.saturating_mul(100) / limit + current_usage as f64 / limit as f64 * 100.0 }; let is_exhausted = limit == 0 || current_usage >= limit; QuotaUsageStatus { @@ -42,8 +42,8 @@ pub fn quota_usage_status(current_usage: u64, limit: u64) -> QuotaUsageStatus { limit, remaining, percentage_used, - is_warning: percentage_used >= 80, - is_critical: percentage_used >= 95, + is_warning: percentage_used >= 80.0, + is_critical: percentage_used >= 95.0, is_exhausted, } } @@ -84,11 +84,25 @@ pub fn increment_quota( quota_id: &str, period_seconds: u64, now_seconds: u64, +) -> u64 { + increment_quota_by(counter, user_id, quota_id, 1, period_seconds, now_seconds) +} + +/// Records an arbitrary positive usage amount, matching the Python tracker's +/// `increment_quota(..., amount=...)` contract. +pub fn increment_quota_by( + counter: &WindowCounter, + user_id: &str, + quota_id: &str, + amount: u64, + period_seconds: u64, + now_seconds: u64, ) -> u64 { let period = period_seconds.max(1); let window_index = now_seconds / period; - counter.incr( + counter.incr_by( "a_counter_key(user_id, quota_id, window_index), + amount, period.saturating_mul(2), now_seconds, ) @@ -138,6 +152,9 @@ mod tests { assert!(exhausted.is_exhausted); assert!(!exhausted.allowed); assert_eq!(exhausted.remaining, 0); + + let fractional = quota_usage_status(1, 3); + assert!((fractional.percentage_used - 33.333_333_333_333_33).abs() < f64::EPSILON); } #[test] @@ -146,5 +163,9 @@ mod tests { assert_eq!(increment_quota(&counter, "alice", "requests", 60, 60), 1); assert_eq!(increment_quota(&counter, "alice", "requests", 60, 61), 2); assert_eq!(increment_quota(&counter, "alice", "requests", 60, 120), 1); + assert_eq!( + increment_quota_by(&counter, "alice", "requests", 4, 60, 121), + 5 + ); } } diff --git a/gateway-rs/src/policy/rate_limit.rs b/gateway-rs/src/policy/rate_limit.rs index b350d17c..1436d71e 100644 --- a/gateway-rs/src/policy/rate_limit.rs +++ b/gateway-rs/src/policy/rate_limit.rs @@ -22,6 +22,19 @@ pub fn duration_to_seconds(duration: &str) -> u64 { } } +/// Python truthiness for a numeric limit field: `bool(x)` / `int(x or default)`. +pub fn truthy_count(value: &Value, field: &str) -> Option { + match value.get(field) { + Some(Value::Number(number)) => number + .as_u64() + .or_else(|| number.as_f64().map(|float| float as u64)) + .filter(|count| *count != 0), + Some(Value::String(raw)) if !raw.is_empty() => raw.parse().ok(), + Some(Value::Bool(true)) => Some(1), + _ => None, + } +} + pub fn enforce_rate_limit( username: &str, user: &Value, @@ -30,11 +43,11 @@ pub fn enforce_rate_limit( now_millis: u64, ) -> Result<(), PolicyFailure> { let rate_enabled = bool_field_default(user, "rate_limit_enabled", false) - || user.get("rate_limit_duration").is_some(); + || truthy_count(user, "rate_limit_duration").is_some(); if !rate_enabled { return Ok(()); } - let limit = u64_field(user, "rate_limit_duration").unwrap_or(60); + let limit = truthy_count(user, "rate_limit_duration").unwrap_or(60); let duration = string_field(user, "rate_limit_duration_type").unwrap_or("minute"); let window = duration_to_seconds(duration); let window_millis = window * 1000; @@ -85,21 +98,28 @@ pub fn enforce_rate_limit( "Rate limit exceeded", )) } else { - if algorithm.eq_ignore_ascii_case("hybrid") - && u64_field(user, "rate_limit_burst_allowance").unwrap_or(0) > 0 - && !bucket_counter.take( - &format!("rate_hybrid_bucket:{username}"), - limit.saturating_add(u64_field(user, "rate_limit_burst_allowance").unwrap_or(0)), - window_millis, - now_millis, - ) - { - return Err(PolicyFailure::new( - PolicyStage::RateLimit, - StatusCode::TOO_MANY_REQUESTS, - "Rate limit exceeded", - "Rate limit exceeded", - )); + if algorithm.eq_ignore_ascii_case("hybrid") { + let burst = u64_field(user, "rate_limit_burst_allowance").unwrap_or(0); + if burst > 0 + && !bucket_counter.take( + &format!("rate_hybrid_bucket:{username}"), + limit, + window_millis, + now_millis, + ) + { + let window_start = window_index.saturating_mul(window); + let burst_key = format!("burst:user:{username}:{window_start}"); + if counter.get(&burst_key, now_seconds) >= burst { + return Err(PolicyFailure::new( + PolicyStage::RateLimit, + StatusCode::TOO_MANY_REQUESTS, + "Rate limit exceeded", + "Rate limit exceeded", + )); + } + counter.incr(&burst_key, window * 2, now_seconds); + } } Ok(()) } @@ -126,7 +146,7 @@ mod tests { } #[test] - fn zero_limit_is_a_graceful_rate_limit_denial() { + fn zero_limit_falls_back_to_python_default_instead_of_denying() { let user = json!({ "rate_limit_enabled": true, "rate_limit_duration": 0, @@ -134,7 +154,12 @@ mod tests { }); let counter = WindowCounter::default(); let buckets = TokenBucketCounter::default(); - assert!(enforce_rate_limit("invalid", &user, &counter, &buckets, 60_000).is_err()); + // Python: `int(user.get('rate_limit_duration') or 60)` treats 0 as unset. + assert!(enforce_rate_limit("invalid", &user, &counter, &buckets, 60_000).is_ok()); + let disabled = json!({"rate_limit_duration": 0}); + for _ in 0..100 { + assert!(enforce_rate_limit("off", &disabled, &counter, &buckets, 60_000).is_ok()); + } } #[test] @@ -187,6 +212,25 @@ mod tests { assert!(enforce_rate_limit("dana", &user, &counter, &buckets, 0).is_err()); } + #[test] + fn hybrid_mode_uses_a_separate_windowed_burst_counter_like_python() { + let user = json!({ + "rate_limit_enabled": true, + "rate_limit_duration": 2, + "rate_limit_duration_type": "second", + "rate_limit_algorithm": "hybrid", + "rate_limit_burst_allowance": 1, + }); + let counter = WindowCounter::default(); + let buckets = TokenBucketCounter::default(); + assert!(buckets.take("rate_hybrid_bucket:erin", 2, 1_000, 0)); + assert!(buckets.take("rate_hybrid_bucket:erin", 2, 1_000, 0)); + + assert!(enforce_rate_limit("erin", &user, &counter, &buckets, 0).is_ok()); + assert_eq!(counter.get("burst:user:erin:0", 0), 1); + assert!(enforce_rate_limit("erin", &user, &counter, &buckets, 0).is_err()); + } + #[test] fn concurrent_requests_share_one_atomic_rate_window() { let user = Arc::new(json!({ diff --git a/gateway-rs/src/policy/rate_limit_models.rs b/gateway-rs/src/policy/rate_limit_models.rs new file mode 100644 index 00000000..956ce4d7 --- /dev/null +++ b/gateway-rs/src/policy/rate_limit_models.rs @@ -0,0 +1,521 @@ +use std::{collections::BTreeMap, fmt, str::FromStr}; + +use serde::{Deserialize, Serialize}; +use serde_json::{Value, json}; + +macro_rules! string_enum { + ($name:ident { $($variant:ident => $value:literal),+ $(,)? }) => { + #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] + pub enum $name { + $(#[serde(rename = $value)] $variant),+ + } + + impl $name { + pub const fn as_str(self) -> &'static str { + match self { $(Self::$variant => $value),+ } + } + } + + impl fmt::Display for $name { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(self.as_str()) + } + } + + impl FromStr for $name { + type Err = String; + + fn from_str(value: &str) -> Result { + match value { + $($value => Ok(Self::$variant),)+ + _ => Err(format!("{value:?} is not a valid {}", stringify!($name))), + } + } + } + }; +} + +string_enum!(RuleType { + PerUser => "per_user", + PerApi => "per_api", + PerEndpoint => "per_endpoint", + PerIp => "per_ip", + PerUserApi => "per_user_api", + PerUserEndpoint => "per_user_endpoint", + Global => "global", +}); + +string_enum!(TimeWindow { + Second => "second", + Minute => "minute", + Hour => "hour", + Day => "day", + Month => "month", +}); + +string_enum!(TierName { + Free => "free", + Pro => "pro", + Enterprise => "enterprise", + Custom => "custom", +}); + +string_enum!(QuotaType { + Requests => "requests", + Bandwidth => "bandwidth", + ComputeTime => "compute_time", +}); + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct RateLimitRule { + pub rule_id: String, + pub rule_type: RuleType, + pub time_window: TimeWindow, + pub limit: i64, + #[serde(default)] + pub target_identifier: Option, + #[serde(default)] + pub burst_allowance: i64, + #[serde(default)] + pub priority: i64, + #[serde(default = "default_true")] + pub enabled: bool, + #[serde(default)] + pub created_at: Option, + #[serde(default)] + pub updated_at: Option, + #[serde(default)] + pub created_by: Option, + #[serde(default)] + pub description: Option, +} + +impl RateLimitRule { + pub fn to_dict(&self) -> Value { + serde_json::to_value(self).expect("rate-limit rule is JSON representable") + } + + pub fn from_dict(value: Value) -> Result { + serde_json::from_value(value) + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct TierLimits { + pub requests_per_second: Option, + pub requests_per_minute: Option, + pub requests_per_hour: Option, + pub requests_per_day: Option, + pub requests_per_month: Option, + pub burst_per_second: i64, + pub burst_per_minute: i64, + pub burst_per_hour: i64, + pub monthly_request_quota: Option, + pub daily_request_quota: Option, + pub monthly_bandwidth_quota: Option, + pub enable_throttling: bool, + pub max_queue_time_ms: i64, +} + +impl Default for TierLimits { + fn default() -> Self { + Self { + requests_per_second: None, + requests_per_minute: None, + requests_per_hour: None, + requests_per_day: None, + requests_per_month: None, + burst_per_second: 0, + burst_per_minute: 0, + burst_per_hour: 0, + monthly_request_quota: None, + daily_request_quota: None, + monthly_bandwidth_quota: None, + enable_throttling: false, + max_queue_time_ms: 5_000, + } + } +} + +impl TierLimits { + pub fn to_dict(&self) -> Value { + serde_json::to_value(self).expect("tier limits are JSON representable") + } + + pub fn from_dict(value: Value) -> Result { + serde_json::from_value(value) + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct Tier { + pub tier_id: String, + pub name: TierName, + pub display_name: String, + pub limits: TierLimits, + #[serde(default)] + pub description: Option, + #[serde(default)] + pub price_monthly: Option, + #[serde(default)] + pub price_yearly: Option, + #[serde(default)] + pub features: Vec, + #[serde(default)] + pub is_default: bool, + #[serde(default = "default_true")] + pub enabled: bool, + #[serde(default)] + pub created_at: Option, + #[serde(default)] + pub updated_at: Option, +} + +impl Tier { + pub fn to_dict(&self) -> Value { + serde_json::to_value(self).expect("tier is JSON representable") + } + + pub fn from_dict(value: Value) -> Result { + serde_json::from_value(value) + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct UserTierAssignment { + pub user_id: String, + pub tier_id: String, + #[serde(default)] + pub override_limits: Option, + #[serde(default)] + pub effective_from: Option, + #[serde(default)] + pub effective_until: Option, + #[serde(default)] + pub assigned_at: Option, + #[serde(default)] + pub assigned_by: Option, + #[serde(default)] + pub notes: Option, +} + +impl UserTierAssignment { + pub fn to_dict(&self) -> Value { + serde_json::to_value(self).expect("tier assignment is JSON representable") + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct QuotaUsage { + pub key: String, + pub quota_type: QuotaType, + pub current_usage: i64, + pub limit: i64, + pub reset_at: String, + pub burst_usage: i64, + pub burst_limit: i64, +} + +impl QuotaUsage { + pub fn remaining(&self) -> i64 { + 0.max(self.limit - self.current_usage) + } + + pub fn percentage_used(&self) -> f64 { + if self.limit == 0 { + 0.0 + } else { + self.current_usage as f64 / self.limit as f64 * 100.0 + } + } + + pub fn is_exhausted(&self) -> bool { + self.current_usage >= self.limit + } + + pub fn to_dict(&self) -> Value { + json!({ + "key": self.key, + "quota_type": self.quota_type, + "current_usage": self.current_usage, + "limit": self.limit, + "remaining": self.remaining(), + "percentage_used": self.percentage_used(), + "reset_at": self.reset_at, + "burst_usage": self.burst_usage, + "burst_limit": self.burst_limit, + "is_exhausted": self.is_exhausted(), + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RateLimitCounter { + pub key: String, + pub window_start: i64, + pub window_size: i64, + pub count: i64, + pub limit: i64, + pub burst_count: i64, + pub burst_limit: i64, +} + +impl RateLimitCounter { + pub fn remaining(&self) -> i64 { + 0.max(self.limit - self.count) + } + + pub fn is_limited(&self) -> bool { + self.count >= self.limit + } + + pub fn reset_at(&self) -> i64 { + self.window_start + self.window_size + } + + pub fn to_dict(&self) -> Value { + json!({ + "key": self.key, + "window_start": self.window_start, + "window_size": self.window_size, + "count": self.count, + "limit": self.limit, + "remaining": self.remaining(), + "reset_at": self.reset_at(), + "burst_count": self.burst_count, + "burst_limit": self.burst_limit, + "is_limited": self.is_limited(), + }) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct UsageHistoryRecord { + pub timestamp: String, + pub user_id: Option, + pub api_name: Option, + pub endpoint_uri: Option, + pub ip_address: Option, + pub request_count: i64, + pub blocked_count: i64, + pub burst_used: i64, + pub period: String, +} + +impl UsageHistoryRecord { + pub fn to_dict(&self) -> Value { + json!({ + "timestamp": self.timestamp, + "user_id": self.user_id, + "api_name": self.api_name, + "endpoint_uri": self.endpoint_uri, + "ip_address": self.ip_address, + "request_count": self.request_count, + "blocked_count": self.blocked_count, + "burst_used": self.burst_used, + "period": self.period, + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RateLimitInfo { + pub limit: i64, + pub remaining: i64, + pub reset_at: i64, + pub retry_after: Option, + pub burst_limit: i64, + pub burst_remaining: i64, + pub tier: Option, +} + +impl RateLimitInfo { + pub fn to_headers(&self) -> BTreeMap { + let mut headers = BTreeMap::from([ + ("X-RateLimit-Limit".to_owned(), self.limit.to_string()), + ( + "X-RateLimit-Remaining".to_owned(), + self.remaining.to_string(), + ), + ("X-RateLimit-Reset".to_owned(), self.reset_at.to_string()), + ]); + if let Some(retry_after) = self.retry_after { + headers.insert( + "X-RateLimit-Retry-After".to_owned(), + retry_after.to_string(), + ); + headers.insert("Retry-After".to_owned(), retry_after.to_string()); + } + if self.burst_limit > 0 { + headers.insert( + "X-RateLimit-Burst-Limit".to_owned(), + self.burst_limit.to_string(), + ); + headers.insert( + "X-RateLimit-Burst-Remaining".to_owned(), + self.burst_remaining.to_string(), + ); + } + headers + } + + pub fn to_dict(&self) -> Value { + json!({ + "limit": self.limit, + "remaining": self.remaining, + "reset_at": self.reset_at, + "retry_after": self.retry_after, + "burst_limit": self.burst_limit, + "burst_remaining": self.burst_remaining, + "tier": self.tier, + }) + } +} + +pub const fn get_time_window_seconds(window: TimeWindow) -> i64 { + match window { + TimeWindow::Second => 1, + TimeWindow::Minute => 60, + TimeWindow::Hour => 3_600, + TimeWindow::Day => 86_400, + TimeWindow::Month => 2_592_000, + } +} + +pub fn generate_redis_key( + rule_type: RuleType, + identifier: &str, + window: TimeWindow, + window_start: i64, +) -> String { + let type_prefix = rule_type.as_str().replace("per_", ""); + format!( + "ratelimit:{type_prefix}:{identifier}:{}:{window_start}", + window.as_str() + ) +} + +pub fn generate_quota_key(user_id: &str, quota_type: QuotaType, period: &str) -> String { + format!( + "quota:user:{user_id}:{}:month:{period}", + quota_type.as_str() + ) +} + +const fn default_true() -> bool { + true +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rule_and_tier_round_trips_preserve_python_defaults_and_wire_values() { + let rule = RateLimitRule::from_dict(json!({ + "rule_id": "rule_001", "rule_type": "per_user", "time_window": "minute", + "limit": 100, "created_at": "2025-12-01T10:20:30" + })) + .unwrap(); + assert_eq!(rule.burst_allowance, 0); + assert!(rule.enabled); + assert_eq!(rule.to_dict()["created_at"], "2025-12-01T10:20:30"); + assert!( + RateLimitRule::from_dict( + json!({"rule_id":"bad", "rule_type":"unknown", "time_window":"minute", "limit":1}) + ) + .is_err() + ); + + let tier = Tier::from_dict(json!({ + "tier_id": "tier_free", "name": "free", "display_name": "Free Plan", + "limits": {"requests_per_minute": 60} + })) + .unwrap(); + assert_eq!(tier.limits.max_queue_time_ms, 5_000); + assert!(tier.enabled); + assert_eq!(tier.to_dict()["features"], json!([])); + assert!(TierLimits::from_dict(json!({"unknown": 1})).is_err()); + } + + #[test] + fn computed_usage_counter_and_history_shapes_match_python() { + let usage = QuotaUsage { + key: "quota:user:alice:requests:month:2025-12".to_owned(), + quota_type: QuotaType::Requests, + current_usage: 25, + limit: 100, + reset_at: "2026-01-01T00:00:00".to_owned(), + burst_usage: 2, + burst_limit: 5, + }; + assert_eq!(usage.remaining(), 75); + assert_eq!(usage.percentage_used(), 25.0); + assert!(!usage.is_exhausted()); + assert_eq!(usage.to_dict()["quota_type"], "requests"); + + let counter = RateLimitCounter { + key: "k".to_owned(), + window_start: 100, + window_size: 60, + count: 11, + limit: 10, + burst_count: 1, + burst_limit: 2, + }; + assert_eq!(counter.remaining(), 0); + assert!(counter.is_limited()); + assert_eq!(counter.reset_at(), 160); + assert_eq!(counter.to_dict()["is_limited"], true); + + let history = UsageHistoryRecord { + timestamp: "2025-12-01T00:00:00".to_owned(), + user_id: Some("alice".to_owned()), + api_name: None, + endpoint_uri: None, + ip_address: None, + request_count: 3, + blocked_count: 1, + burst_used: 1, + period: "minute".to_owned(), + }; + assert_eq!(history.to_dict()["period"], "minute"); + } + + #[test] + fn response_headers_windows_and_storage_keys_match_python() { + let info = RateLimitInfo { + limit: 100, + remaining: 4, + reset_at: 1_701_504_000, + retry_after: Some(7), + burst_limit: 20, + burst_remaining: 3, + tier: Some("pro".to_owned()), + }; + let headers = info.to_headers(); + assert_eq!(headers["X-RateLimit-Limit"], "100"); + assert_eq!(headers["Retry-After"], "7"); + assert_eq!(headers["X-RateLimit-Burst-Remaining"], "3"); + assert_eq!(info.to_dict()["tier"], "pro"); + assert_eq!(get_time_window_seconds(TimeWindow::Month), 2_592_000); + assert_eq!( + generate_redis_key( + RuleType::PerUser, + "john_doe", + TimeWindow::Minute, + 1_701_504_000 + ), + "ratelimit:user:john_doe:minute:1701504000" + ); + assert_eq!( + generate_redis_key(RuleType::Global, "all", TimeWindow::Second, 10), + "ratelimit:global:all:second:10" + ); + assert_eq!( + generate_quota_key("john_doe", QuotaType::Requests, "2025-12"), + "quota:user:john_doe:requests:month:2025-12" + ); + } +} diff --git a/gateway-rs/src/policy/roles.rs b/gateway-rs/src/policy/roles.rs index 5bfd4a2d..07ee69e7 100644 --- a/gateway-rs/src/policy/roles.rs +++ b/gateway-rs/src/policy/roles.rs @@ -4,15 +4,26 @@ use serde_json::Value; use super::{PolicyFailure, PolicyStage}; use crate::storage::models::{bool_field_default, string_field, string_list_field}; +/// The pinned `is_admin_role`: a role flagged `platform_admin`, or one named +/// "admin" / "platform admin" (case-insensitive). pub fn is_admin_user(user: &Value, roles: &[Value]) -> bool { let role_name = string_field(user, "role").unwrap_or_default(); - if role_name == "admin" { - return true; + let admin_name = |name: &str| { + matches!( + name.trim().to_lowercase().as_str(), + "admin" | "platform admin" + ) + }; + match roles + .iter() + .find(|role| string_field(role, "role_name") == Some(role_name)) + { + Some(role) => { + bool_field_default(role, "platform_admin", false) + || admin_name(string_field(role, "role_name").unwrap_or_default()) + } + None => admin_name(role_name), } - roles.iter().any(|role| { - string_field(role, "role_name") == Some(role_name) - && bool_field_default(role, "manage_gateway", false) - }) } pub fn enforce_allowed_roles(api: &Value, user: &Value) -> Result<(), PolicyFailure> { diff --git a/gateway-rs/src/policy/simulator.rs b/gateway-rs/src/policy/simulator.rs new file mode 100644 index 00000000..a6afaf38 --- /dev/null +++ b/gateway-rs/src/policy/simulator.rs @@ -0,0 +1,351 @@ +//! Offline rate-limit simulation, translated from the Python utility. + +use std::{ + collections::{BTreeMap, HashMap}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use serde::Serialize; + +#[derive(Clone, Debug, PartialEq)] +pub struct SimulationRequest { + pub timestamp_seconds: f64, + pub user_id: String, + pub endpoint: String, + pub ip: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct SimulationRule { + pub rule_id: String, + pub rule_type: String, + pub time_window: String, + pub limit: u64, + pub burst_allowance: u64, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct SimulationResult { + pub total_requests: u64, + pub allowed_requests: u64, + pub blocked_requests: u64, + pub burst_used_count: u64, + pub success_rate: f64, + pub average_remaining: f64, + pub peak_usage: u64, + pub requests_by_second: BTreeMap, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct SimulationScenario { + pub scenario_name: String, + pub rule: SimulationRule, + pub pattern: String, + pub result: SimulationResult, + pub report: String, +} + +fn now_seconds() -> f64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs_f64() +} + +pub fn window_seconds(window: &str) -> u64 { + match window { + "second" => 1, + "minute" => 60, + "hour" => 3_600, + "day" => 86_400, + "month" => 2_592_000, + _ => 60, + } +} + +pub fn generate_requests( + num_requests: usize, + duration_seconds: f64, + pattern: &str, + start_seconds: f64, +) -> Vec { + if num_requests == 0 { + return Vec::new(); + } + let mut seed = start_seconds.to_bits().wrapping_add(num_requests as u64); + let mut requests = (0..num_requests) + .filter_map(|index| { + let offset = match pattern { + "uniform" => index as f64 * duration_seconds / num_requests as f64, + "burst" => index as f64 * (duration_seconds * 0.1) / num_requests as f64, + "spike" => { + duration_seconds * 0.4 + + index as f64 * (duration_seconds * 0.2) / num_requests as f64 + } + "gradual" => { + let progress = index as f64 / num_requests as f64; + progress * progress * duration_seconds + } + "random" => { + // A local generator keeps this utility dependency-free; Python also + // deliberately leaves random simulations unseeded. + seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1); + seed as f64 / u64::MAX as f64 * duration_seconds + } + _ => return None, + }; + Some(SimulationRequest { + timestamp_seconds: start_seconds + offset, + user_id: "sim_user".to_owned(), + endpoint: "/api/test".to_owned(), + ip: "192.168.1.1".to_owned(), + }) + }) + .collect::>(); + if pattern == "random" { + requests.sort_by(|left, right| left.timestamp_seconds.total_cmp(&right.timestamp_seconds)); + } + requests +} + +pub fn simulate_rule(rule: &SimulationRule, requests: &[SimulationRequest]) -> SimulationResult { + let mut allowed_requests = 0_u64; + let mut blocked_requests = 0_u64; + let mut burst_used_count = 0_u64; + let mut remaining_values = Vec::with_capacity(requests.len()); + let mut requests_by_second = BTreeMap::new(); + + for request in requests { + *requests_by_second + .entry(request.timestamp_seconds as i64) + .or_insert(0) += 1; + } + let window = window_seconds(&rule.time_window) as f64; + for request in requests { + let window_start = request.timestamp_seconds - window; + let current_usage = requests + .iter() + .filter(|candidate| { + candidate.timestamp_seconds >= window_start + && candidate.timestamp_seconds <= request.timestamp_seconds + }) + .count() as u64; + if current_usage <= rule.limit { + allowed_requests += 1; + remaining_values.push(rule.limit - current_usage); + } else if rule.burst_allowance > 0 + && current_usage <= rule.limit.saturating_add(rule.burst_allowance) + { + allowed_requests += 1; + burst_used_count += 1; + remaining_values.push( + rule.limit + .saturating_add(rule.burst_allowance) + .saturating_sub(current_usage), + ); + } else { + blocked_requests += 1; + remaining_values.push(0); + } + } + let total_requests = requests.len() as u64; + let success_rate = if total_requests == 0 { + 0.0 + } else { + allowed_requests as f64 / total_requests as f64 * 100.0 + }; + let average_remaining = if remaining_values.is_empty() { + 0.0 + } else { + remaining_values.iter().sum::() as f64 / remaining_values.len() as f64 + }; + let peak_usage = requests_by_second.values().copied().max().unwrap_or(0); + SimulationResult { + total_requests, + allowed_requests, + blocked_requests, + burst_used_count, + success_rate, + average_remaining, + peak_usage, + requests_by_second, + } +} + +pub fn compare_rules( + rules: &[SimulationRule], + requests: &[SimulationRequest], +) -> HashMap { + rules + .iter() + .map(|rule| (rule.rule_id.clone(), simulate_rule(rule, requests))) + .collect() +} + +pub fn preview_rule_change( + current_rule: &SimulationRule, + new_rule: &SimulationRule, + historical_pattern: &str, + duration_minutes: u64, +) -> HashMap { + let requests = generate_requests( + (current_rule.limit as f64 * 1.5) as usize, + duration_minutes as f64 * 60.0, + historical_pattern, + now_seconds(), + ); + compare_rules(&[current_rule.clone(), new_rule.clone()], &requests) +} + +pub fn test_burst_effectiveness( + base_limit: u64, + burst_allowances: &[u64], + spike_intensity: f64, +) -> BTreeMap { + let requests = generate_requests( + (base_limit as f64 * spike_intensity) as usize, + 60.0, + "spike", + now_seconds(), + ); + burst_allowances + .iter() + .map(|burst| { + let rule = SimulationRule { + rule_id: format!("burst_{burst}"), + rule_type: "per_user".to_owned(), + time_window: "minute".to_owned(), + limit: base_limit, + burst_allowance: *burst, + }; + (*burst, simulate_rule(&rule, &requests)) + }) + .collect() +} + +pub fn generate_report(rule: &SimulationRule, result: &SimulationResult) -> String { + let mut report = format!( + "\nRate Limit Simulation Report\n{line}\n\nRule Configuration:\n Rule ID: {id}\n Type: {kind}\n Time Window: {window}\n Limit: {limit}\n Burst Allowance: {burst}\n\nSimulation Results:\n Total Requests: {total}\n Allowed: {allowed} ({rate:.1}%)\n Blocked: {blocked}\n Burst Used: {burst_used}\n\nPerformance Metrics:\n Success Rate: {rate:.1}%\n Average Remaining: {remaining:.1}\n Peak Usage: {peak} req/sec\n\nRecommendation:\n", + line = "=".repeat(50), + id = rule.rule_id, + kind = rule.rule_type, + window = rule.time_window, + limit = rule.limit, + burst = rule.burst_allowance, + total = result.total_requests, + allowed = result.allowed_requests, + rate = result.success_rate, + blocked = result.blocked_requests, + burst_used = result.burst_used_count, + remaining = result.average_remaining, + peak = result.peak_usage, + ); + if result.success_rate < 90.0 { + report.push_str(" ⚠️ Consider increasing limit or burst allowance\n"); + } else if result.success_rate > 99.0 && result.average_remaining > rule.limit as f64 * 0.5 { + report.push_str(" ℹ️ Limit may be too high, consider reducing\n"); + } else { + report.push_str(" ✅ Rule configuration appears appropriate\n"); + } + if result.burst_used_count > 0 { + let percentage = result.burst_used_count as f64 / result.allowed_requests as f64 * 100.0; + report.push_str(&format!( + " ℹ️ {percentage:.1}% of requests used burst tokens\n" + )); + } + report +} + +pub fn run_scenario( + scenario_name: &str, + rule: &SimulationRule, + pattern: &str, + duration_minutes: u64, +) -> SimulationScenario { + let requests = generate_requests( + rule.limit.saturating_mul(2) as usize, + duration_minutes as f64 * 60.0, + pattern, + now_seconds(), + ); + let result = simulate_rule(rule, &requests); + let report = generate_report(rule, &result); + SimulationScenario { + scenario_name: scenario_name.to_owned(), + rule: rule.clone(), + pattern: pattern.to_owned(), + result, + report, + } +} + +pub fn quick_simulate(limit: u64, time_window: &str, burst: u64, pattern: &str) -> String { + let rule = SimulationRule { + rule_id: "quick_sim".to_owned(), + rule_type: "per_user".to_owned(), + time_window: time_window.to_owned(), + limit, + burst_allowance: burst, + }; + let requests = generate_requests( + limit.saturating_mul(2) as usize, + 60.0, + pattern, + now_seconds(), + ); + generate_report(&rule, &simulate_rule(&rule, &requests)) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn rule(limit: u64, burst: u64) -> SimulationRule { + SimulationRule { + rule_id: "example".to_owned(), + rule_type: "per_user".to_owned(), + time_window: "minute".to_owned(), + limit, + burst_allowance: burst, + } + } + + #[test] + fn generates_python_traffic_patterns() { + let uniform = generate_requests(4, 100.0, "uniform", 1_000.0); + assert_eq!(uniform[3].timestamp_seconds, 1_075.0); + let spike = generate_requests(4, 100.0, "spike", 1_000.0); + assert_eq!(spike[0].timestamp_seconds, 1_040.0); + assert_eq!(spike[3].timestamp_seconds, 1_055.0); + assert!(generate_requests(3, 10.0, "unknown", 0.0).is_empty()); + } + + #[test] + fn simulates_limit_and_burst_like_python() { + let requests = generate_requests(5, 1.0, "burst", 1_000.0); + let result = simulate_rule(&rule(2, 1), &requests); + assert_eq!(result.allowed_requests, 3); + assert_eq!(result.blocked_requests, 2); + assert_eq!(result.burst_used_count, 1); + assert_eq!(result.peak_usage, 5); + assert_eq!(result.success_rate, 60.0); + assert!(generate_report(&rule(2, 1), &result).contains("Consider increasing limit")); + } + + #[test] + fn comparison_and_scenario_helpers_preserve_python_shapes() { + let current = rule(4, 0); + let mut proposed = rule(8, 2); + proposed.rule_id = "proposed".to_owned(); + let comparison = preview_rule_change(¤t, &proposed, "uniform", 1); + assert_eq!(comparison.len(), 2); + assert!(comparison.contains_key("example")); + assert!(comparison.contains_key("proposed")); + let bursts = test_burst_effectiveness(4, &[0, 2], 2.0); + assert_eq!(bursts.len(), 2); + let scenario = run_scenario("load", ¤t, "uniform", 5); + assert_eq!(scenario.scenario_name, "load"); + assert_eq!(scenario.result.total_requests, 8); + assert!(quick_simulate(4, "minute", 0, "uniform").contains("quick_sim")); + } +} diff --git a/gateway-rs/src/policy/subscription.rs b/gateway-rs/src/policy/subscription.rs index 911034ca..4590e132 100644 --- a/gateway-rs/src/policy/subscription.rs +++ b/gateway-rs/src/policy/subscription.rs @@ -55,7 +55,7 @@ mod tests { use serde_json::json; #[test] - fn admin_requires_subscription_by_default() { + fn admin_bypasses_subscription_unless_enforced() { let user = json!({ "username": "admin", "role": "admin" }); assert!(enforce_subscription("demo/v1", &user, &[], &[], true).is_err()); assert!(enforce_subscription("demo/v1", &user, &[], &[], false).is_ok()); diff --git a/gateway-rs/src/policy/throttle.rs b/gateway-rs/src/policy/throttle.rs index df0183b1..5a218838 100644 --- a/gateway-rs/src/policy/throttle.rs +++ b/gateway-rs/src/policy/throttle.rs @@ -1,10 +1,13 @@ use http::StatusCode; use serde_json::Value; -use super::{PolicyFailure, PolicyStage, rate_limit::duration_to_seconds}; +use super::{ + PolicyFailure, PolicyStage, + rate_limit::{duration_to_seconds, truthy_count}, +}; use crate::storage::{ cache::WindowCounter, - models::{bool_field_default, f64_field, string_field, u64_field}, + models::{bool_field_default, f64_field, string_field}, redis::throttle_key, }; @@ -20,13 +23,13 @@ pub fn enforce_throttle( now_millis: u64, ) -> Result { let enabled = bool_field_default(user, "throttle_enabled", false) - || user.get("throttle_duration").is_some() - || user.get("throttle_queue_limit").is_some(); + || truthy_count(user, "throttle_duration").is_some() + || truthy_count(user, "throttle_queue_limit").is_some(); if !enabled { return Ok(ThrottleOutcome::default()); } - let throttle_limit = u64_field(user, "throttle_duration").unwrap_or(10); + let throttle_limit = truthy_count(user, "throttle_duration").unwrap_or(10); let duration = string_field(user, "throttle_duration_type").unwrap_or("second"); let window = duration_to_seconds(duration); let window_ms = window.max(1) * 1000; @@ -36,7 +39,7 @@ pub fn enforce_throttle( window, now_millis / 1000, ); - let queue_limit = u64_field(user, "throttle_queue_limit").unwrap_or(10); + let queue_limit = truthy_count(user, "throttle_queue_limit").unwrap_or(10); if queue_limit > 0 && count > queue_limit { return Err(queue_limit_exceeded()); } @@ -74,6 +77,28 @@ mod tests { use super::*; use serde_json::json; + #[test] + fn falsy_throttle_fields_are_unset_like_python() { + let counter = WindowCounter::default(); + let user = json!({"throttle_duration": 0, "throttle_queue_limit": 0}); + for now in 0..30 { + assert_eq!( + enforce_throttle("zed", &user, &counter, now) + .unwrap() + .delay_ms, + None + ); + } + // Enabled with a zero queue limit: `int(0 or 10)` gives a queue limit of 10. + let user = + json!({"throttle_enabled": true, "throttle_duration": 1, "throttle_queue_limit": 0}); + let results = (0..12) + .map(|_| enforce_throttle("yan", &user, &counter, 0).is_ok()) + .collect::>(); + assert!(results[..10].iter().all(|ok| *ok)); + assert!(!results[10]); + } + #[test] fn returns_delay_before_queue_limit() { let user = json!({ diff --git a/gateway-rs/src/protocol/crud.rs b/gateway-rs/src/protocol/crud.rs index 56b0dd7d..417e8280 100644 --- a/gateway-rs/src/protocol/crud.rs +++ b/gateway-rs/src/protocol/crud.rs @@ -50,6 +50,7 @@ pub async fn execute( } let query = request.uri().query().unwrap_or_default().to_owned(); let request_path = request.uri().path().to_owned(); + let is_get = request.method() == http::Method::GET; let grpc_web_target = request .extensions() .get::() @@ -79,8 +80,8 @@ pub async fn execute( }; let response = match protocol { DataPlaneProtocol::Graphql => execute_graphql(state, decision, &body).await, - DataPlaneProtocol::Soap => execute_soap(state, decision, &query, &body).await, - DataPlaneProtocol::Grpc => execute_grpc(state, decision, &query, &body).await, + DataPlaneProtocol::Soap => execute_soap(state, decision, is_get, &query, &body).await, + DataPlaneProtocol::Grpc => execute_grpc(state, decision, is_get, &query, &body).await, DataPlaneProtocol::GrpcWeb => { execute_grpc_web( state, @@ -99,6 +100,12 @@ pub async fn execute( }; Ok(match response { Ok(response) => response, + // Python's CRUD SOAP handler turns every failure into a 500 fault that + // `process_soap_response` renders as the generic unknown-error message. + Err(error) if protocol == DataPlaneProtocol::Soap => { + tracing::debug!(error = %error, "SOAP CRUD failed"); + soap_python_fault() + } Err(StorageError::InvalidDocument(error)) => { if protocol == DataPlaneProtocol::GrpcWeb { crate::protocol::grpc::web_trailer_response( @@ -320,14 +327,16 @@ async fn execute_graphql( async fn execute_soap( state: &AppState, decision: &PolicyDecision, + is_get: bool, query: &str, body: &[u8], ) -> Result { - if query - .split('&') - .any(|part| part.eq_ignore_ascii_case("wsdl")) + if is_get + && query + .split('&') + .any(|part| part.split('=').next().is_some_and(|key| key == "wsdl")) { - return Ok(xml_response(StatusCode::OK, soap_wsdl(decision))); + return Ok(soap_xml_response(soap_wsdl(decision))); } let xml = std::str::from_utf8(body) .map_err(|error| StorageError::InvalidDocument(error.to_string()))?; @@ -346,70 +355,68 @@ async fn execute_soap( "Invalid SOAP envelope".to_owned(), )); } - let operation = Regex::new( - r"(?s)<(?:[A-Za-z_][A-Za-z0-9_.-]*:)?(createItem|listItems|getItem|updateItem|deleteItem)\b", - ) - .expect("static SOAP operation regex") - .captures(xml) - .and_then(|captures| captures.get(1)) - .map(|value| value.as_str()) - .ok_or_else(|| StorageError::InvalidDocument("Unknown SOAP CRUD operation".to_owned()))?; + // Python's CRUD SOAP handler implements only createItem and listItems; + // getItem is "not supported yet" and everything else is unknown. + static OPERATION: std::sync::LazyLock = std::sync::LazyLock::new(|| { + Regex::new(r"(?s)<(?:[A-Za-z_][A-Za-z0-9_.-]*:)?(createItem|listItems)\b") + .expect("static SOAP operation regex") + }); + let operation = OPERATION + .captures(xml) + .and_then(|captures| captures.get(1)) + .map(|value| value.as_str()) + .ok_or_else(|| StorageError::InvalidDocument("Unknown SOAP CRUD operation".to_owned()))?; let (storage, collection) = storage_collection(state, decision)?; - let result = match operation { - "listItems" => Value::Array(storage.crud_list(collection).await?), - "getItem" => { - let id = xml_element(xml, "id")?; - storage - .crud_find_one(collection, &id) - .await? - .unwrap_or(Value::Null) - } - "createItem" => { + let (response_tag, result_xml) = match operation { + "listItems" => ( + "listItemsResponse", + format!( + "{}", + python_json_dumps(&Value::Array(storage.crud_list(collection).await?)) + ), + ), + _ => { let input = xml_element(xml, "input")?; + if input.is_empty() { + return Err(StorageError::InvalidDocument( + "Missing input element or empty".to_owned(), + )); + } let mut input: Value = serde_json::from_str(&xml_unescape(&input))?; + if !input.is_object() { + return Err(StorageError::InvalidDocument( + "SOAP CRUD input must be a JSON object".to_owned(), + )); + } validate_crud_schema(decision.crud_schema.as_ref(), &input, false)?; if input.get("_id").is_none() { input["_id"] = Value::String(uuid::Uuid::new_v4().to_string()); } storage.crud_insert(collection, &input).await?; - input - } - "updateItem" => { - let id = xml_element(xml, "id")?; - let input = xml_element(xml, "input")?; - let input: Value = serde_json::from_str(&xml_unescape(&input))?; - validate_crud_schema(decision.crud_schema.as_ref(), &input, true)?; - storage - .crud_update(collection, &id, &input) - .await? - .unwrap_or(Value::Null) - } - "deleteItem" => { - let id = xml_element(xml, "id")?; - Value::Bool(storage.crud_delete(collection, &id).await?) + ( + "createItemResponse", + format!("{}", python_json_dumps(&input)), + ) } - _ => unreachable!(), }; - let response_tag = format!("{operation}Response"); - let json = serde_json::to_string(&result)?; - Ok(xml_response( - StatusCode::OK, - format!( - "{}", - xml_escape(&json) - ), - )) + let api_name = decision.api_name.as_deref().unwrap_or_default(); + let tns = format!("http://doorman.dev/{}", xml_escape(api_name)); + Ok(soap_xml_response(format!( + "\n\n \n \n {result_xml}\n \n \n" + ))) } async fn execute_grpc( state: &AppState, decision: &PolicyDecision, + is_get: bool, query: &str, body: &[u8], ) -> Result { - if query - .split('&') - .any(|part| part.eq_ignore_ascii_case("proto")) + if is_get + && query + .split('&') + .any(|part| part.split('=').next().is_some_and(|key| key == "proto")) { return Ok(( StatusCode::OK, @@ -497,9 +504,26 @@ fn storage_collection<'a>( Ok((storage, collection)) } +/// Patterns here are built from a small fixed set of operation and element +/// names; compile each once instead of per request. +fn cached_regex(pattern: String) -> Option { + static CACHE: std::sync::OnceLock>> = + std::sync::OnceLock::new(); + let mut cache = CACHE.get_or_init(Default::default).lock().ok()?; + if let Some(regex) = cache.get(&pattern) { + return Some(regex.clone()); + } + let regex = Regex::new(&pattern).ok()?; + if cache.len() >= 256 { + cache.clear(); + } + cache.insert(pattern, regex.clone()); + Some(regex) +} + fn has_operation(query: &str, operation: &str) -> bool { - Regex::new(&format!(r"\b{}\b", regex::escape(operation))) - .is_ok_and(|regex| regex.is_match(query)) + cached_regex(format!(r"\b{}\b", regex::escape(operation))) + .is_some_and(|regex| regex.is_match(query)) } fn variable_string<'a>( @@ -526,7 +550,7 @@ fn variable_object( } fn xml_element(xml: &str, name: &str) -> Result { - Regex::new(&format!( + cached_regex(format!( r"(?s)<(?:[A-Za-z_][A-Za-z0-9_.-]*:)?{0}\b[^>]*>(.*?)", regex::escape(name) )) @@ -565,17 +589,171 @@ fn xml_response(status: StatusCode, body: String) -> Response { } fn soap_wsdl(decision: &PolicyDecision) -> String { - let name = xml_escape(decision.api_name.as_deref().unwrap_or("Crud")); + let name = xml_escape(decision.api_name.as_deref().unwrap_or_default()); + let tns = format!("http://doorman.dev/{name}"); format!( - "" + r#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "# ) } +/// `text/xml` response carrying the raw body, as Python's SOAP CRUD returns it. +fn soap_xml_response(body: String) -> Response { + (StatusCode::OK, [(header::CONTENT_TYPE, "text/xml")], body).into_response() +} + +/// What `process_soap_response` renders for the CRUD handler's 500 fault. +fn soap_python_fault() -> Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + [(header::CONTENT_TYPE, "text/xml")], + "An unknown error occurred in SOAP response", + ) + .into_response() +} + +/// Python `json.dumps` defaults: `", "` / `": "` separators and ASCII escaping. +fn python_json_dumps(value: &Value) -> String { + fn dump(value: &Value, out: &mut String) { + match value { + Value::Null => out.push_str("null"), + Value::Bool(flag) => out.push_str(if *flag { "true" } else { "false" }), + Value::Number(number) => out.push_str(&number.to_string()), + Value::String(text) => dump_string(text, out), + Value::Array(items) => { + out.push('['); + for (index, item) in items.iter().enumerate() { + if index > 0 { + out.push_str(", "); + } + dump(item, out); + } + out.push(']'); + } + Value::Object(map) => { + out.push('{'); + for (index, (key, item)) in map.iter().enumerate() { + if index > 0 { + out.push_str(", "); + } + dump_string(key, out); + out.push_str(": "); + dump(item, out); + } + out.push('}'); + } + } + } + fn dump_string(text: &str, out: &mut String) { + out.push('"'); + for character in text.chars() { + match character { + '"' => out.push_str("\\\""), + '\\' => out.push_str("\\\\"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\u{8}' => out.push_str("\\b"), + '\u{c}' => out.push_str("\\f"), + character if (' '..='~').contains(&character) => out.push(character), + character => { + let mut units = [0u16; 2]; + for unit in character.encode_utf16(&mut units) { + out.push_str(&format!("\\u{unit:04x}")); + } + } + } + } + out.push('"'); + } + let mut out = String::new(); + dump(value, &mut out); + out +} + fn crud_proto(decision: &PolicyDecision) -> String { - let mut package = decision - .api_name - .as_deref() - .unwrap_or("crud") + let raw = decision.api_name.as_deref().unwrap_or_default(); + let mut package = raw .chars() .map(|character| { if character.is_ascii_alphanumeric() || character == '_' { @@ -585,13 +763,42 @@ fn crud_proto(decision: &PolicyDecision) -> String { } }) .collect::(); - if package.is_empty() { - package.push_str("crud"); - } else if package.starts_with(|character: char| character.is_ascii_digit()) { + let mut service = package.chars(); + let service = service + .next() + .map(|first| first.to_uppercase().collect::() + &service.as_str().to_lowercase()) + .unwrap_or_default(); + if package.starts_with(|character: char| character.is_ascii_digit()) { package.insert(0, '_'); } + let type_map = |declared: &str| match declared { + "number" => "double", + "integer" => "int32", + "boolean" => "bool", + "array" => "repeated string", + _ => "string", + }; + let fields = decision + .crud_schema + .as_ref() + .and_then(Value::as_object) + .map(|schema| { + schema + .iter() + .enumerate() + .map(|(index, (field, rules))| { + let declared = rules + .get("type") + .and_then(Value::as_str) + .unwrap_or("string"); + format!(" {} {field} = {};", type_map(declared), index + 1) + }) + .collect::>() + .join("\n") + }) + .unwrap_or_default(); format!( - "syntax = \"proto3\";\npackage {package};\nservice CrudService {{ rpc CreateItem (CrudRequest) returns (CrudReply); rpc ListItems (CrudRequest) returns (CrudReply); rpc GetItem (CrudRequest) returns (CrudReply); rpc UpdateItem (CrudRequest) returns (CrudReply); rpc DeleteItem (CrudRequest) returns (CrudReply); }}\nmessage CrudRequest {{ string id = 1; string input = 2; }}\nmessage CrudReply {{ string result = 1; bool ok = 2; }}\n" + "syntax = \"proto3\";\n\npackage {package};\n\nservice {service}Service {{\n rpc CreateItem (CreateItemRequest) returns (CreateItemResponse);\n rpc ListItems (ListItemsRequest) returns (ListItemsResponse);\n}}\n\nmessage CreateItemRequest {{\n{fields}\n}}\n\nmessage CreateItemResponse {{\n string result = 1; // JSON string of created object\n}}\n\nmessage ListItemsRequest {{}}\n\nmessage ListItemsResponse {{\n string items = 1; // JSON string of list\n}}\n" ) } @@ -633,32 +840,40 @@ mod tests { use super::*; #[test] - fn discovers_all_crud_operations() { + fn discovery_documents_match_python_templates() { let decision = PolicyDecision { api_name: Some("customer-api".to_owned()), + crud_schema: Some(serde_json::json!({ + "name": {"type": "string"}, "age": {"type": "integer"}, + "score": {"type": "number"}, "ok": {"type": "boolean"}, + "tags": {"type": "array"}, "meta": {"type": "object"} + })), ..Default::default() }; let wsdl = soap_wsdl(&decision); - let proto = crud_proto(&decision); - for operation in [ - "createItem", - "listItems", - "getItem", - "updateItem", - "deleteItem", - ] { - assert!(wsdl.contains(operation)); - } - for operation in [ - "CreateItem", - "ListItems", - "GetItem", - "UpdateItem", - "DeleteItem", - ] { - assert!(proto.contains(operation)); - } - assert!(proto.contains("package customer_api;")); + assert!( + wsdl.starts_with("\n\n \n " + )); + assert!(wsdl.contains("location=\"http://localhost:8080/api/soap/customer-api\"")); + assert!(wsdl.ends_with("\n ")); + assert!(!wsdl.contains("getItem")); + assert_eq!( + crud_proto(&decision), + include_str!("../../tests/fixtures/crud_customer_api.proto") + ); + } + + #[test] + fn python_json_dumps_matches_json_dumps_defaults() { + let value = + serde_json::json!({"a": [1, 2.5, null, true], "n": "caf\u{e9} \u{1f600}\n\"q\""}); + assert_eq!( + python_json_dumps(&value), + r#"{"a": [1, 2.5, null, true], "n": "caf\u00e9 \ud83d\ude00\n\"q\""}"# + ); } #[test] diff --git a/gateway-rs/src/protocol/grpc.rs b/gateway-rs/src/protocol/grpc.rs index 9e937ec4..1d1dce46 100644 --- a/gateway-rs/src/protocol/grpc.rs +++ b/gateway-rs/src/protocol/grpc.rs @@ -19,7 +19,7 @@ use tonic::{ Request, Status, codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder}, metadata::{Ascii, MetadataKey, MetadataValue}, - transport::{Channel, Endpoint}, + transport::Channel, }; use crate::{ @@ -77,7 +77,7 @@ pub async fn execute_json_gateway( } pub async fn execute_web_gateway( - _state: &AppState, + state: &AppState, decision: &PolicyDecision, headers: &HeaderMap, body: &[u8], @@ -100,6 +100,13 @@ pub async fn execute_web_gateway( "gRPC-Web disabled", ); } + // The pinned gRPC-Web proxy accepts an empty body and emits an empty + // successful data frame instead of treating it as malformed framing. + if body.is_empty() { + let mut framed = web_data_frame(&[]); + framed.extend(web_trailer_frame(tonic::Code::Ok, "OK")); + return web_body_response(text_mode, framed); + } let descriptor = match decision.grpc_descriptor_set.as_deref() { Some(descriptor) => descriptor, None => { @@ -195,24 +202,21 @@ pub async fn execute_web_gateway( return web_trailer_response(text_mode, tonic::Code::Unavailable, "Invalid upstream"); } }; - let channel = match Endpoint::from_shared(endpoint) { - Ok(endpoint) => match endpoint - .connect_timeout(Duration::from_millis(decision.request_timeout_ms.max(1))) - .timeout(Duration::from_millis(decision.request_timeout_ms.max(1))) - .connect() - .await - { - Ok(channel) => channel, - Err(_) => { - return web_trailer_response( - text_mode, - tonic::Code::Unavailable, - "Upstream unavailable", - ); - } - }, + let channel = match state + .grpc_channel( + &endpoint, + decision.upstream_tls_profile_id.as_deref(), + decision.request_timeout_ms, + ) + .await + { + Ok(channel) => channel, Err(_) => { - return web_trailer_response(text_mode, tonic::Code::Unavailable, "Invalid upstream"); + return web_trailer_response( + text_mode, + tonic::Code::Unavailable, + "Upstream unavailable", + ); } }; let path = match PathAndQuery::try_from(format!( @@ -369,7 +373,7 @@ pub(crate) fn web_body_response(text_mode: bool, body: Vec) -> Response { } async fn execute( - _state: &AppState, + state: &AppState, decision: &PolicyDecision, headers: &HeaderMap, body: &[u8], @@ -425,16 +429,20 @@ async fn execute( "No upstream servers configured".to_owned(), )); }; - let endpoint = Endpoint::from_shared(normalize_endpoint(upstream)?) - .map_err(|error| GrpcGatewayError::Transport(error.to_string()))? - .connect_timeout(Duration::from_millis(decision.request_timeout_ms.max(1))) - .timeout(Duration::from_millis(decision.request_timeout_ms.max(1))); + let endpoint = normalize_endpoint(upstream)?; let attempts = decision .retry_count .max(env_u32("GRPC_MAX_RETRIES", 0)) .saturating_add(1); for attempt in 0..attempts { - let result = match endpoint.clone().connect().await { + let result = match state + .grpc_channel( + &endpoint, + decision.upstream_tls_profile_id.as_deref(), + decision.request_timeout_ms, + ) + .await + { Ok(channel) => { invoke( channel, @@ -461,7 +469,14 @@ async fn execute( Err(primary_error) => { // The Python gateway makes one final compatibility attempt without the // protobuf package. Some legacy gRPC services register that path. - let fallback = match endpoint.clone().connect().await { + let fallback = match state + .grpc_channel( + &endpoint, + decision.upstream_tls_profile_id.as_deref(), + decision.request_timeout_ms, + ) + .await + { Ok(channel) => { invoke( channel, diff --git a/gateway-rs/src/routes/discovery.rs b/gateway-rs/src/routes/discovery.rs index e3fd7910..c843e81b 100644 --- a/gateway-rs/src/routes/discovery.rs +++ b/gateway-rs/src/routes/discovery.rs @@ -95,6 +95,42 @@ pub fn openapi_endpoints(spec: &Value) -> Vec { endpoints } +/// The pinned `validate_wsdl_content`: well-formed XML whose root is a WSDL +/// `definitions` element containing at least one service or portType. +pub fn validate_wsdl(content: &str) -> Result<(), String> { + if content.trim().is_empty() { + return Err("Empty WSDL content".to_owned()); + } + let mut reader = Reader::from_str(content); + let mut root: Option> = None; + let mut has_service = false; + loop { + match reader.read_event() { + Ok(Event::Start(event)) | Ok(Event::Empty(event)) => { + if root.is_none() { + root = Some(event.name().as_ref().to_ascii_lowercase()); + } + let local = event.local_name(); + has_service |= matches!(local.as_ref(), b"service" | b"portType"); + } + Ok(Event::DocType(_)) => return Err("DTD declarations are not allowed".to_owned()), + Ok(Event::Eof) => break, + Err(error) => return Err(format!("Invalid XML: {error}")), + _ => {} + } + } + let Some(root) = root else { + return Err("Invalid XML: no element found".to_owned()); + }; + if !String::from_utf8_lossy(&root).contains("definitions") { + return Err("Root element must be wsdl:definitions".to_owned()); + } + if !has_service { + return Err("WSDL must contain at least one service or portType".to_owned()); + } + Ok(()) +} + pub fn parse_wsdl(content: &str) -> Result { let lowered = content.to_ascii_lowercase(); if lowered.contains(", - mut request: Request, + request: Request, ) -> Result { - let proto_discovery = request.method() == http::Method::GET - && request.uri().query().is_some_and(|query| { - query - .split('&') - .any(|part| part.eq_ignore_ascii_case("proto")) - }); + // Python's gRPC gateway route accepts both POST and GET (the latter used + // for proto discovery as well as plain GET calls), plus OPTIONS for CORS. if !matches!( request.method(), - &http::Method::POST | &http::Method::OPTIONS - ) && !proto_discovery - { + &http::Method::POST | &http::Method::GET | &http::Method::OPTIONS + ) { return Ok(http::StatusCode::METHOD_NOT_ALLOWED.into_response()); } + if let Some(response) = crate::routes::rest::early_body_limit( + &state, + request.uri().path(), + request.headers(), + request.method() == http::Method::OPTIONS, + crate::routes::rest::DataPlaneProtocol::Grpc, + ) + .await + { + return Ok(response); + } let path = request.uri().path(); let subpath = path .strip_prefix("/api/grpc/") .or_else(|| path.strip_prefix("/grpc/")) .unwrap_or(path) .trim_matches('/'); - let api_name = subpath.split('/').next().unwrap_or_default().to_owned(); + // Python resolves the API from the last path segment (`path_parts[-1]`). + let api_name = subpath.rsplit('/').next().unwrap_or_default().to_owned(); + // Unlike GraphQL, the pinned gRPC route defaults a missing X-API-Version + // to v1 (`request.headers.get('X-API-Version', 'v1')`). let version = request .headers() .get("x-api-version") .and_then(|value| value.to_str().ok()) - .map(str::to_owned) - .or_else(|| (request.method() == http::Method::OPTIONS).then(|| "v1".to_owned())); - let Some(version) = version else { - return Ok(( - StatusCode::BAD_REQUEST, - Json(PolicyErrorBody { - error_code: "X-API-Version header is required".to_owned(), - error_message: "X-API-Version header is required".to_owned(), - }), - ) - .into_response()); - }; + .filter(|value| !value.is_empty()) + .unwrap_or("v1") + .to_owned(); tracing::info!(path = %path, subpath = %subpath, api_name = %api_name, version = %version, "grpc_policy_then_execute"); + let get = request.method() == http::Method::GET; + let (parts, body) = request.into_parts(); + let limit = BodyLimits::for_path(parts.uri.path(), BodyLimits::from_env().default); + let Ok(bytes) = to_bytes(body, limit).await else { + return Ok(json_error( + StatusCode::PAYLOAD_TOO_LARGE, + "REQ001", + "Request entity too large", + )); + }; + let mut request = Request::from_parts(parts, Body::from(bytes.clone())); request .extensions_mut() .insert(PolicyPath(grpc_policy_path(&api_name, &version))); request.extensions_mut().insert(DataPlaneProtocol::Grpc); - rest_policy_then_proxy(State(state), request).await + let response = rest_policy_then_proxy(State(state), request).await?; + if response.status() != StatusCode::NOT_FOUND { + return Ok(response); + } + let (parts, body) = response.into_parts(); + let body = to_bytes(body, 64 * 1024).await.unwrap_or_default(); + let missing_api = serde_json::from_slice::(&body) + .is_ok_and(|value| value.get("error_code").and_then(Value::as_str) == Some("GTW001")); + if !missing_api { + return Ok(Response::from_parts(parts, Body::from(body))); + } + Ok(missing_api_response(get, &bytes, &api_name, &version)) +} + +/// For an unknown API the pinned gateway skips the auth pipeline and reaches +/// GatewayService.grpc_gateway, which validates the body and method before +/// failing to locate the generated proto module. +fn missing_api_response(get: bool, body: &[u8], api_name: &str, version: &str) -> Response { + let parsed = if get { + Ok(Value::Object(Default::default())) + } else { + serde_json::from_slice::(body) + }; + let Ok(value) = parsed else { + return json_error( + StatusCode::BAD_REQUEST, + "GTW011", + "Invalid JSON in request body", + ); + }; + let Some(object) = value.as_object() else { + return json_error( + StatusCode::BAD_REQUEST, + "GTW011", + "Invalid request body format", + ); + }; + let method = object + .get("method") + .and_then(Value::as_str) + .unwrap_or_default() + .trim(); + let valid_method = method + .split_once('.') + .is_some_and(|(service, name)| is_identifier(service.trim()) && is_identifier(name.trim())); + if !valid_method { + return json_error( + StatusCode::BAD_REQUEST, + "GTW011", + "Invalid gRPC method. Use Service.Method with alphanumerics/underscore.", + ); + } + let package = object + .get("package") + .and_then(Value::as_str) + .unwrap_or_default() + .trim(); + if !package.is_empty() + && (package.contains('/') + || package.contains('\\') + || package.contains("..") + || !package.split('.').all(is_identifier)) + { + return json_error( + StatusCode::BAD_REQUEST, + "GTW011", + "Invalid gRPC package. Use letters, digits, underscore only.", + ); + } + json_error( + StatusCode::NOT_FOUND, + "GTW012", + &format!("Proto file not found for API: {api_name}/{version}"), + ) +} + +/// Python `_is_valid_identifier`: ASCII letter/underscore start, then +/// letters, digits, or underscores, at most 128 characters. +fn is_identifier(name: &str) -> bool { + let mut chars = name.chars(); + name.len() <= 128 + && chars + .next() + .is_some_and(|first| first.is_ascii_alphabetic() || first == '_') + && chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_') +} + +fn json_error(status: StatusCode, code: &str, message: &str) -> Response { + ( + status, + Json(json!({"error_code": code, "error_message": message})), + ) + .into_response() } fn grpc_policy_path(api_name: &str, version: &str) -> String { @@ -67,30 +172,23 @@ fn grpc_policy_path(api_name: &str, version: &str) -> String { #[cfg(test)] mod tests { use super::*; - use axum::body::{Body, to_bytes}; - use serde_json::Value; #[test] fn normalizes_grpc_subscription_lookup_path() { assert_eq!(grpc_policy_path("svc4", "v4"), "/api/rest/svc4/v4/grpc"); } - #[tokio::test] - async fn requires_version_header_for_json_grpc_requests_like_python() { - let state = - AppState::new(crate::Config::for_test("http://127.0.0.1:9".to_owned())).unwrap(); - let request = Request::builder() - .method(http::Method::POST) - .uri("/api/grpc/service/do") - .header(http::header::CONTENT_TYPE, "application/json") - .body(Body::from(r#"{"data":"{}"}"#)) - .unwrap(); - let response = grpc_policy_then_execute(State(state), request) - .await - .unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - let body = to_bytes(response.into_body(), 1024).await.unwrap(); - let body: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!(body["error_code"], "X-API-Version header is required"); + #[test] + fn missing_api_reports_python_proto_error_after_method_validation() { + let response = missing_api_response(false, br#"{"method":"Resource.Create"}"#, "do", "v1"); + assert_eq!(response.status(), StatusCode::NOT_FOUND); + assert_eq!( + missing_api_response(false, b"{bad", "do", "v1").status(), + StatusCode::BAD_REQUEST + ); + assert_eq!( + missing_api_response(true, b"", "do", "v1").status(), + StatusCode::BAD_REQUEST + ); } } diff --git a/gateway-rs/src/routes/grpc_web.rs b/gateway-rs/src/routes/grpc_web.rs index 6ae2d53c..f808cdbe 100644 --- a/gateway-rs/src/routes/grpc_web.rs +++ b/gateway-rs/src/routes/grpc_web.rs @@ -26,6 +26,31 @@ pub async fn grpc_web_policy_then_execute( ) { return Ok(http::StatusCode::METHOD_NOT_ALLOWED.into_response()); } + // The pinned proxy rejects a non-gRPC-Web body before resolving the API. + if request.method() == http::Method::POST + && !request + .headers() + .get(http::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .is_some_and(|value| value.starts_with("application/grpc-web")) + { + // Starlette's bare Response sets no media type. + return Ok(Response::builder() + .status(http::StatusCode::UNSUPPORTED_MEDIA_TYPE) + .body(axum::body::Body::from("Invalid Content-Type")) + .expect("static 415 response")); + } + if let Some(response) = crate::routes::rest::early_body_limit( + &state, + request.uri().path(), + request.headers(), + request.method() == http::Method::OPTIONS, + DataPlaneProtocol::GrpcWeb, + ) + .await + { + return Ok(response); + } let version = request .headers() .get("x-api-version") @@ -39,7 +64,31 @@ pub async fn grpc_web_policy_then_execute( request .extensions_mut() .insert(GrpcWebTarget { service, method }); - rest_policy_then_proxy(State(state), request).await + let response = rest_policy_then_proxy(State(state), request).await?; + if response.status() != http::StatusCode::NOT_FOUND { + return Ok(response); + } + let (parts, body) = response.into_parts(); + let body = axum::body::to_bytes(body, 64 * 1024) + .await + .unwrap_or_default(); + let missing_api = serde_json::from_slice::(&body).is_ok_and(|value| { + value.get("error_code").and_then(serde_json::Value::as_str) == Some("GTW001") + }); + if !missing_api { + return Ok(Response::from_parts(parts, axum::body::Body::from(body))); + } + // Python answers an unknown API in-band: HTTP 200 carrying grpc-status 12. + let mut response = crate::protocol::grpc::web_trailer_response( + false, + tonic::Code::Unimplemented, + "API not found", + ); + response.headers_mut().insert( + http::header::CONTENT_TYPE, + http::HeaderValue::from_static("application/grpc-web"), + ); + Ok(response) } use axum::response::IntoResponse; diff --git a/gateway-rs/src/routes/mod.rs b/gateway-rs/src/routes/mod.rs index 460c2d0d..6acb7b68 100644 --- a/gateway-rs/src/routes/mod.rs +++ b/gateway-rs/src/routes/mod.rs @@ -7,3 +7,4 @@ pub mod operations; pub mod platform; pub mod rest; pub mod soap; +pub mod tls_admin; diff --git a/gateway-rs/src/routes/operations.rs b/gateway-rs/src/routes/operations.rs index d93637a7..a1fca996 100644 --- a/gateway-rs/src/routes/operations.rs +++ b/gateway-rs/src/routes/operations.rs @@ -33,6 +33,11 @@ struct StatusResponse { uptime: String, } +#[derive(Serialize)] +pub struct FeaturesResponse { + logs_enabled: bool, +} + #[derive(Clone, Copy)] enum GatewayOperation { Status, @@ -49,6 +54,12 @@ pub async fn health( Ok(Json(HealthResponse { status: "online" }).into_response()) } +pub async fn features(State(state): State) -> Json { + Json(FeaturesResponse { + logs_enabled: state.config.logs_enabled, + }) +} + pub async fn status( State(state): State, request: Request, @@ -149,23 +160,31 @@ async fn require_manage_gateway( headers: &HeaderMap, operation: GatewayOperation, ) -> Result { - let claims = verify_request_token(headers, &state.config.shared_storage) - .map_err(|_| policy_error(StatusCode::UNAUTHORIZED, "GTW401", "Unauthorized"))?; + // The pinned status route wraps an auth failure as GTW401, while the + // cache-clearing route lets FastAPI render `{"detail": "Unauthorized"}`. + let unauthorized = || match operation { + GatewayOperation::Status => { + policy_error(StatusCode::UNAUTHORIZED, "GTW401", "Unauthorized") + } + GatewayOperation::ClearCaches => ( + StatusCode::UNAUTHORIZED, + Json(json!({"detail": "Unauthorized"})), + ) + .into_response(), + }; + let claims = + verify_request_token(headers, &state.config.shared_storage).map_err(|_| unauthorized())?; let username = claims.sub.as_deref().unwrap_or_default(); let documents = load_documents(state).await?; if is_revoked(&documents.revocations, username, claims.jti.as_deref()) { - return Err(policy_error( - StatusCode::UNAUTHORIZED, - "GTW401", - "Unauthorized", - )); + return Err(unauthorized()); } let user = documents .users .iter() .find(|user| string_field(user, "username") == Some(username)) .filter(|user| bool_field_default(user, "active", true)) - .ok_or_else(|| policy_error(StatusCode::UNAUTHORIZED, "GTW401", "Unauthorized"))?; + .ok_or_else(unauthorized)?; let role_name = string_field(user, "role").unwrap_or_default(); let allowed = documents.roles.iter().any(|role| { string_field(role, "role_name") == Some(role_name) diff --git a/gateway-rs/src/routes/platform.rs b/gateway-rs/src/routes/platform.rs index 14e40f09..dbb9241f 100644 --- a/gateway-rs/src/routes/platform.rs +++ b/gateway-rs/src/routes/platform.rs @@ -6,7 +6,7 @@ use std::{ io::Read, net::SocketAddr, process::{Command, Stdio}, - sync::{OnceLock, atomic::Ordering}, + sync::OnceLock, time::{SystemTime, UNIX_EPOCH}, }; @@ -18,6 +18,7 @@ use axum::{ use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD}; use http::{HeaderMap, HeaderValue, Method, StatusCode, header}; use jsonwebtoken::{Algorithm, EncodingKey, Header, encode}; +use regex::Regex; use serde::Serialize; use serde_json::{Map, Value, json}; use sha2::{Digest, Sha256}; @@ -31,14 +32,13 @@ static PYTHON_OPENAPI: OnceLock = OnceLock::new(); use crate::{ middleware::{ body_limit::BodyLimits, - chaos::{ - CHAOS_ENABLED, CHAOS_ERROR_BUDGET_BURN, CHAOS_ERROR_STATUS, CHAOS_EVENTS_COUNT, - CHAOS_LATENCY_MS, CHAOS_MONGO_OUTAGE, CHAOS_REDIS_OUTAGE, - }, + chaos::{CHAOS_ERROR_BUDGET_BURN, CHAOS_MONGO_OUTAGE, CHAOS_REDIS_OUTAGE}, response_compat::MessageEnvelope, }, observability::{ - analytics_aggregator::{AggregatedPoint, EntityCounter, global_analytics}, + analytics_aggregator::{ + AggregatedPoint, EndpointEntity, EntityCounter, global_analytics, pooled_percentiles, + }, audit::{self, global_ip_deny}, }, platform_contract::{normalize_create_api, normalize_update_api}, @@ -70,6 +70,17 @@ impl DescriptorBackfill { } } +fn python_truthy(value: &Value) -> bool { + match value { + Value::Null => false, + Value::Bool(value) => *value, + Value::Number(value) => value.as_f64().is_some_and(|value| value != 0.0), + Value::String(value) => !value.is_empty(), + Value::Array(value) => !value.is_empty(), + Value::Object(value) => !value.is_empty(), + } +} + #[derive(Serialize)] struct AccessClaims { sub: String, @@ -113,25 +124,31 @@ impl Drop for ProtoCompileDirectory { } } -const RBAC_PERMISSIONS: &[&str] = &[ - "manage_users", - "manage_apis", - "manage_endpoints", - "manage_groups", - "manage_roles", - "manage_routings", - "manage_gateway", - "manage_subscriptions", - "manage_credits", - "manage_auth", - "manage_security", - "manage_tiers", - "manage_rate_limits", - "view_analytics", - "view_logs", - "export_logs", - "ui_access", -]; +/// Percent-decode a request path as UTF-8, leaving invalid escapes intact. +fn percent_decode_path(path: &str) -> String { + let bytes = path.as_bytes(); + let mut decoded = Vec::with_capacity(bytes.len()); + let mut index = 0; + while index < bytes.len() { + if bytes[index] == b'%' + && let (Some(high), Some(low)) = ( + bytes + .get(index + 1) + .and_then(|byte| (*byte as char).to_digit(16)), + bytes + .get(index + 2) + .and_then(|byte| (*byte as char).to_digit(16)), + ) + { + decoded.push((high * 16 + low) as u8); + index += 3; + continue; + } + decoded.push(bytes[index]); + index += 1; + } + String::from_utf8(decoded).unwrap_or_else(|_| path.to_owned()) +} pub async fn platform_dispatch( State(state): State, @@ -146,7 +163,11 @@ pub async fn platform_dispatch( let headers = request.headers().clone(); let request_id = request_id_from(&headers); let query = parse_query(uri.query()); - let path = uri.path().strip_prefix("/platform").unwrap_or(uri.path()); + // Starlette routes on the percent-decoded path (e.g. `%40` in an email). + let decoded_path = percent_decode_path(uri.path()); + let path = decoded_path + .strip_prefix("/platform") + .unwrap_or(decoded_path.as_str()); if path != "/security/settings" && let Some(response) = platform_ip_filter(&state, &headers, direct_addr, &request_id).await { @@ -164,21 +185,78 @@ pub async fn platform_dispatch( ); } }; + // Body-size middleware runs first; then Starlette resolves the route before + // any handler runs: an unknown path is + // a bare 404 and a known path with another method is a 405 naming the + // first registered route's method. + match python_route_match(path, &method) { + RouteMatch::Found => {} + RouteMatch::NotFound => { + // Starlette's redirect_slashes: when the path with its trailing + // slash toggled resolves (for any method), answer 307 to it. + let toggled = match uri.path().strip_suffix('/') { + Some(stripped) => stripped.to_owned(), + None => format!("{}/", uri.path()), + }; + let toggled_decoded = percent_decode_path(&toggled); + let toggled_path = toggled_decoded + .strip_prefix("/platform") + .unwrap_or(toggled_decoded.as_str()); + if !matches!( + python_route_match(toggled_path, &method), + RouteMatch::NotFound + ) { + // A relative Location stays correct behind TLS termination. + let query = uri.query().map(|q| format!("?{q}")).unwrap_or_default(); + let mut response = StatusCode::TEMPORARY_REDIRECT.into_response(); + if let Ok(value) = HeaderValue::from_str(&format!("{toggled}{query}")) { + response.headers_mut().insert(header::LOCATION, value); + } + return response; + } + return json_response( + StatusCode::NOT_FOUND, + json!({"detail": "Not Found"}), + &request_id, + ); + } + RouteMatch::MethodNotAllowed(allow) => { + let mut response = json_response( + StatusCode::METHOD_NOT_ALLOWED, + json!({"detail": "Method Not Allowed"}), + &request_id, + ); + if let Ok(value) = HeaderValue::from_str(&allow) { + response.headers_mut().insert(header::ALLOW, value); + } + return response; + } + } + if rejects_non_json_body(path, &method, &headers, &body) { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", + &request_id, + ); + } let parsed_payload = if body.is_empty() { Ok(Value::Object(Map::new())) } else { serde_json::from_slice(&body) }; - if matches!(path, "/authorization" | "/authorization/register") + // Python rate-limits login/registration by IP before parsing the body, so + // the invalid-payload response is deferred until after that check. + let invalid_auth_payload = matches!(path, "/authorization" | "/authorization/register") && method == Method::POST - && parsed_payload.is_err() - { - return error( + && (parsed_payload.is_err() || body.is_empty()); + let invalid_auth_response = || { + error( StatusCode::BAD_REQUEST, "AUTH004", "Invalid JSON payload", &request_id, - ); + ) }; // FastAPI validates a typed JSON body before entering the route handler. // The shared entity routes model those typed Python endpoints, so malformed @@ -187,7 +265,20 @@ pub async fn platform_dispatch( // and WSDL uploads retain their content-specific parsing behavior. if parsed_payload.is_err() && content_type_is_json(&headers) - && is_typed_json_mutation(path, &method) + && declares_json_body(path, &method) + { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", + &request_id, + ); + } + let mut payload = parsed_payload.unwrap_or(Value::Null); + // FastAPI validates the declared body model before the handler runs. + if !body.is_empty() + && content_type_is_json(&headers) + && body_fails_declared_model(path, &method, &payload) { return error( StatusCode::UNPROCESSABLE_ENTITY, @@ -196,7 +287,6 @@ pub async fn platform_dispatch( &request_id, ); } - let payload = parsed_payload.unwrap_or(Value::Null); // SubscribeModel requires all three fields. Keep this lightweight // compatibility check at dispatch time until the remaining typed model is // ported, so a missing field reaches FastAPI's global validation envelope @@ -212,21 +302,95 @@ pub async fn platform_dispatch( &request_id, ); } - - if path == "/authorization" && method == Method::POST { - if let Some(response) = auth_account_rate_limit( - &state, - &payload, - "LOGIN_ACCOUNT_RATE_LIMIT", - 10, - "LOGIN_ACCOUNT_RATE_WINDOW", - 900, + if method == Method::PUT + && is_update_password_path(path) + && normalize_update_password_model(&mut payload).is_err() + { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", &request_id, - ) - .await - { - return response; + ); + } + if (content_type_is_json(&headers) || body.is_empty()) + && is_tier_typed_mutation(path, &method) + && normalize_tier_request(path, &method, &mut payload).is_err() + { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", + &request_id, + ); + } + // Endpoint validation models are FastAPI body parameters, so an invalid + // body is rejected before the handler authenticates the caller. + let validation_create = method == Method::POST + && matches!( + path, + "/endpoint/validation" | "/endpoint/endpoint/validation" + ); + let validation_update = method == Method::PUT + && ["/endpoint/validation/", "/endpoint/endpoint/validation/"] + .iter() + .any(|prefix| { + path.strip_prefix(prefix) + .is_some_and(|id| !id.is_empty() && !id.contains('/')) + }); + if (validation_create || validation_update) + && normalize_endpoint_validation_model(&mut payload.clone(), validation_create).is_err() + { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", + &request_id, + ); + } + // A request with no body at all is a 422 for every route whose Python + // counterpart declares a required typed body parameter, regardless of + // Content-Type (see requires_nonempty_json_body). + if body.is_empty() && requires_nonempty_json_body(path, &method) { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", + &request_id, + ); + } + // Python's body: dict[str, Any] parameter has no default, so a request + // with no body at all is a 422 even though an explicit `{}` is valid. + if path == "/config/import" + && method == Method::POST + && (body.is_empty() || !payload.is_object()) + { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", + &request_id, + ); + } + + // FastAPI validates `limit: int = Query(ge=1, le=1000)` and + // `offset: int = Query(ge=0)` before the handler authenticates. + if path == "/logging/logs" && method == Method::GET { + let out_of_range = |field: &str, min: i64, max: i64| { + query + .get(field) + .is_some_and(|value| !value.parse::().is_ok_and(|n| (min..=max).contains(&n))) + }; + if out_of_range("limit", 1, 1_000) || out_of_range("offset", 0, i64::MAX) { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", + &request_id, + ); } + } + if path == "/authorization" && method == Method::POST { if let Some(response) = auth_ip_rate_limit( &state, &headers, @@ -241,6 +405,22 @@ pub async fn platform_dispatch( { return response; } + if invalid_auth_payload { + return invalid_auth_response(); + } + if let Some(response) = auth_account_rate_limit( + &state, + &payload, + "LOGIN_ACCOUNT_RATE_LIMIT", + 10, + "LOGIN_ACCOUNT_RATE_WINDOW", + 900, + &request_id, + ) + .await + { + return response; + } return login(&state, &headers, payload, &request_id).await; } if path == "/authorization/register" && method == Method::POST { @@ -258,6 +438,9 @@ pub async fn platform_dispatch( { return response; } + if invalid_auth_payload { + return invalid_auth_response(); + } if let Some(response) = auth_account_rate_limit( &state, &payload, @@ -277,7 +460,7 @@ pub async fn platform_dispatch( return success(StatusCode::OK, json!({"status": "alive"}), &request_id); } if path == "/monitor/readiness" && method == Method::GET { - let privileged = match authorize(&state, &headers, path, &request_id).await { + let privileged = match authorize(&state, &headers, &method, path, &request_id).await { Ok(claims) => { let username = claims.sub.as_deref().unwrap_or_default(); has_permission(&state, username, "manage_gateway").await @@ -287,33 +470,31 @@ pub async fn platform_dispatch( return readiness(&state, privileged, &request_id).await; } - // The pinned rate-limit-rule router has no authentication dependency for - // its management endpoints. Its status endpoint is the lone exception - // (it depends on the authenticated subject). Dispatch the public slice - // before the platform-wide authorization gate, retaining the reference's - // intentionally permissive surface. - if path == "/rate-limits" || path == "/rate-limits/" || path.starts_with("/rate-limits/") { - if let Some(response) = dispatch_core_entities( - &state, - path, - &method, - payload.clone(), - &query, - "", - &request_id, - ) - .await - { - return response; - } - } - - let claims = match authorize(&state, &headers, path, &request_id).await { + // The pinned tier and rate-limit-rule routers have no authentication at + // all, so anyone could create, delete or assign tiers and limits. The + // candidate requires an authenticated caller with the management + // permission instead (approved divergence MIG-123). + let claims = match authorize(&state, &headers, &method, path, &request_id).await { Ok(claims) => claims, Err(response) => return response, }; let username = claims.sub.clone().unwrap_or_default(); + if path == "/tls" || path.starts_with("/tls/") { + let response = if has_permission(&state, &username, "manage_security").await { + crate::routes::tls_admin::dispatch(&state, path, &method, payload, &request_id).await + } else { + error( + StatusCode::FORBIDDEN, + "TLSA001", + "manage_security is required", + &request_id, + ) + }; + audit_management_request(&username, &method, path, &response); + return with_platform_activity_context(response, &username, path); + } + if path.starts_with("/authorization") { let response = authorization_routes( &state, @@ -375,13 +556,8 @@ pub async fn platform_dispatch( (Method::POST, "/memory/restore") => { memory_restore(&state, payload, &username, &request_id).await } - (Method::GET, "/dashboard") => { - if !has_permission(&state, &username, "view_analytics").await { - analytics_denied(&request_id) - } else { - dashboard(&state, &request_id).await - } - } + // The pinned dashboard only requires authentication. + (Method::GET, "/dashboard") => dashboard(&state, &request_id).await, (Method::GET, "/monitor/metrics") => { if !has_permission(&state, &username, "manage_gateway").await { error( @@ -391,7 +567,7 @@ pub async fn platform_dispatch( &request_id, ) } else { - monitor_metrics(&state, &request_id).await + monitor_metrics(&state, &query, &request_id).await } } (Method::GET, "/monitor/report") => { @@ -407,57 +583,77 @@ pub async fn platform_dispatch( } } (Method::GET, "/analytics/overview") => { - analytics_overview(&state, &username, &query, &request_id).await + if let Some(response) = analytics_query_error(&query, false, &request_id) { + response + } else { + analytics_overview(&state, &username, &query, &request_id).await + } } (Method::GET, "/analytics/timeseries") => { - if !has_permission(&state, &username, "view_analytics").await { + if let Some(response) = analytics_query_error(&query, false, &request_id) { + response + } else if !has_permission(&state, &username, "view_analytics").await { analytics_denied(&request_id) } else { analytics_timeseries(&query, &request_id) } } (Method::GET, "/analytics/top-apis") => { - if !has_permission(&state, &username, "view_analytics").await { + if let Some(response) = analytics_query_error(&query, true, &request_id) { + response + } else if !has_permission(&state, &username, "view_analytics").await { analytics_denied(&request_id) } else { analytics_top("api", &query, &request_id) } } (Method::GET, "/analytics/top-users") => { - if !has_permission(&state, &username, "view_analytics").await { + if let Some(response) = analytics_query_error(&query, true, &request_id) { + response + } else if !has_permission(&state, &username, "view_analytics").await { analytics_denied(&request_id) } else { analytics_top("user", &query, &request_id) } } (Method::GET, "/analytics/top-endpoints") => { - if !has_permission(&state, &username, "view_analytics").await { + if let Some(response) = analytics_query_error(&query, true, &request_id) { + response + } else if !has_permission(&state, &username, "view_analytics").await { analytics_denied(&request_id) } else { analytics_top("endpoint", &query, &request_id) } } (Method::GET, detail) if detail.starts_with("/analytics/api/") => { - analytics_detail( - &state, - &username, - "api", - detail.trim_start_matches("/analytics/api/"), - &query, - &request_id, - ) - .await + if let Some(response) = analytics_query_error(&query, false, &request_id) { + response + } else { + analytics_detail( + &state, + &username, + "api", + detail.trim_start_matches("/analytics/api/"), + &query, + &request_id, + ) + .await + } } (Method::GET, detail) if detail.starts_with("/analytics/user/") => { - analytics_detail( - &state, - &username, - "user", - detail.trim_start_matches("/analytics/user/"), - &query, - &request_id, - ) - .await + if let Some(response) = analytics_query_error(&query, false, &request_id) { + response + } else { + analytics_detail( + &state, + &username, + "user", + detail.trim_start_matches("/analytics/user/"), + &query, + &request_id, + ) + .await + } } (Method::GET, "/security/settings") => { if !has_permission(&state, &username, "manage_security").await { @@ -542,28 +738,21 @@ pub async fn platform_dispatch( } } (Method::GET, "/config/reloadable-keys") => { - if !has_permission(&state, &username, "manage_gateway").await { - http_detail( - StatusCode::FORBIDDEN, - "Insufficient permissions: manage_gateway required", - &request_id, - ) - } else { - success( - StatusCode::OK, - json!({ - "reloadable_keys": active_reloadable_keys(), - "total": 3, - "restart_required_keys": restart_required_keys(), - "notes": [ - "Environment variables always override config file values", - "GATEWAY_TIMEOUT, RETRY_ENABLED, and RETRY_MAX_ATTEMPTS apply to the next REST, GraphQL, or SOAP request", - "All other listed settings require a controlled restart or rolling deployment" - ] - }), - &request_id, - ) - } + // The pinned route only requires authentication (no role check). + success( + StatusCode::OK, + json!({ + "reloadable_keys": active_reloadable_keys(), + "total": 3, + "restart_required_keys": restart_required_keys(), + "notes": [ + "Environment variables always override config file values", + "GATEWAY_TIMEOUT, RETRY_ENABLED, and RETRY_MAX_ATTEMPTS apply to the next REST, GraphQL, or SOAP request", + "All other listed settings require a controlled restart or rolling deployment" + ] + }), + &request_id, + ) } (Method::POST, "/config/reload") => { if !has_permission(&state, &username, "manage_gateway").await { @@ -596,7 +785,7 @@ pub async fn platform_dispatch( } } } - (Method::POST, "/demo/seed") => demo_seed(&state, &username, &request_id).await, + (Method::POST, "/demo/seed") => demo_seed(&state, &username, &query, &request_id).await, (Method::POST, "/tools/cors/check") => { if !has_permission(&state, &username, "manage_security").await { error( @@ -649,7 +838,11 @@ pub async fn platform_dispatch( "You do not have permission to use tools", &request_id, ) - } else if let Some(backend) = payload.get("backend").and_then(Value::as_str) { + } else { + let (backend, enabled, duration_ms) = match normalize_chaos_toggle(&payload) { + Ok(values) => values, + Err(errors) => return validation_errors(errors, &request_id), + }; let backend = backend.trim().to_ascii_lowercase(); let target = match backend.as_str() { "redis" => &CHAOS_REDIS_OUTAGE, @@ -663,24 +856,11 @@ pub async fn platform_dispatch( ); } }; - let Some(enabled) = payload.get("enabled").and_then(Value::as_bool) else { - return validation_errors( - vec![json!({ - "loc": ["body", "enabled"], - "msg": "field required", - "type": "value_error.missing" - })], - &request_id, - ); - }; - let duration_ms = payload - .get("duration_ms") - .and_then(Value::as_u64) - .unwrap_or(0); if duration_ms > 0 { target.store(true, std::sync::atomic::Ordering::Relaxed); tokio::spawn(async move { - tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await; + tokio::time::sleep(std::time::Duration::from_millis(duration_ms as u64)) + .await; target.store(false, std::sync::atomic::Ordering::Relaxed); }); } else { @@ -694,32 +874,6 @@ pub async fn platform_dispatch( }), &request_id, ) - } else { - // Retain the additive v2 latency/error injection extension. - let enabled = payload - .get("enabled") - .and_then(Value::as_bool) - .unwrap_or(false); - let latency = payload - .get("latency_ms") - .and_then(Value::as_u64) - .unwrap_or(0); - let error_status = payload - .get("error_status") - .and_then(Value::as_u64) - .unwrap_or(0) as u32; - CHAOS_ENABLED.store(enabled, std::sync::atomic::Ordering::Relaxed); - CHAOS_LATENCY_MS.store(latency, std::sync::atomic::Ordering::Relaxed); - CHAOS_ERROR_STATUS.store(error_status, std::sync::atomic::Ordering::Relaxed); - success( - StatusCode::OK, - json!({ - "enabled": enabled, - "latency_ms": latency, - "error_status": error_status - }), - &request_id, - ) } } (Method::GET, "/tools/chaos/stats") => { @@ -736,11 +890,7 @@ pub async fn platform_dispatch( json!({ "redis_outage": CHAOS_REDIS_OUTAGE.load(std::sync::atomic::Ordering::Relaxed), "mongo_outage": CHAOS_MONGO_OUTAGE.load(std::sync::atomic::Ordering::Relaxed), - "error_budget_burn": CHAOS_ERROR_BUDGET_BURN.load(std::sync::atomic::Ordering::Relaxed), - "enabled": CHAOS_ENABLED.load(std::sync::atomic::Ordering::Relaxed), - "latency_ms": CHAOS_LATENCY_MS.load(std::sync::atomic::Ordering::Relaxed), - "error_status": CHAOS_ERROR_STATUS.load(std::sync::atomic::Ordering::Relaxed), - "events": CHAOS_EVENTS_COUNT.load(std::sync::atomic::Ordering::Relaxed) + "error_budget_burn": CHAOS_ERROR_BUDGET_BURN.load(std::sync::atomic::Ordering::Relaxed) }), &request_id, ) @@ -788,7 +938,16 @@ pub async fn platform_dispatch( if path.starts_with("/subscription") { subscription_routes(&state, path, &method, payload, &username, &request_id).await } else if path.starts_with("/credit") { - credit_routes(&state, path, &method, payload, &username, &request_id).await + credit_routes( + &state, + path, + &method, + payload, + &query, + &username, + &request_id, + ) + .await } else if path.starts_with("/vault") { vault_routes(&state, path, &method, payload, &username, &request_id).await } else if path.starts_with("/quota") { @@ -884,13 +1043,22 @@ async fn dispatch_core_entities( request_id: &str, ) -> Option { if path == "/tiers" || path == "/tiers/" || path.starts_with("/tiers/") { + if !has_permission(state, username, "manage_tiers").await { + return Some(error( + StatusCode::FORBIDDEN, + "TIER001", + "You do not have permission to manage tiers", + request_id, + )); + } let suffix = path.trim_start_matches("/tiers").trim_matches('/'); let basic = suffix.is_empty() || (!suffix.contains('/') - && !matches!( - suffix, - "upgrade" | "downgrade" | "temporary-upgrade" | "compare" | "assignments" - )); + && (method == Method::GET + || !matches!( + suffix, + "upgrade" | "downgrade" | "temporary-upgrade" | "compare" | "assignments" + ))); if basic { return Some( tier_crud_routes(state, path, method, payload, query, username, request_id).await, @@ -902,6 +1070,15 @@ async fn dispatch_core_entities( } if path == "/rate-limits" || path == "/rate-limits/" || path.starts_with("/rate-limits/") { let suffix = path.trim_start_matches("/rate-limits").trim_matches('/'); + // A caller may always read their own limit status. + if suffix != "status" && !has_permission(state, username, "manage_rate_limits").await { + return Some(error( + StatusCode::FORBIDDEN, + "RATE002", + "You do not have permission to manage rate limits", + request_id, + )); + } let basic = suffix.is_empty() || (!suffix.contains('/') && !matches!(suffix, "search" | "status")); if basic { @@ -930,7 +1107,7 @@ async fn dispatch_core_entities( updated: "Group updated successfully", deleted: "Group deleted successfully", duplicate_code: "GRP001", - not_found_code: "GRP002", + not_found_code: "GRP003", }, ), ( @@ -945,8 +1122,8 @@ async fn dispatch_core_entities( created: "Role created successfully", updated: "Role updated successfully", deleted: "Role deleted successfully", - duplicate_code: "ROL001", - not_found_code: "ROL002", + duplicate_code: "ROLE001", + not_found_code: "ROLE004", }, ), ( @@ -1014,17 +1191,9 @@ async fn dispatch_core_entities( async fn tier_delete_route( state: &AppState, tier_id: &str, - username: &str, + _username: &str, request_id: &str, ) -> Response { - if !has_permission(state, username, "manage_tiers").await { - return error( - StatusCode::FORBIDDEN, - "TIER001", - "You do not have permission to manage tiers", - request_id, - ); - } let Some(storage) = &state.storage else { return unexpected(request_id); }; @@ -1033,7 +1202,7 @@ async fn tier_delete_route( .await { Ok(assignments) => assignments.len(), - Err(_) => return unexpected(request_id), + Err(_) => return tier_detail_failure("delete", request_id), }; if assigned > 0 { return http_detail( @@ -1046,13 +1215,13 @@ async fn tier_delete_route( .delete_one("tiers", &json!({"tier_id": tier_id})) .await { - Ok(true) => message(StatusCode::OK, "Tier deleted", request_id), + Ok(true) => response_model_envelope("Tier deleted", request_id), Ok(false) => http_detail( StatusCode::NOT_FOUND, &format!("Tier {tier_id} not found"), request_id, ), - Err(_) => unexpected(request_id), + Err(_) => tier_detail_failure("delete", request_id), } } @@ -1065,7 +1234,11 @@ async fn tier_crud_routes( username: &str, request_id: &str, ) -> Response { - if !has_permission(state, username, "manage_tiers").await { + let suffix = path.trim_start_matches("/tiers").trim_matches('/'); + if suffix.is_empty() + && method == Method::GET + && !has_permission(state, username, "manage_tiers").await + { return error( StatusCode::FORBIDDEN, "TIER001", @@ -1076,7 +1249,6 @@ async fn tier_crud_routes( let Some(storage) = &state.storage else { return unexpected(request_id); }; - let suffix = path.trim_start_matches("/tiers").trim_matches('/'); if suffix.is_empty() && method == Method::GET { let skip = match tier_pagination(query, "skip", 0, 0, usize::MAX) { Ok(value) => value, @@ -1086,9 +1258,13 @@ async fn tier_crud_routes( Ok(value) => value, Err(()) => return tier_pagination_error("limit", request_id), }; - let enabled_only = query - .get("enabled_only") - .is_some_and(|value| value.eq_ignore_ascii_case("true")); + let enabled_only = match query.get("enabled_only") { + Some(value) => match pydantic_bool_string(value) { + Some(value) => value, + None => return tier_pagination_error("enabled_only", request_id), + }, + None => false, + }; let search = query .get("search") .map(|value| value.to_ascii_lowercase()) @@ -1147,14 +1323,10 @@ async fn tier_crud_routes( request_id, ); }; - let Some(name) = payload - .get("name") - .and_then(Value::as_str) - .filter(|value| matches!(*value, "free" | "pro" | "enterprise" | "custom")) - else { + let Some(name) = payload.get("name").and_then(Value::as_str) else { return validation_errors( vec![ - json!({"loc": ["body", "name"], "msg": "value is not a valid enumeration member", "type": "type_error.enum"}), + json!({"loc": ["body", "name"], "msg": "field required", "type": "value_error.missing"}), ], request_id, ); @@ -1175,10 +1347,34 @@ async fn tier_crud_routes( request_id, ); }; - if let Ok(Some(existing)) = storage + let fallback = || { + success( + StatusCode::CREATED, + tier_response(json!({ + "tier_id": tier_id, "name": name, "display_name": display_name, + "description": payload.get("description").cloned().unwrap_or(Value::Null), + "limits": tier_limits_response(limits), + "price_monthly": payload.get("price_monthly").cloned().unwrap_or(Value::Null), + "price_yearly": payload.get("price_yearly").cloned().unwrap_or(Value::Null), + "features": payload.get("features").cloned().unwrap_or_else(|| json!([])), + "is_default": payload.get("is_default").cloned().unwrap_or_else(|| json!(false)), + "enabled": payload.get("enabled").cloned().unwrap_or_else(|| json!(true)), + "created_at": Value::Null, "updated_at": Value::Null, + })), + request_id, + ) + }; + if !matches!(name, "free" | "pro" | "enterprise" | "custom") { + return fallback(); + } + let existing = match storage .find_one("tiers", &json!({"tier_id": tier_id})) .await { + Ok(existing) => existing, + Err(_) => return fallback(), + }; + if let Some(existing) = existing { return success(StatusCode::CREATED, tier_response(existing), request_id); } let now = timestamp_now_naive(); @@ -1198,7 +1394,7 @@ async fn tier_crud_routes( }); return match storage.insert_one("tiers", tier.clone()).await { Ok(_) => success(StatusCode::CREATED, tier_response(tier), request_id), - Err(_) => unexpected(request_id), + Err(_) => fallback(), }; } if suffix.is_empty() { @@ -1220,7 +1416,7 @@ async fn tier_crud_routes( &format!("Tier {suffix} not found"), request_id, ), - Err(_) => unexpected(request_id), + Err(_) => tier_detail_failure("get", request_id), }; } if method == Method::PUT { @@ -1252,7 +1448,7 @@ async fn tier_crud_routes( &format!("Tier {suffix} not found"), request_id, ), - Err(_) => unexpected(request_id), + Err(_) => tier_detail_failure("update", request_id), }; } error( @@ -1286,6 +1482,414 @@ fn tier_pagination_error(field: &str, request_id: &str) -> Response { ) } +fn tier_detail_failure(operation: &str, request_id: &str) -> Response { + let detail = match operation { + "get" => "Failed to get tier", + "update" => "Failed to update tier", + "delete" => "Failed to delete tier", + "assign" => "Failed to assign user", + "get_assignment" => "Failed to get assignment", + "get_user_tier" => "Failed to get user tier", + "remove_assignment" => "Failed to remove assignment", + "list_users" => "Failed to list users", + "upgrade" => "Failed to upgrade tier", + "downgrade" => "Failed to downgrade tier", + "temporary-upgrade" => "Failed to create temporary upgrade", + "trial/start" => "Failed to start trial", + "payment/failure" => "Failed to handle payment failure", + "compare" => "Failed to compare tiers", + _ => "An unexpected error occurred", + }; + http_detail(StatusCode::INTERNAL_SERVER_ERROR, detail, request_id) +} + +fn tier_statistics_failure(request_id: &str) -> Response { + error( + StatusCode::INTERNAL_SERVER_ERROR, + "TIER999", + "Failed to get statistics", + request_id, + ) +} + +fn pydantic_bool_string(value: &str) -> Option { + security_setting_bool(&Value::String(value.to_owned())) +} + +fn is_tier_typed_mutation(path: &str, method: &Method) -> bool { + if method == Method::POST { + return matches!( + path, + "/tiers" + | "/tiers/" + | "/tiers/assignments" + | "/tiers/upgrade" + | "/tiers/downgrade" + | "/tiers/temporary-upgrade" + | "/tiers/trial/start" + | "/tiers/payment/failure" + | "/tiers/compare" + ); + } + method == Method::PUT + && path + .strip_prefix("/tiers/") + .is_some_and(|suffix| !suffix.is_empty() && !suffix.contains('/')) +} + +fn tier_model_string(value: Option<&Value>, required: bool) -> Result, ()> { + match value { + Some(Value::Null) if !required => Ok(None), + Some(value) => security_setting_string(value) + .map(|value| Some(Value::String(value))) + .ok_or(()), + None if !required => Ok(None), + None => Err(()), + } +} + +fn tier_model_integer(value: Option<&Value>, default: Option) -> Result, ()> { + match value { + Some(Value::Null) if default.is_none() => Ok(Some(Value::Null)), + Some(value) => rate_rule_integer(value) + .map(|value| Some(json!(value))) + .ok_or(()), + None => Ok(default.map(|value| json!(value))), + } +} + +fn tier_model_float(value: Option<&Value>) -> Result, ()> { + let Some(value) = value else { + return Ok(None); + }; + if value.is_null() { + return Ok(None); + } + let parsed = match value { + Value::Number(value) => value.as_f64(), + Value::String(value) => value.parse::().ok(), + Value::Bool(value) => Some(if *value { 1.0 } else { 0.0 }), + _ => None, + } + .filter(|value| value.is_finite()) + .ok_or(())?; + serde_json::Number::from_f64(parsed) + .map(Value::Number) + .map(Some) + .ok_or(()) +} + +fn tier_model_datetime(value: &Value) -> Option { + let numeric = match value { + Value::Number(value) => value.as_f64(), + Value::Bool(value) => Some(if *value { 1.0 } else { 0.0 }), + Value::String(value) => value.trim().parse::().ok(), + _ => None, + }; + if let Some(mut seconds) = numeric { + if seconds.is_nan() { + return None; + } + if seconds > 3e20 { + return Some(json!("9999-12-31T23:59:59.999999")); + } + if seconds < -3e20 { + return Some(json!("0001-01-01T00:00:00")); + } + while seconds.abs() > 2e10 { + seconds /= 1_000.0; + } + let micros = (seconds * 1_000_000.0).round_ties_even() as i128; + let datetime = time::OffsetDateTime::from_unix_timestamp_nanos(micros * 1_000).ok()?; + return Some(Value::String(format_tier_datetime(datetime, Some(0)))); + } + + static DATETIME: OnceLock = OnceLock::new(); + let value = value.as_str()?; + let captures = DATETIME + .get_or_init(|| { + Regex::new( + r"^(\d{4})-(\d{1,2})-(\d{1,2})[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{1,12}))?)?(Z|[+-]\d{2}(?::?\d{2})?)?$", + ) + .expect("tier datetime regex") + }) + .captures(value)?; + let number = |index| captures.get(index)?.as_str().parse::().ok(); + let year = captures.get(1)?.as_str().parse::().ok()?; + let month = time::Month::try_from(number(2)?).ok()?; + let day = number(3)?; + let hour = number(4)?; + let minute = number(5)?; + let second = captures + .get(6) + .map_or(Some(0), |value| value.as_str().parse::().ok())?; + let micros = captures.get(7).map_or(Some(0), |value| { + let digits = value.as_str(); + let padded = format!("{digits:0<6}"); + padded[..6].parse::().ok() + })?; + let date = time::Date::from_calendar_date(year, month, day).ok()?; + let time = time::Time::from_hms_micro(hour, minute, second, micros).ok()?; + let offset_minutes = match captures.get(8).map(|value| value.as_str()) { + None => None, + Some("Z") => Some(0), + Some(offset) => { + let sign = if offset.starts_with('-') { -1 } else { 1 }; + let digits = &offset[1..]; + let hours = digits.get(..2)?.parse::().ok()?; + let minutes = match digits.len() { + 2 => 0, + 4 => digits.get(2..)?.parse::().ok()?, + 5 if digits.as_bytes().get(2) == Some(&b':') => { + digits.get(3..)?.parse::().ok()? + } + _ => return None, + }; + if hours >= 24 || minutes >= 60 { + return None; + } + Some(sign * (hours * 60 + minutes)) + } + }; + let datetime = time::PrimitiveDateTime::new(date, time).assume_utc(); + Some(Value::String(format_tier_datetime( + datetime, + offset_minutes, + ))) +} + +fn format_tier_datetime(datetime: time::OffsetDateTime, offset_minutes: Option) -> String { + let mut value = format!( + "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}", + datetime.year(), + u8::from(datetime.month()), + datetime.day(), + datetime.hour(), + datetime.minute(), + datetime.second(), + ); + if datetime.microsecond() != 0 { + write!(value, ".{:06}", datetime.microsecond()).expect("write to string"); + } + if let Some(minutes) = offset_minutes { + let sign = if minutes < 0 { '-' } else { '+' }; + let minutes = minutes.abs(); + write!(value, "{sign}{:02}:{:02}", minutes / 60, minutes % 60).expect("write to string"); + } + value +} + +fn normalize_tier_limits(value: &Value) -> Result { + let input = value.as_object().ok_or(())?; + let mut output = Map::new(); + for field in [ + "requests_per_second", + "requests_per_minute", + "requests_per_hour", + "requests_per_day", + "requests_per_month", + "monthly_request_quota", + "daily_request_quota", + "monthly_bandwidth_quota", + ] { + if let Some(value) = tier_model_integer(input.get(field), None)? { + output.insert(field.to_owned(), value); + } + } + for (field, default) in [ + ("burst_per_second", 0), + ("burst_per_minute", 0), + ("burst_per_hour", 0), + ("max_queue_time_ms", 5_000), + ] { + output.insert( + field.to_owned(), + tier_model_integer(input.get(field), Some(default))?.ok_or(())?, + ); + } + output.insert( + "enable_throttling".to_owned(), + json!(match input.get("enable_throttling") { + Some(value) => security_setting_bool(value).ok_or(())?, + None => false, + }), + ); + Ok(Value::Object(output)) +} + +fn tier_model_features(value: Option<&Value>, default: bool) -> Result, ()> { + let Some(value) = value else { + return Ok(default.then(|| json!([]))); + }; + if value.is_null() { + return Ok(None); + } + let values = value.as_array().ok_or(())?; + let values = values + .iter() + .map(|value| security_setting_string(value).map(Value::String).ok_or(())) + .collect::, _>>()?; + Ok(Some(Value::Array(values))) +} + +fn insert_tier_optional(output: &mut Map, field: &str, value: Option) { + if let Some(value) = value { + output.insert(field.to_owned(), value); + } +} + +fn normalize_tier_create_or_update(payload: &Value, create: bool) -> Result { + let input = payload.as_object().ok_or(())?; + let mut output = Map::new(); + for field in ["tier_id", "name", "display_name"] { + if create || input.contains_key(field) { + insert_tier_optional( + &mut output, + field, + tier_model_string(input.get(field), create)?, + ); + } + } + if let Some(value) = tier_model_string(input.get("description"), false)? { + output.insert("description".to_owned(), value); + } + if create || input.contains_key("limits") { + match input.get("limits") { + Some(Value::Null) if !create => {} + Some(value) => { + output.insert("limits".to_owned(), normalize_tier_limits(value)?); + } + None if create => return Err(()), + None => {} + } + } + for field in ["price_monthly", "price_yearly"] { + insert_tier_optional(&mut output, field, tier_model_float(input.get(field))?); + } + insert_tier_optional( + &mut output, + "features", + tier_model_features(input.get("features"), create)?, + ); + for (field, default) in [("is_default", false), ("enabled", true)] { + match input.get(field) { + Some(Value::Null) if !create => {} + Some(value) => { + output.insert( + field.to_owned(), + json!(security_setting_bool(value).ok_or(())?), + ); + } + None if create => { + output.insert(field.to_owned(), json!(default)); + } + None => {} + } + } + Ok(Value::Object(output)) +} + +fn tier_action_string( + input: &Map, + output: &mut Map, + field: &str, + required: bool, +) -> Result<(), ()> { + insert_tier_optional( + output, + field, + tier_model_string(input.get(field), required)?, + ); + Ok(()) +} + +fn normalize_tier_request(path: &str, method: &Method, payload: &mut Value) -> Result<(), ()> { + if path == "/tiers/compare" { + let input = payload.as_array().ok_or(())?; + *payload = Value::Array( + input + .iter() + .map(|value| security_setting_string(value).map(Value::String).ok_or(())) + .collect::, _>>()?, + ); + return Ok(()); + } + if matches!(path, "/tiers" | "/tiers/") { + *payload = normalize_tier_create_or_update(payload, true)?; + return Ok(()); + } + if method == Method::PUT { + *payload = normalize_tier_create_or_update(payload, false)?; + return Ok(()); + } + + let input = payload.as_object().ok_or(())?; + let mut output = Map::new(); + tier_action_string(input, &mut output, "user_id", true)?; + match path { + "/tiers/assignments" => { + tier_action_string(input, &mut output, "tier_id", true)?; + for field in ["effective_from", "effective_until"] { + if let Some(value) = input.get(field).filter(|value| !value.is_null()) { + output.insert(field.to_owned(), tier_model_datetime(value).ok_or(())?); + } + } + if let Some(value) = input + .get("override_limits") + .filter(|value| !value.is_null()) + { + output.insert("override_limits".to_owned(), normalize_tier_limits(value)?); + } + tier_action_string(input, &mut output, "notes", false)?; + } + "/tiers/upgrade" => { + tier_action_string(input, &mut output, "new_tier_id", true)?; + output.insert( + "immediate".to_owned(), + json!(match input.get("immediate") { + Some(value) => security_setting_bool(value).ok_or(())?, + None => true, + }), + ); + if let Some(value) = input.get("scheduled_date").filter(|value| !value.is_null()) { + output.insert( + "scheduled_date".to_owned(), + tier_model_datetime(value).ok_or(())?, + ); + } + } + "/tiers/downgrade" => { + tier_action_string(input, &mut output, "new_tier_id", true)?; + output.insert( + "grace_period_days".to_owned(), + tier_model_integer(input.get("grace_period_days"), Some(0))?.ok_or(())?, + ); + } + "/tiers/temporary-upgrade" => { + tier_action_string(input, &mut output, "temp_tier_id", true)?; + let duration = input + .get("duration_days") + .filter(|value| !value.is_null()) + .and_then(rate_rule_integer) + .ok_or(())?; + output.insert("duration_days".to_owned(), json!(duration)); + } + "/tiers/trial/start" => { + tier_action_string(input, &mut output, "tier_id", true)?; + output.insert( + "days".to_owned(), + tier_model_integer(input.get("days"), Some(14))?.ok_or(())?, + ); + } + "/tiers/payment/failure" => { + tier_action_string(input, &mut output, "reason", false)?; + } + _ => return Err(()), + } + *payload = Value::Object(output); + Ok(()) +} + fn tier_limits_response(limits: &Value) -> Value { let mut response = limits.clone(); for field in [ @@ -1333,6 +1937,19 @@ fn tier_response(tier: Value) -> Value { }) } +fn tier_assignment_response(assignment: Value) -> Value { + json!({ + "user_id": assignment.get("user_id").cloned().unwrap_or(Value::Null), + "tier_id": assignment.get("tier_id").cloned().unwrap_or(Value::Null), + "override_limits": assignment.get("override_limits").cloned().unwrap_or(Value::Null), + "effective_from": assignment.get("effective_from").cloned().unwrap_or(Value::Null), + "effective_until": assignment.get("effective_until").cloned().unwrap_or(Value::Null), + "assigned_at": assignment.get("assigned_at").cloned().unwrap_or(Value::Null), + "assigned_by": assignment.get("assigned_by").cloned().unwrap_or(Value::Null), + "notes": assignment.get("notes").cloned().unwrap_or(Value::Null), + }) +} + async fn tier_management_routes( state: &AppState, path: &str, @@ -1342,7 +1959,10 @@ async fn tier_management_routes( username: &str, request_id: &str, ) -> Response { - if !has_permission(state, username, "manage_tiers").await { + let suffix = path.trim_start_matches("/tiers/"); + let protected_statistics = + method == Method::GET && (suffix == "statistics/all" || suffix.ends_with("/statistics")); + if protected_statistics && !has_permission(state, username, "manage_tiers").await { return error( StatusCode::FORBIDDEN, "TIER001", @@ -1353,7 +1973,6 @@ async fn tier_management_routes( let Some(storage) = &state.storage else { return unexpected(request_id); }; - let suffix = path.trim_start_matches("/tiers/"); if suffix == "assignments" && method == Method::GET { let items = storage .find_many("user_tier_assignments", &json!({})) @@ -1383,13 +2002,14 @@ async fn tier_management_routes( request_id, ); } - if storage + let tier_exists = match storage .find_one("tiers", &json!({"tier_id": &tier_id})) .await - .ok() - .flatten() - .is_none() { + Ok(tier) => tier.is_some(), + Err(_) => return tier_detail_failure("assign", request_id), + }; + if !tier_exists { // Oracle: tier_service.assign_user_to_tier raises ValueError here, // which the FastAPI route exposes as a 400 detail response. return http_detail( @@ -1398,11 +2018,13 @@ async fn tier_management_routes( request_id, ); } - let existing = storage + let existing = match storage .find_one("user_tier_assignments", &json!({"user_id": &user_id})) .await - .ok() - .flatten(); + { + Ok(existing) => existing, + Err(_) => return tier_detail_failure("assign", request_id), + }; // Match UserTierAssignment.to_dict(), including explicit nulls. In // particular, this is a replacement on reassignment: fields omitted // from the new request must not survive from the previous assignment. @@ -1433,7 +2055,7 @@ async fn tier_management_routes( }; return match result { Ok(()) => success(StatusCode::CREATED, assignment, request_id), - Err(_) => unexpected(request_id), + Err(_) => tier_detail_failure("assign", request_id), }; } if let Some(rest) = suffix.strip_prefix("assignments/") { @@ -1443,20 +2065,32 @@ async fn tier_management_routes( .delete_one("user_tier_assignments", &json!({"user_id": user_id})) .await { - Ok(true) => message(StatusCode::OK, "Assignment removed", request_id), - _ => http_detail( + Ok(true) => response_model_envelope("Assignment removed", request_id), + Ok(false) => http_detail( StatusCode::NOT_FOUND, &format!("No assignment found for user {user_id}"), request_id, ), + Err(_) => tier_detail_failure("remove_assignment", request_id), }; } if method == Method::GET { - let assignment = storage + let assignment = match storage .find_one("user_tier_assignments", &json!({"user_id": user_id})) .await - .ok() - .flatten(); + { + Ok(assignment) => assignment, + Err(_) => { + return tier_detail_failure( + if rest.ends_with("/tier") { + "get_user_tier" + } else { + "get_assignment" + }, + request_id, + ); + } + }; if rest.ends_with("/tier") { // UserTierAssignment.to_dict serializes datetimes, while the // pinned dataclass loader leaves those strings unparsed. @@ -1491,20 +2125,20 @@ async fn tier_management_routes( .await }; return match tier { - Ok(Some(tier)) => success(StatusCode::OK, strip_internal(tier), request_id), - _ => http_detail( + Ok(Some(tier)) => success(StatusCode::OK, tier_response(tier), request_id), + Ok(None) => http_detail( StatusCode::NOT_FOUND, &format!("No tier found for user {user_id}"), request_id, ), + Err(_) => tier_detail_failure("get_user_tier", request_id), }; } return match assignment { - Some(value) => success(StatusCode::OK, strip_internal(value), request_id), - None => error( + Some(value) => success(StatusCode::OK, tier_assignment_response(value), request_id), + None => http_detail( StatusCode::NOT_FOUND, - "TIER404", - "Assignment not found", + &format!("No assignment found for user {user_id}"), request_id, ), }; @@ -1512,34 +2146,37 @@ async fn tier_management_routes( } if suffix.ends_with("/users") && method == Method::GET { let tier_id = suffix.trim_end_matches("/users"); - let skip = query - .get("skip") - .and_then(|value| value.parse::().ok()) - .unwrap_or(0); - let limit = query - .get("limit") - .and_then(|value| value.parse::().ok()) - .unwrap_or(100); - let users = storage + let skip = match tier_pagination(query, "skip", 0, 0, usize::MAX) { + Ok(value) => value, + Err(()) => return tier_pagination_error("skip", request_id), + }; + let limit = match tier_pagination(query, "limit", 100, 1, 1_000) { + Ok(value) => value, + Err(()) => return tier_pagination_error("limit", request_id), + }; + let users = match storage .find_many("user_tier_assignments", &json!({"tier_id": tier_id})) .await - .unwrap_or_default() - .into_iter() - .skip(skip) - .take(limit) - .map(strip_internal) - .collect::>(); + { + Ok(users) => users, + Err(_) => return tier_detail_failure("list_users", request_id), + } + .into_iter() + .skip(skip) + .take(limit) + .map(tier_assignment_response) + .collect::>(); return success(StatusCode::OK, json!(users), request_id); } if suffix == "statistics/all" && method == Method::GET { - let tiers = storage - .find_many("tiers", &json!({})) - .await - .unwrap_or_default(); - let assignments = storage - .find_many("user_tier_assignments", &json!({})) - .await - .unwrap_or_default(); + let tiers = match storage.find_many("tiers", &json!({})).await { + Ok(tiers) => tiers, + Err(_) => return tier_statistics_failure(request_id), + }; + let assignments = match storage.find_many("user_tier_assignments", &json!({})).await { + Ok(assignments) => assignments, + Err(_) => return tier_statistics_failure(request_id), + }; let result: Vec = tiers .into_iter() .map(|tier| { @@ -1576,10 +2213,13 @@ async fn tier_management_routes( } if suffix.ends_with("/statistics") && method == Method::GET { let tier_id = suffix.trim_end_matches("/statistics"); - let assignments = storage + let assignments = match storage .find_many("user_tier_assignments", &json!({"tier_id": tier_id})) .await - .unwrap_or_default(); + { + Ok(assignments) => assignments, + Err(_) => return tier_statistics_failure(request_id), + }; let total_users = assignments.len(); let active_users = assignments .iter() @@ -1599,23 +2239,20 @@ async fn tier_management_routes( ); } if suffix == "compare" && method == Method::POST { - let ids = payload - .as_array() - .cloned() - .or_else(|| { - payload - .get("tier_ids") - .or_else(|| payload.get("tiers")) - .and_then(Value::as_array) - .cloned() + let ids = payload.as_array().cloned().unwrap_or_default(); + let stored = match storage.find_many("tiers", &json!({})).await { + Ok(tiers) => tiers, + Err(_) => return tier_detail_failure("compare", request_id), + }; + // TierService loops over the requested identifiers, so input order and + // duplicates are observable and must not be replaced by collection order. + let tiers = ids + .iter() + .filter_map(|tier_id| { + stored + .iter() + .find(|tier| tier.get("tier_id") == Some(tier_id)) }) - .unwrap_or_default(); - let tiers = storage - .find_many("tiers", &json!({})) - .await - .unwrap_or_default() - .into_iter() - .filter(|tier| ids.contains(tier.get("tier_id").unwrap_or(&Value::Null))) .map(|tier| { json!({ "tier_id": tier.get("tier_id").cloned().unwrap_or(Value::Null), @@ -1649,7 +2286,7 @@ async fn tier_management_routes( let serialized_effective_dates = match tier_assignment_has_serialized_effective_dates(storage, user_id).await { Ok(value) => value, - Err(_) => return unexpected(request_id), + Err(_) => return tier_detail_failure(suffix, request_id), }; if serialized_effective_dates { let detail = match suffix { @@ -1661,6 +2298,14 @@ async fn tier_management_routes( return http_detail(StatusCode::INTERNAL_SERVER_ERROR, detail, request_id); } } + let current_tier_id = if matches!(suffix, "upgrade" | "downgrade" | "payment/failure") { + match tier_action_current_tier_id(storage, user_id).await { + Ok(tier_id) => tier_id.unwrap_or_else(|| "default".to_owned()), + Err(_) => return tier_detail_failure(suffix, request_id), + } + } else { + "default".to_owned() + }; let (tier_id, effective_from, effective_until, notes, assigned_by, missing_status) = match suffix { "upgrade" => { @@ -1688,14 +2333,7 @@ async fn tier_management_routes( tier_id.to_owned(), effective_from, Value::Null, - format!( - "Upgraded from {}", - tier_action_current_tier_id(storage, user_id) - .await - .ok() - .flatten() - .unwrap_or_else(|| "default".to_owned()) - ), + format!("Upgraded from {current_tier_id}"), Value::Null, StatusCode::BAD_REQUEST, ) @@ -1719,12 +2357,7 @@ async fn tier_management_routes( Value::String(effective_from), Value::Null, format!( - "Downgraded from {} with {grace_days} day grace period", - tier_action_current_tier_id(storage, user_id) - .await - .ok() - .flatten() - .unwrap_or_else(|| "default".to_owned()) + "Downgraded from {current_tier_id} with {grace_days} day grace period" ), Value::Null, StatusCode::BAD_REQUEST, @@ -1778,7 +2411,7 @@ async fn tier_management_routes( request_id, ); } - Err(_) => return unexpected(request_id), + Err(_) => return tier_detail_failure(suffix, request_id), }; let Some(tier_id) = default_tier.get("tier_id").and_then(Value::as_str) else { return http_detail( @@ -1791,14 +2424,7 @@ async fn tier_management_routes( tier_id.to_owned(), Value::String(timestamp_after_days(0)), Value::Null, - format!( - "Downgraded from {} with 0 day grace period", - tier_action_current_tier_id(storage, user_id) - .await - .ok() - .flatten() - .unwrap_or_else(|| "default".to_owned()) - ), + format!("Downgraded from {current_tier_id} with 0 day grace period"), json!("system:payment_failure"), StatusCode::BAD_REQUEST, ) @@ -1817,9 +2443,9 @@ async fn tier_management_routes( request_id, ); } - Err(_) => return unexpected(request_id), + Err(_) => return tier_detail_failure(suffix, request_id), }; - let _ = tier; + let notification_effective_from = effective_from.clone(); return match replace_tier_assignment( storage, user_id, @@ -1834,8 +2460,18 @@ async fn tier_management_routes( ) .await { - Ok(assignment) => success(StatusCode::OK, assignment, request_id), - Err(_) => unexpected(request_id), + Ok(assignment) => { + send_tier_notification( + suffix, + user_id, + &tier, + ¬ification_effective_from, + &payload, + ) + .await; + success(StatusCode::OK, assignment, request_id) + } + Err(_) => tier_detail_failure(suffix, request_id), }; } error( @@ -1846,6 +2482,62 @@ async fn tier_management_routes( ) } +async fn send_tier_notification( + action: &str, + user_id: &str, + tier: &Value, + effective_from: &Value, + payload: &Value, +) { + let display_name = tier + .get("display_name") + .and_then(Value::as_str) + .unwrap_or_default(); + let effective = effective_from.as_str().unwrap_or_default(); + let recipient = format!("{user_id}@example.com"); + let (subject, body) = match action { + "upgrade" => ( + "Tier Upgrade Notification", + format!("You have been upgraded to the {display_name} plan.\nEffective: {effective}"), + ), + "downgrade" => ( + "Tier Downgrade Notification", + format!("You have been downgraded to the {display_name} plan.\nEffective: {effective}"), + ), + "temporary-upgrade" | "trial/start" => { + let days = payload + .get(if action == "temporary-upgrade" { + "duration_days" + } else { + "days" + }) + .and_then(Value::as_i64) + .unwrap_or(if action == "temporary-upgrade" { 0 } else { 14 }); + ( + "Trial Activated", + format!("You have been given a temporary trial of {display_name} for {days} days."), + ) + } + "payment/failure" => ( + "Tier Downgrade Notification", + format!("You have been downgraded to the {display_name} plan.\nEffective: {effective}"), + ), + _ => return, + }; + crate::observability::email::send_email(&recipient, subject, &body, None).await; + if action == "payment/failure" { + crate::observability::email::send_email( + &recipient, + "Payment Failure - Account Downgraded", + &format!( + "We could not process your payment. Your account has been downgraded to {display_name}." + ), + None, + ) + .await; + } +} + async fn tier_action_current_tier_id( storage: &SharedStorage, user_id: &str, @@ -1929,7 +2621,7 @@ async fn rate_limit_management_routes( return http_detail(StatusCode::NOT_FOUND, "Rule status not found", request_id); } if suffix == "search" && method == Method::GET { - let Some(term) = query.get("q").map(|value| value.to_ascii_lowercase()) else { + let Some(term) = query.get("q") else { return rate_rule_validation("q", "field required", "value_error.missing", request_id); }; // The pinned async in-memory backend does not evaluate the Mongo @@ -1938,6 +2630,13 @@ async fn rate_limit_management_routes( if storage.is_memory() { return success(StatusCode::OK, json!([]), request_id); } + let pattern = match regex::RegexBuilder::new(term) + .case_insensitive(true) + .build() + { + Ok(pattern) => pattern, + Err(_) => return rate_rule_failure("search", request_id), + }; let mut rules = match storage.find_many("rate_limit_rules", &json!({})).await { Ok(rules) => rules, Err(_) => { @@ -1955,7 +2654,7 @@ async fn rate_limit_management_routes( ["rule_id", "description", "target_identifier"] .iter() .filter_map(|field| rule.get(*field).and_then(Value::as_str)) - .any(|field| field.to_ascii_lowercase().contains(&term)) + .any(|field| pattern.is_match(field)) }); rules.sort_by_key(|rule| { std::cmp::Reverse(rule.get("priority").and_then(Value::as_i64).unwrap_or(0)) @@ -1969,11 +2668,11 @@ async fn rate_limit_management_routes( if suffix == "statistics/summary" && method == Method::GET { let rules = match storage.find_many("rate_limit_rules", &json!({})).await { Ok(rules) => rules, - Err(_) => return unexpected(request_id), + Err(_) => return rate_rule_failure("statistics", request_id), }; let enabled = rules .iter() - .filter(|rule| rule.get("enabled").and_then(Value::as_bool).unwrap_or(true)) + .filter(|rule| rule.get("enabled").and_then(Value::as_bool) == Some(true)) .count(); let rule_types = [ "per_user", @@ -2016,14 +2715,53 @@ async fn rate_limit_management_routes( && matches!(operation, "delete" | "enable" | "disable") { // The pinned Python app's bulk routes are internally inconsistent: - // `/bulk/enable` and `/bulk/disable` are shadowed by `{rule_id}` and - // fail as a missing rule, while its delete service raises a 500. - // Preserve the observed wire behavior instead of implementing the - // advertised OpenAPI success responses. + // `/bulk/enable` and `/bulk/disable` are shadowed by `{rule_id}`. The + // in-memory backend lacks delete_many, so non-empty bulk deletes fail; + // its early empty-list return and external-storage success remain live. return if operation == "delete" { - http_detail( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to delete rules", + let Some(rule_ids) = payload.get("rule_ids").and_then(Value::as_array) else { + return rate_rule_validation( + "rule_ids", + "field required", + "value_error.missing", + request_id, + ); + }; + let rule_ids = match rule_ids + .iter() + .map(|value| security_setting_string(value).ok_or(())) + .collect::, _>>() + { + Ok(rule_ids) => rule_ids, + Err(()) => { + return rate_rule_validation( + "rule_ids", + "str type expected", + "type_error.str", + request_id, + ); + } + }; + if rule_ids.is_empty() { + return success(StatusCode::OK, json!({"deleted_count": 0}), request_id); + } + if storage.is_memory() { + return rate_rule_failure("bulk/delete", request_id); + } + let mut deleted_count = 0; + for rule_id in rule_ids { + match storage + .delete_one("rate_limit_rules", &json!({"rule_id": rule_id})) + .await + { + Ok(true) => deleted_count += 1, + Ok(false) => {} + Err(_) => return rate_rule_failure("bulk/delete", request_id), + } + } + success( + StatusCode::OK, + json!({"deleted_count": deleted_count}), request_id, ) } else { @@ -2051,46 +2789,46 @@ async fn rate_limit_management_routes( &format!("Rule {id} not found"), request_id, ), - Err(_) => unexpected(request_id), + Err(_) => rate_rule_failure(parts[1], request_id), }; } if parts[1] == "duplicate" { - let Some(mut rule) = storage + let mut rule = match storage .find_one("rate_limit_rules", &json!({"rule_id": id})) .await - .ok() - .flatten() - else { - return error( - StatusCode::NOT_FOUND, - "RATE404", - "Rule not found", - request_id, - ); + { + Ok(Some(rule)) => rule, + Ok(None) => { + return http_detail( + StatusCode::BAD_REQUEST, + &format!("Source rule {id} not found"), + request_id, + ); + } + Err(_) => return rate_rule_failure("duplicate", request_id), }; - let Some(new_rule_id) = payload - .get("new_rule_id") - .and_then(Value::as_str) - .filter(|value| !value.is_empty()) + let Some(new_rule_id) = payload.get("new_rule_id").and_then(security_setting_string) else { - return validation_errors( - vec![ - json!({"loc": ["body", "new_rule_id"], "msg": "field required", "type": "value_error.missing"}), - ], + return rate_rule_validation( + "new_rule_id", + "field required", + "value_error.missing", request_id, ); }; - if matches!( - storage - .find_one("rate_limit_rules", &json!({"rule_id": new_rule_id})) - .await, - Ok(Some(_)) - ) { - return http_detail( - StatusCode::BAD_REQUEST, - &format!("Rule with ID {new_rule_id} already exists"), - request_id, - ); + match storage + .find_one("rate_limit_rules", &json!({"rule_id": new_rule_id})) + .await + { + Ok(Some(_)) => { + return http_detail( + StatusCode::BAD_REQUEST, + &format!("Rule with ID {new_rule_id} already exists"), + request_id, + ); + } + Ok(None) => {} + Err(_) => return rate_rule_failure("duplicate", request_id), } rule["rule_id"] = json!(new_rule_id); rule["description"] = json!(format!("Copy of {id}")); @@ -2101,7 +2839,7 @@ async fn rate_limit_management_routes( } return match storage.insert_one("rate_limit_rules", rule).await { Ok(rule) => success(StatusCode::CREATED, rate_rule_response(rule), request_id), - Err(_) => unexpected(request_id), + Err(_) => rate_rule_failure("duplicate", request_id), }; } } @@ -2129,11 +2867,25 @@ async fn rate_limit_crud_routes( if suffix.is_empty() && method == Method::GET { let skip = match tier_pagination(query, "skip", 0, 0, usize::MAX) { Ok(value) => value, - Err(()) => return tier_pagination_error("skip", request_id), + Err(()) => { + return rate_rule_validation( + "skip", + "value is not valid", + "value_error", + request_id, + ); + } }; let limit = match tier_pagination(query, "limit", 100, 1, 1_000) { Ok(value) => value, - Err(()) => return tier_pagination_error("limit", request_id), + Err(()) => { + return rate_rule_validation( + "limit", + "value is not valid", + "value_error", + request_id, + ); + } }; let rule_type = query.get("rule_type").map(String::as_str); if rule_type.is_some_and(|rule_type| { @@ -2154,12 +2906,23 @@ async fn rate_limit_crud_routes( request_id, ); } - let enabled_only = query - .get("enabled_only") - .is_some_and(|value| value.eq_ignore_ascii_case("true")); + let enabled_only = match query.get("enabled_only") { + Some(value) => match security_setting_bool(&json!(value)) { + Some(value) => value, + None => { + return rate_rule_validation( + "enabled_only", + "value could not be parsed to a boolean", + "type_error.bool", + request_id, + ); + } + }, + None => false, + }; let mut rules = match storage.find_many("rate_limit_rules", &json!({})).await { Ok(rules) => rules, - Err(_) => return unexpected(request_id), + Err(_) => return rate_rule_failure("list", request_id), }; rules.sort_by_key(|rule| { std::cmp::Reverse(rule.get("priority").and_then(Value::as_i64).unwrap_or(0)) @@ -2178,11 +2941,15 @@ async fn rate_limit_crud_routes( return success(StatusCode::OK, json!(rules), request_id); } if suffix.is_empty() && method == Method::POST { - let Some(rule_id) = payload - .get("rule_id") - .and_then(security_setting_string) - .filter(|value| !value.is_empty()) - else { + if !payload.is_object() { + return rate_rule_validation( + "body", + "value is not a valid dict", + "type_error.dict", + request_id, + ); + } + let Some(rule_id) = payload.get("rule_id").and_then(security_setting_string) else { return rate_rule_validation( "rule_id", "field required", @@ -2335,17 +3102,19 @@ async fn rate_limit_crud_routes( request_id, ); } - if matches!( - storage - .find_one("rate_limit_rules", &json!({"rule_id": rule_id})) - .await, - Ok(Some(_)) - ) { - return http_detail( - StatusCode::BAD_REQUEST, - &format!("Rule with ID {rule_id} already exists"), - request_id, - ); + match storage + .find_one("rate_limit_rules", &json!({"rule_id": rule_id})) + .await + { + Ok(Some(_)) => { + return http_detail( + StatusCode::BAD_REQUEST, + &format!("Rule with ID {rule_id} already exists"), + request_id, + ); + } + Ok(None) => {} + Err(_) => return rate_rule_failure("create", request_id), } let rule = json!({ "rule_id": rule_id, @@ -2362,7 +3131,7 @@ async fn rate_limit_crud_routes( }); return match storage.insert_one("rate_limit_rules", rule.clone()).await { Ok(_) => success(StatusCode::CREATED, rate_rule_response(rule), request_id), - Err(_) => unexpected(request_id), + Err(_) => rate_rule_failure("create", request_id), }; } if suffix.is_empty() { @@ -2384,10 +3153,18 @@ async fn rate_limit_crud_routes( &format!("Rule {suffix} not found"), request_id, ), - Err(_) => unexpected(request_id), + Err(_) => rate_rule_failure("get", request_id), }; } if method == Method::PUT { + if !payload.is_object() { + return rate_rule_validation( + "body", + "value is not a valid dict", + "type_error.dict", + request_id, + ); + } let mut updates = Value::Object(Map::new()); if let Some(value) = payload.get("limit").filter(|value| !value.is_null()) { let Some(value) = rate_rule_integer(value).filter(|value| *value > 0) else { @@ -2472,7 +3249,7 @@ async fn rate_limit_crud_routes( &format!("Rule {suffix} not found"), request_id, ), - Err(_) => unexpected(request_id), + Err(_) => rate_rule_failure("update", request_id), }; } if method == Method::DELETE { @@ -2490,7 +3267,7 @@ async fn rate_limit_crud_routes( &format!("Rule {suffix} not found"), request_id, ), - Err(_) => unexpected(request_id), + Err(_) => rate_rule_failure("delete", request_id), }; } error( @@ -2517,6 +3294,27 @@ fn rate_rule_validation( ) } +fn rate_rule_failure(operation: &str, request_id: &str) -> Response { + let detail = match operation { + "create" => "Failed to create rule", + "list" => "Failed to list rules", + "search" => "Failed to search rules", + "get" => "Failed to get rule", + "update" => "Failed to update rule", + "delete" => "Failed to delete rule", + "enable" => "Failed to enable rule", + "disable" => "Failed to disable rule", + "bulk/delete" => "Failed to delete rules", + "bulk/enable" => "Failed to enable rules", + "bulk/disable" => "Failed to disable rules", + "duplicate" => "Failed to duplicate rule", + "statistics" => "Failed to get statistics", + "status" => "Failed to get rate limit status", + _ => "An unexpected error occurred", + }; + http_detail(StatusCode::INTERNAL_SERVER_ERROR, detail, request_id) +} + fn rate_rule_integer(value: &Value) -> Option { match value { Value::Number(value) => value.as_i64().or_else(|| { @@ -2579,40 +3377,111 @@ async fn entity_routes( request_id, ); } - if method == Method::PUT && payload.as_object().is_some_and(|object| object.is_empty()) { - let (code, message_text) = match spec.collection { - "roles" => ("ROLE007", "No data to update"), - "groups" => ("GRP006", "No data to update"), - "routings" => ("RTG007", "No data to update"), - _ => ("VAL001", "No data to update"), - }; - return error(StatusCode::BAD_REQUEST, code, message_text, request_id); - } } if method == Method::GET && (suffix.is_empty() || suffix == "all") { - if !has_permission(state, username, spec.permission).await { + // The pinned get_groups only authenticates; get_routings requires + // manage_routings (RTG012). + if spec.collection == "routings" && !has_permission(state, username, spec.permission).await + { return error( StatusCode::FORBIDDEN, - spec.permission_code, - "Insufficient permissions", + "RTG012", + "You do not have permission to get routings", request_id, ); } - if let Err(message_text) = validate_pagination(query) { - return error(StatusCode::BAD_REQUEST, "PAG001", message_text, request_id); + if spec.collection != "roles" { + // Python's `get_groups`/`get_routings` type `page`/`page_size` as + // typed FastAPI query params (a non-numeric value never reaches the + // handler and produces FastAPI's own 422), matching roles above. + if let Some(response) = + int_query_validation_error(query, &["page", "page_size"], request_id) + { + return response; + } + if let Err(message_text) = validate_pagination(query) { + return error(StatusCode::BAD_REQUEST, "PAG001", &message_text, request_id); + } } - let items = match storage.find_many(spec.collection, &json!({})).await { + let mut items = match storage.find_many(spec.collection, &json!({})).await { Ok(items) => items.into_iter().map(strip_internal).collect::>(), Err(_) => return unexpected(request_id), }; - let items = if spec.collection == "roles" && !is_admin_user(state, username).await { - items - .into_iter() - .filter(|role| role.get("role_name").and_then(Value::as_str) != Some("admin")) - .collect() - } else { - items - }; + if matches!(spec.collection, "roles" | "groups" | "routings") { + // Python's `get_roles(page: int = ..., page_size: int = ...)` are + // typed FastAPI query params: a non-numeric value never reaches the + // handler and instead produces FastAPI's own 422 validation body. + // Unlike the other collections above, this branch previously parsed + // page/page_size with `.unwrap_or(1)`, silently accepting garbage. + if let Some(response) = + int_query_validation_error(query, &["page", "page_size"], request_id) + { + return response; + } + if spec.collection == "roles" { + items.sort_by_key(|role| { + role.get("role_name") + .and_then(Value::as_str) + .unwrap_or_default() + .to_owned() + }); + } + let page = query + .get("page") + .and_then(|value| value.parse::().ok()) + .unwrap_or(1); + let page_size = query + .get("page_size") + .and_then(|value| value.parse::().ok()) + .unwrap_or(10); + let maximum = env::var("MAX_PAGE_SIZE") + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(200); + if page == 0 || page_size == 0 { + return error( + StatusCode::BAD_REQUEST, + "PAG001", + "Invalid page or page size", + request_id, + ); + } + if page_size > maximum { + return error( + StatusCode::BAD_REQUEST, + "PAG001", + "Page size exceeds maximum limit", + request_id, + ); + } + let total = items.len(); + let start = (page - 1).saturating_mul(page_size); + items = items.into_iter().skip(start).take(page_size).collect(); + if spec.collection == "roles" && !is_admin_user(state, username).await { + let mut visible = Vec::new(); + for mut role in items { + let role_name = role + .get("role_name") + .and_then(Value::as_str) + .unwrap_or_default(); + if is_admin_role(state, role_name).await { + continue; + } + role.as_object_mut() + .map(|role| role.remove("platform_admin")); + visible.push(role); + } + // The pinned route replaces the page with just the filtered roles. + return success(StatusCode::OK, json!({"roles": visible}), request_id); + } + return success( + StatusCode::OK, + json!({spec.collection: items, "page": page, "page_size": page_size, + "has_next": total > start.saturating_add(page_size), "total": total}), + request_id, + ); + } let payload = match spec.list_key { Some(list_key) => paginate_named(items, query, list_key), None => paginate(items, query), @@ -2628,7 +3497,11 @@ async fn entity_routes( } else { spec.permission_code }, - "Insufficient permissions", + if spec.collection == "roles" { + "You do not have permission to create roles" + } else { + "Insufficient permissions" + }, request_id, ); } @@ -2647,11 +3520,17 @@ async fn entity_routes( request_id, ); }; - if spec.collection == "roles" && key == "admin" && !is_admin_user(state, username).await { + if spec.collection == "roles" + && matches!( + key.trim().to_ascii_lowercase().as_str(), + "admin" | "platform admin" + ) + && !is_admin_user(state, username).await + { return error( StatusCode::FORBIDDEN, - "ROLE009", - "Only an administrator can create the administrator role", + "ROLE013", + "Only admin may create the admin role", request_id, ); } @@ -2664,17 +3543,11 @@ async fn entity_routes( return error( StatusCode::BAD_REQUEST, spec.duplicate_code, - "Resource already exists", - request_id, - ); - } - if spec.collection == "roles" - && !role_permissions_within_actor(state, username, &payload).await - { - return error( - StatusCode::FORBIDDEN, - "ROLE009", - "Roles cannot grant permissions you do not hold", + if spec.collection == "roles" { + "Role already exists" + } else { + "Resource already exists" + }, request_id, ); } @@ -2708,7 +3581,11 @@ async fn entity_routes( Err(duplicate_error) if duplicate_error.is_duplicate_key() => error( StatusCode::BAD_REQUEST, spec.duplicate_code, - "Resource already exists", + if spec.collection == "roles" { + "Role already exists" + } else { + "Resource already exists" + }, request_id, ), Err(_) => unexpected(request_id), @@ -2725,32 +3602,44 @@ async fn entity_routes( } let filter = json!({spec.key: key}); if method == Method::GET { - if !has_permission(state, username, spec.permission).await { + // The pinned get_group only authenticates; get_routing needs manage_routings. + if spec.collection == "routings" && !has_permission(state, username, spec.permission).await + { return error( StatusCode::FORBIDDEN, - if spec.collection == "routings" { - "RTG013" - } else { - spec.permission_code - }, - "Insufficient permissions", + "RTG013", + "You do not have permission to get routings", request_id, ); } - if spec.collection == "roles" && key == "admin" && !is_admin_user(state, username).await { - return error( + if spec.collection == "roles" + && is_admin_role(state, key).await + && !is_admin_user(state, username).await + { + return json_response( StatusCode::NOT_FOUND, - spec.not_found_code, - "Resource not found", + json!({"error_message": "Role not found"}), request_id, ); } return match storage.find_one(spec.collection, &filter).await { - Ok(Some(item)) => success(StatusCode::OK, strip_internal(item), request_id), + Ok(Some(mut item)) => { + if spec.collection == "roles" && !is_admin_user(state, username).await { + item.as_object_mut() + .map(|role| role.remove("platform_admin")); + } + success(StatusCode::OK, strip_internal(item), request_id) + } Ok(None) => error( - StatusCode::NOT_FOUND, + // Python's routing retrieval also returns 400 (RTG004) for a + // missing client_key, unlike groups/roles which use 404 here. + if spec.collection == "routings" { + StatusCode::BAD_REQUEST + } else { + StatusCode::NOT_FOUND + }, spec.not_found_code, - "Resource not found", + not_found_message(spec.collection), request_id, ), Err(_) => unexpected(request_id), @@ -2765,46 +3654,84 @@ async fn entity_routes( } else { "RTG011" } + } else if spec.collection == "roles" { + if method == Method::PUT { + "ROLE010" + } else { + "ROLE011" + } + } else if spec.collection == "groups" { + if method == Method::PUT { + "GRP009" + } else { + "GRP010" + } } else { "AUTH006" }, - "Insufficient permissions", + match (spec.collection, method == Method::PUT) { + ("roles", true) => "You do not have permission to update roles", + ("roles", false) => "You do not have permission to delete roles", + ("groups", true) => "You do not have permission to update groups", + ("groups", false) => "You do not have permission to delete groups", + _ => "Insufficient permissions", + }, + request_id, + ); + } + // Python's update/delete service methods for groups and routings return + // 400 (not 404) when the target doesn't exist, matching the roles + // convention below rather than the generic 404 used elsewhere. + if matches!(spec.collection, "groups" | "routings") + && matches!(storage.find_one(spec.collection, &filter).await, Ok(None)) + { + return error( + StatusCode::BAD_REQUEST, + spec.not_found_code, + not_found_message(spec.collection), request_id, ); } if spec.collection == "roles" { - let existing = match storage.find_one(spec.collection, &filter).await { - Ok(Some(role)) => role, + match storage.find_one(spec.collection, &filter).await { + Ok(Some(_)) => {} Ok(None) => { return error( - StatusCode::NOT_FOUND, + StatusCode::BAD_REQUEST, spec.not_found_code, - "Resource not found", + "Role does not exist", request_id, ); } Err(_) => return unexpected(request_id), - }; - let actor_is_admin = is_admin_user(state, username).await; - let bootstrap_admin_restores_manage_users = key == "admin" - && actor_is_admin - && payload.as_object().is_some_and(|updates| { - updates.len() == 1 - && updates.get("manage_users").and_then(Value::as_bool) == Some(true) - }); - let actor_can_modify = actor_is_admin - || bootstrap_admin_restores_manage_users - || (role_permissions_within_actor(state, username, &existing).await - && role_permissions_within_actor(state, username, &payload).await); - if (key == "admin" && !actor_is_admin) || !actor_can_modify { + } + if is_admin_role(state, key).await && !is_admin_user(state, username).await { return error( StatusCode::FORBIDDEN, - "ROLE009", - "Roles cannot grant or modify permissions you do not hold", + if method == Method::PUT { + "ROLE014" + } else { + "ROLE016" + }, + if method == Method::PUT { + "Only admin may modify the admin role" + } else { + "Only admin may delete the admin role" + }, request_id, ); } } + // The service rejects an empty update only after its existence check. + if method == Method::PUT && payload.as_object().is_some_and(|object| object.is_empty()) { + let (code, message_text) = match spec.collection { + "roles" => ("ROLE007", "No data to update"), + "groups" => ("GRP006", "No data to update"), + "routings" => ("RTG007", "No data to update"), + _ => ("VAL001", "No data to update"), + }; + return error(StatusCode::BAD_REQUEST, code, message_text, request_id); + } if method == Method::PUT { if spec.collection == "routings" { let existing = match storage.find_one(spec.collection, &filter).await { @@ -2849,8 +3776,16 @@ async fn entity_routes( { return error( StatusCode::BAD_REQUEST, - "VAL001", - "Resource identifier cannot be updated", + if spec.collection == "roles" { + "ROLE005" + } else { + "VAL001" + }, + if spec.collection == "roles" { + "Role name cannot be changed" + } else { + "Resource identifier cannot be updated" + }, request_id, ); } @@ -2878,40 +3813,20 @@ async fn entity_routes( } } Ok(None) => error( - StatusCode::NOT_FOUND, + if spec.collection == "roles" { + StatusCode::BAD_REQUEST + } else { + StatusCode::NOT_FOUND + }, spec.not_found_code, - "Resource not found", + not_found_message(spec.collection), request_id, ), Err(_) => unexpected(request_id), }; } if method == Method::DELETE { - if spec.collection == "roles" { - let existing = match storage.find_one(spec.collection, &filter).await { - Ok(Some(role)) => role, - Ok(None) => { - return error( - StatusCode::NOT_FOUND, - spec.not_found_code, - "Resource not found", - request_id, - ); - } - Err(_) => return unexpected(request_id), - }; - if !role_permissions_within_actor(state, username, &existing).await { - return error( - StatusCode::FORBIDDEN, - "ROLE009", - "Roles cannot be deleted when they include permissions you do not hold", - request_id, - ); - } - } - if (spec.collection == "roles" || spec.collection == "groups") - && (key == "admin" || key == "ALL") - { + if spec.collection == "groups" && key == "ALL" { return error( StatusCode::BAD_REQUEST, "AUTH900", @@ -2930,9 +3845,13 @@ async fn entity_routes( message(StatusCode::OK, spec.deleted, request_id) } Ok(false) => error( - StatusCode::NOT_FOUND, + if spec.collection == "roles" { + StatusCode::BAD_REQUEST + } else { + StatusCode::NOT_FOUND + }, spec.not_found_code, - "Resource not found", + not_found_message(spec.collection), request_id, ), Err(_) => unexpected(request_id), @@ -2946,6 +3865,26 @@ async fn entity_routes( ) } +/// Mirrors the pinned `sanitize_filename` checks on a proto path segment. +fn proto_file_name_error(name: &str) -> Option<&'static str> { + if name.is_empty() { + return Some("Empty filename provided"); + } + if name.contains("..") { + return Some("Path traversal detected: .. not allowed in filename"); + } + if name.starts_with('/') || name.starts_with('\\') { + return Some("Absolute paths not allowed in filename"); + } + if name.as_bytes().get(1) == Some(&b':') { + return Some("Drive letters not allowed in filename"); + } + if name.chars().count() > 255 { + return Some("Filename too long (max 255 characters)"); + } + None +} + fn merge_proto_metadata(target: &mut Value, source: &Value) { for key in [ "api_grpc_proto_source", @@ -2963,6 +3902,36 @@ fn merge_proto_metadata(target: &mut Value, source: &Value) { } } +async fn lock_tls_binding_mutation( + state: &AppState, + payload: &Value, + prefix: &str, +) -> Result, ()> { + let has_binding = [ + "client_tls_policy", + "upstream_tls_profile", + "upstream_tls_profiles", + ] + .iter() + .any(|field| { + payload + .get(format!("{prefix}_{field}")) + .is_some_and(|value| !value.is_null()) + }); + if !has_binding { + return Ok(None); + } + let guard = state + .storage + .as_ref() + .ok_or(())? + .tls_mutation_lock() + .await + .map_err(|_| ())?; + state.reload_tls_from_storage().await.map_err(|_| ())?; + Ok(Some(guard)) +} + async fn api_routes( state: &AppState, path: &str, @@ -2977,16 +3946,16 @@ async fn api_routes( }; let suffix = path.strip_prefix("/api").unwrap_or("").trim_matches('/'); if method == Method::GET && (suffix.is_empty() || suffix == "all") { - if !has_permission(state, username, "manage_apis").await { - return error( - StatusCode::FORBIDDEN, - "API008", - "You do not have permission to view APIs", - request_id, - ); + // Python's `get_apis(page: int = ..., page_size: int = ...)` typed + // FastAPI query params: a non-numeric value produces a 422, not a + // silently-defaulted page or a custom 400 PAG001 body. + if let Some(response) = + int_query_validation_error(query, &["page", "page_size"], request_id) + { + return response; } if let Err(message_text) = validate_pagination(query) { - return error(StatusCode::BAD_REQUEST, "PAG001", message_text, request_id); + return error(StatusCode::BAD_REQUEST, "PAG001", &message_text, request_id); } return match storage.find_many("apis", &json!({})).await { Ok(items) => success( @@ -3017,6 +3986,50 @@ async fn api_routes( Ok(payload) => payload, Err(errors) => return validation_errors(errors, request_id), }; + let _tls_guard = match lock_tls_binding_mutation(state, &payload, "api").await { + Ok(guard) => guard, + Err(()) => { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "TLS004", + "TLS profiles are unavailable", + request_id, + ); + } + }; + if let Some(policy) = payload + .get("api_client_tls_policy") + .filter(|value| !value.is_null()) + { + if crate::tls::policy::ClientTlsPolicy::validate_value( + policy, + &state.tls_snapshot().profiles, + state.config.downstream_tls_mode == crate::config::DownstreamTlsMode::Native, + ) + .is_err() + { + return error( + StatusCode::BAD_REQUEST, + "TLS003", + "Invalid client TLS policy", + request_id, + ); + } + } + if crate::tls::profiles::validate_upstream_bindings( + &payload, + "api", + &state.tls_snapshot().profiles, + ) + .is_err() + { + return error( + StatusCode::BAD_REQUEST, + "TLS004", + "Invalid upstream TLS binding", + request_id, + ); + } let name = payload["api_name"] .as_str() .expect("validated API name") @@ -3099,18 +4112,10 @@ async fn api_routes( } let filter = json!({"api_name": parts[0], "api_version": parts[1]}); if method == Method::GET { - if !has_permission(state, username, "manage_apis").await { - return error( - StatusCode::FORBIDDEN, - "API008", - "You do not have permission to view APIs", - request_id, - ); - } return match storage.find_one("apis", &filter).await { Ok(Some(api)) => success(StatusCode::OK, strip_internal(api), request_id), Ok(None) => error( - StatusCode::NOT_FOUND, + StatusCode::BAD_REQUEST, "API003", "API does not exist for the requested name and version", request_id, @@ -3118,30 +4123,66 @@ async fn api_routes( Err(_) => unexpected(request_id), }; } - if !has_permission(state, username, "manage_apis").await { - return error( - StatusCode::FORBIDDEN, - "API008", - "You do not have permission to update APIs", - request_id, - ); - } if method == Method::PUT { + if !has_permission(state, username, "manage_apis").await { + return error( + StatusCode::FORBIDDEN, + "API008", + "You do not have permission to update APIs", + request_id, + ); + } payload = match normalize_update_api(&payload) { Ok(payload) => payload, Err(errors) => return validation_errors(errors, request_id), }; - let Some(updates) = payload.as_object() else { - return unexpected(request_id); + let _tls_guard = match lock_tls_binding_mutation(state, &payload, "api").await { + Ok(guard) => guard, + Err(()) => { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "TLS004", + "TLS profiles are unavailable", + request_id, + ); + } }; - if updates.is_empty() { + if let Some(policy) = payload + .get("api_client_tls_policy") + .filter(|value| !value.is_null()) + { + if crate::tls::policy::ClientTlsPolicy::validate_value( + policy, + &state.tls_snapshot().profiles, + state.config.downstream_tls_mode == crate::config::DownstreamTlsMode::Native, + ) + .is_err() + { + return error( + StatusCode::BAD_REQUEST, + "TLS003", + "Invalid client TLS policy", + request_id, + ); + } + } + if crate::tls::profiles::validate_upstream_bindings( + &payload, + "api", + &state.tls_snapshot().profiles, + ) + .is_err() + { return error( StatusCode::BAD_REQUEST, - "API006", - "No data to update", + "TLS004", + "Invalid upstream TLS binding", request_id, ); } + let Some(updates) = payload.as_object() else { + return unexpected(request_id); + }; for key in ["api_name", "api_version", "api_path"] { if payload.get(key).is_some() { let expected = if key == "api_name" { @@ -3173,6 +4214,15 @@ async fn api_routes( } Err(_) => return unexpected(request_id), }; + // The pinned service checks the rename guard and existence first. + if updates.is_empty() { + return error( + StatusCode::BAD_REQUEST, + "API006", + "No data to update", + request_id, + ); + } let desired_public = payload .get("api_public") .and_then(Value::as_bool) @@ -3222,8 +4272,18 @@ async fn api_routes( }; } if method == Method::DELETE { + let existing = storage.find_one("apis", &filter).await.ok().flatten(); return match storage.delete_one("apis", &filter).await { Ok(true) => { + // The pinned proto file outlives its API; keep the upload so a + // re-created API picks it up again. + if let Some(existing) = existing + .filter(|api| api.get("api_grpc_proto_source").is_some_and(python_truthy)) + { + let mut pending = filter.clone(); + merge_proto_metadata(&mut pending, &existing); + let _ = storage.insert_one("grpc_proto_uploads", pending).await; + } audit::management_mutation( username, "api.delete", @@ -3267,16 +4327,15 @@ async fn user_routes( .unwrap_or("") .trim_matches('/'); if method == Method::GET && (suffix.is_empty() || suffix == "all") { - if !has_permission(state, active_user, "manage_users").await { - return error( - StatusCode::FORBIDDEN, - "USR008", - "Unable to retrieve users", - request_id, - ); + // Python's `get_users(page: int = ..., page_size: int = ...)` typed + // FastAPI query params: a non-numeric value produces a 422. + if let Some(response) = + int_query_validation_error(query, &["page", "page_size"], request_id) + { + return response; } if let Err(message_text) = validate_pagination(query) { - return error(StatusCode::BAD_REQUEST, "PAG001", message_text, request_id); + return error(StatusCode::BAD_REQUEST, "PAG001", &message_text, request_id); } return match storage.find_many("users", &json!({})).await { Ok(items) => { @@ -3290,7 +4349,7 @@ async fn user_routes( .collect(); success( StatusCode::OK, - paginate_named(items, query, "users"), + json!({"users": paginate_items(items, query)}), request_id, ) } @@ -3315,7 +4374,7 @@ async fn user_routes( return error( StatusCode::FORBIDDEN, "USR006", - "You do not have permission to create users", + "Can only update your own information", request_id, ); } @@ -3333,15 +4392,19 @@ async fn user_routes( if method == Method::GET { return user_by(state, "username", target, active_user, request_id).await; } - if active_user != target && !has_permission(state, active_user, "manage_users").await { - return error( - StatusCode::FORBIDDEN, - "USR008", - "Unable to update user", - request_id, - ); - } if method == Method::PUT { + if active_user != target && !has_permission(state, active_user, "manage_users").await { + return error( + StatusCode::FORBIDDEN, + "USR006", + if suffix.ends_with("/update-password") { + "Can only update your own password" + } else { + "Can only update your own information" + }, + request_id, + ); + } if !suffix.ends_with("/update-password") && normalize_update_user_model(&mut payload).is_err() { @@ -3356,7 +4419,7 @@ async fn user_routes( return error( StatusCode::FORBIDDEN, "USR022", - "Super admin password cannot be changed via the API", + "Super admin password cannot be changed via UI", request_id, ); } @@ -3368,16 +4431,19 @@ async fn user_routes( request_id, ); } - if active_user == target - && !has_permission(state, active_user, "manage_users").await - && !self_update_fields_are_safe(&payload) - { - return error( - StatusCode::FORBIDDEN, - "USR023", - "Users without manage_users may not update restricted fields", - request_id, - ); + if active_user == target && !has_permission(state, active_user, "manage_users").await { + let attempted = restricted_self_update_fields(&payload); + if !attempted.is_empty() { + return error( + StatusCode::FORBIDDEN, + "USR023", + &format!( + "Cannot modify restricted fields without manage_users permission: {}", + attempted.join(", ") + ), + request_id, + ); + } } let existing = match storage .find_one("users", &json!({"username": target})) @@ -3399,8 +4465,8 @@ async fn user_routes( { return error( StatusCode::FORBIDDEN, - "USR008", - "Only an administrator can modify an administrator account", + "USR012", + "Only admin may modify admin users", request_id, ); } @@ -3423,7 +4489,7 @@ async fn user_routes( return error( StatusCode::FORBIDDEN, "USR013", - "Only an administrator can assign the administrator role", + "Only admin may change admin role assignments", request_id, ); } @@ -3489,15 +4555,7 @@ async fn user_routes( target, "success", ); - message( - StatusCode::OK, - if password_update { - "Password updated successfully" - } else { - "User updated successfully" - }, - request_id, - ) + message(StatusCode::OK, "User updated successfully", request_id) } Ok(None) => error( StatusCode::NOT_FOUND, @@ -3509,6 +4567,22 @@ async fn user_routes( }; } if method == Method::DELETE { + if target == "admin" { + return error( + StatusCode::FORBIDDEN, + "USR021", + "Super admin user cannot be deleted", + request_id, + ); + } + if active_user != target && !has_permission(state, active_user, "manage_users").await { + return error( + StatusCode::FORBIDDEN, + "USR007", + "Can only delete your own account", + request_id, + ); + } let existing = match storage .find_one("users", &json!({"username": target})) .await @@ -3529,16 +4603,8 @@ async fn user_routes( { return error( StatusCode::FORBIDDEN, - "USR008", - "Only an administrator can delete an administrator account", - request_id, - ); - } - if target == "admin" { - return error( - StatusCode::FORBIDDEN, - "USR021", - "Super admin user cannot be deleted", + "USR014", + "Only admin may delete admin users", request_id, ); } @@ -3638,7 +4704,7 @@ async fn endpoint_routes( path: &str, method: &Method, mut payload: Value, - query: &HashMap, + _query: &HashMap, username: &str, request_id: &str, ) -> Response { @@ -3651,58 +4717,15 @@ async fn endpoint_routes( .trim_matches('/'); let validation_suffix = suffix .strip_prefix("endpoint/validation/") - .or_else(|| suffix.strip_prefix("validation/")); + .or_else(|| suffix.strip_prefix("validation/")) + // Deeper paths belong to the endpoint route registered before it. + .filter(|endpoint_id| !endpoint_id.contains('/')) + // GET /validation/{id} is shadowed by GET /{api_name}/{api_version}. + .filter(|_| !(method == Method::GET && suffix.starts_with("validation/"))); if let Some(endpoint_id) = validation_suffix { - let filter = json!({"endpoint_id": endpoint_id}); - return document_by_method( - state, - "endpoint_validations", - filter, - method, - payload, - username, - "manage_endpoints", - "Endpoint validation", - request_id, - ) - .await; - } - if (suffix == "endpoint/validation" || suffix == "validation") && method == Method::POST { - if !has_permission(state, username, "manage_endpoints").await { - return error( - StatusCode::FORBIDDEN, - "END010", - "Insufficient permissions", - request_id, - ); - } - if payload.get("endpoint_id").is_none() { - return error( - StatusCode::BAD_REQUEST, - "VAL001", - "endpoint_id is required", - request_id, - ); - } - return match storage.insert_one("endpoint_validations", payload).await { - Ok(_) => { - audit::management_mutation( - username, - "endpoint_validation.create", - "endpoint_validation", - "success", - ); - message( - StatusCode::CREATED, - "Endpoint validation created successfully", - request_id, - ) - } - Err(_) => unexpected(request_id), - }; - } - if method == Method::POST && suffix.is_empty() { - if normalize_endpoint_model(&mut payload, true).is_err() { + if method == Method::PUT + && normalize_endpoint_validation_model(&mut payload, false).is_err() + { return error( StatusCode::UNPROCESSABLE_ENTITY, "VAL001", @@ -3710,15 +4733,241 @@ async fn endpoint_routes( request_id, ); } - if !has_permission(state, username, "manage_endpoints").await { - return error( - StatusCode::FORBIDDEN, - "END010", - "You do not have permission to create endpoints", - request_id, - ); + if method != Method::GET && !has_permission(state, username, "manage_endpoints").await { + let (code, text) = if method == Method::PUT { + ( + "END014", + "You do not have permission to update endpoint validations", + ) + } else { + ( + "END015", + "You do not have permission to delete endpoint validations", + ) + }; + return error(StatusCode::FORBIDDEN, code, text, request_id); } - for field in [ + let filter = json!({"endpoint_id": endpoint_id}); + if method == Method::GET { + return match storage.find_one("endpoint_validations", &filter).await { + Ok(Some(validation)) => { + success(StatusCode::OK, strip_internal(validation), request_id) + } + Ok(None) => error( + StatusCode::BAD_REQUEST, + "END018", + "Endpoint validation does not exist", + request_id, + ), + Err(_) => unexpected(request_id), + }; + } + if method == Method::PUT { + if payload.get("validation_enabled") == Some(&Value::Bool(false)) { + return error( + StatusCode::BAD_REQUEST, + "END020", + "Validation enabled is required", + request_id, + ); + } + match storage + .find_one("endpoints", &json!({"endpoint_id": endpoint_id})) + .await + { + Ok(Some(_)) => {} + Ok(None) => { + return error( + StatusCode::BAD_REQUEST, + "END022", + "Endpoint does not exist", + request_id, + ); + } + Err(_) => return unexpected(request_id), + } + return match storage + .update_one("endpoint_validations", &filter, &payload) + .await + { + Ok(_) => message( + StatusCode::OK, + "Endpoint validation updated successfully", + request_id, + ), + Err(_) => error( + StatusCode::BAD_REQUEST, + "END023", + "Unable to update endpoint validation", + request_id, + ), + }; + } + if method == Method::DELETE { + return match storage.delete_one("endpoint_validations", &filter).await { + Ok(_) => message( + StatusCode::OK, + "Endpoint validation deleted successfully", + request_id, + ), + Err(_) => error( + StatusCode::BAD_REQUEST, + "END019", + "Unable to delete endpoint validation", + request_id, + ), + }; + } + return error( + StatusCode::METHOD_NOT_ALLOWED, + "GTW004", + "Method not allowed", + request_id, + ); + } + if (suffix == "endpoint/validation" || suffix == "validation") && method == Method::POST { + if normalize_endpoint_validation_model(&mut payload, true).is_err() { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", + request_id, + ); + } + if !has_permission(state, username, "manage_endpoints").await { + return error( + StatusCode::FORBIDDEN, + "END013", + "You do not have permission to create endpoint validations", + request_id, + ); + } + let endpoint_id = payload + .get("endpoint_id") + .and_then(Value::as_str) + .unwrap_or_default(); + if endpoint_id.is_empty() { + return error( + StatusCode::BAD_REQUEST, + "END013", + "Endpoint ID is required", + request_id, + ); + } + match storage + .find_one("endpoint_validations", &json!({"endpoint_id": endpoint_id})) + .await + { + Ok(Some(_)) => { + return error( + StatusCode::BAD_REQUEST, + "END017", + "Endpoint validation already exists", + request_id, + ); + } + Ok(None) => {} + Err(_) => return unexpected(request_id), + } + match storage + .find_one("endpoints", &json!({"endpoint_id": endpoint_id})) + .await + { + Ok(Some(_)) => {} + Ok(None) => { + return error( + StatusCode::BAD_REQUEST, + "END015", + "Endpoint does not exist", + request_id, + ); + } + Err(_) => return unexpected(request_id), + } + return match storage.insert_one("endpoint_validations", payload).await { + Ok(_) => { + audit::management_mutation( + username, + "endpoint_validation.create", + "endpoint_validation", + "success", + ); + message( + StatusCode::CREATED, + "Endpoint validation created successfully", + request_id, + ) + } + Err(_) => error( + StatusCode::BAD_REQUEST, + "END016", + "Unable to create endpoint validation", + request_id, + ), + }; + } + if method == Method::POST && suffix.is_empty() { + if normalize_endpoint_model(&mut payload, true).is_err() { + return error( + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", + request_id, + ); + } + let _tls_guard = match lock_tls_binding_mutation(state, &payload, "endpoint").await { + Ok(guard) => guard, + Err(()) => { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "TLS004", + "TLS profiles are unavailable", + request_id, + ); + } + }; + if let Some(policy) = payload + .get("endpoint_client_tls_policy") + .filter(|value| !value.is_null()) + { + if crate::tls::policy::ClientTlsPolicy::validate_value( + policy, + &state.tls_snapshot().profiles, + state.config.downstream_tls_mode == crate::config::DownstreamTlsMode::Native, + ) + .is_err() + { + return error( + StatusCode::BAD_REQUEST, + "TLS003", + "Invalid client TLS policy", + request_id, + ); + } + } + if crate::tls::profiles::validate_upstream_bindings( + &payload, + "endpoint", + &state.tls_snapshot().profiles, + ) + .is_err() + { + return error( + StatusCode::BAD_REQUEST, + "TLS004", + "Invalid upstream TLS binding", + request_id, + ); + } + if !has_permission(state, username, "manage_endpoints").await { + return error( + StatusCode::FORBIDDEN, + "END010", + "You do not have permission to create endpoints", + request_id, + ); + } + for field in [ "api_name", "api_version", "endpoint_method", @@ -3815,22 +5064,17 @@ async fn endpoint_routes( request_id, ) } - Err(_) => unexpected(request_id), + Err(_) => error( + StatusCode::BAD_REQUEST, + "END003", + "Unable to insert endpoint", + request_id, + ), }; } let parts = suffix.split('/').collect::>(); + // The pinned listing takes no pagination parameters. if method == Method::GET && parts.len() == 2 { - if !has_permission(state, username, "manage_endpoints").await { - return error( - StatusCode::FORBIDDEN, - "END010", - "You do not have permission to view endpoints", - request_id, - ); - } - if let Err(message_text) = validate_pagination(query) { - return error(StatusCode::BAD_REQUEST, "PAG001", message_text, request_id); - } return match storage .find_many( "endpoints", @@ -3838,9 +5082,15 @@ async fn endpoint_routes( ) .await { + Ok(items) if items.is_empty() => error( + StatusCode::BAD_REQUEST, + "END005", + "No endpoints found for the requested API name and version", + request_id, + ), Ok(items) => success( StatusCode::OK, - paginate(items.into_iter().map(strip_internal).collect(), query), + json!({"endpoints": items.into_iter().map(strip_internal).collect::>()}), request_id, ), Err(_) => unexpected(request_id), @@ -3855,9 +5105,67 @@ async fn endpoint_routes( request_id, ); } + let _tls_guard = if method == Method::PUT { + match lock_tls_binding_mutation(state, &payload, "endpoint").await { + Ok(guard) => guard, + Err(()) => { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "TLS004", + "TLS profiles are unavailable", + request_id, + ); + } + } + } else { + None + }; + if method == Method::PUT { + if let Some(policy) = payload + .get("endpoint_client_tls_policy") + .filter(|value| !value.is_null()) + { + if crate::tls::policy::ClientTlsPolicy::validate_value( + policy, + &state.tls_snapshot().profiles, + state.config.downstream_tls_mode == crate::config::DownstreamTlsMode::Native, + ) + .is_err() + { + return error( + StatusCode::BAD_REQUEST, + "TLS003", + "Invalid client TLS policy", + request_id, + ); + } + } + if crate::tls::profiles::validate_upstream_bindings( + &payload, + "endpoint", + &state.tls_snapshot().profiles, + ) + .is_err() + { + return error( + StatusCode::BAD_REQUEST, + "TLS004", + "Invalid upstream TLS binding", + request_id, + ); + } + } let uri = format!("/{}", parts[3..].join("/")); let filter = json!({"endpoint_method": parts[0], "api_name": parts[1], "api_version": parts[2], "endpoint_uri": uri}); if method == Method::PUT { + if !has_permission(state, username, "manage_endpoints").await { + return error( + StatusCode::FORBIDDEN, + "END011", + "You do not have permission to update endpoints", + request_id, + ); + } let existing = match storage.find_one("endpoints", &filter).await { Ok(Some(endpoint)) => endpoint, Ok(None) => { @@ -3960,21 +5268,37 @@ async fn endpoint_routes( "Endpoint does not exist for the requested API name, version and URI", request_id, ), + Err(_) => error( + StatusCode::BAD_REQUEST, + "END009", + "Unable to delete endpoint", + request_id, + ), + }; + } + if method == Method::GET { + return match storage.find_one("endpoints", &filter).await { + Ok(Some(endpoint)) => success(StatusCode::OK, strip_internal(endpoint), request_id), + Ok(None) => error( + StatusCode::BAD_REQUEST, + "END004", + "Endpoint does not exist for the requested API name, version and URI", + request_id, + ), Err(_) => unexpected(request_id), }; } - return document_by_method( - state, - "endpoints", - filter, - method, - payload, - username, - "manage_endpoints", - "Endpoint", - request_id, - ) - .await; + if method == Method::PUT { + return match storage.update_one("endpoints", &filter, &payload).await { + Ok(Some(_)) => message(StatusCode::OK, "Endpoint updated successfully", request_id), + Ok(None) | Err(_) => error( + StatusCode::BAD_REQUEST, + "END003", + "Unable to update endpoint", + request_id, + ), + }; + } } error( StatusCode::NOT_FOUND, @@ -3984,108 +5308,17 @@ async fn endpoint_routes( ) } -#[allow(clippy::too_many_arguments)] -async fn document_by_method( +async fn login( state: &AppState, - collection: &str, - filter: Value, - method: &Method, + headers: &HeaderMap, payload: Value, - username: &str, - permission: &str, - label: &str, request_id: &str, ) -> Response { let Some(storage) = &state.storage else { return unexpected(request_id); }; - if method == Method::GET { - return match storage.find_one(collection, &filter).await { - Ok(Some(item)) => success(StatusCode::OK, strip_internal(item), request_id), - Ok(None) => error( - StatusCode::NOT_FOUND, - "EPT002", - &format!("{label} not found"), - request_id, - ), - Err(_) => unexpected(request_id), - }; - } - if !has_permission(state, username, permission).await { - return error( - StatusCode::FORBIDDEN, - "END010", - "Insufficient permissions", - request_id, - ); - } - if method == Method::PUT { - return match storage.update_one(collection, &filter, &payload).await { - Ok(Some(_)) => { - audit::management_mutation( - username, - &format!("{}.update", label.to_ascii_lowercase().replace(' ', "_")), - &filter.to_string(), - "success", - ); - message( - StatusCode::OK, - &format!("{label} updated successfully"), - request_id, - ) - } - Ok(None) => error( - StatusCode::NOT_FOUND, - "EPT002", - &format!("{label} not found"), - request_id, - ), - Err(_) => unexpected(request_id), - }; - } - if method == Method::DELETE { - return match storage.delete_one(collection, &filter).await { - Ok(true) => { - audit::management_mutation( - username, - &format!("{}.delete", label.to_ascii_lowercase().replace(' ', "_")), - &filter.to_string(), - "success", - ); - message( - StatusCode::OK, - &format!("{label} deleted successfully"), - request_id, - ) - } - Ok(false) => error( - StatusCode::NOT_FOUND, - "EPT002", - &format!("{label} not found"), - request_id, - ), - Err(_) => unexpected(request_id), - }; - } - error( - StatusCode::METHOD_NOT_ALLOWED, - "GTW004", - "Method not allowed", - request_id, - ) -} - -async fn login( - state: &AppState, - headers: &HeaderMap, - payload: Value, - request_id: &str, -) -> Response { - let Some(storage) = &state.storage else { - return unexpected(request_id); - }; - let Some(email) = payload.get("email").and_then(Value::as_str) else { - audit::management_mutation("anonymous", "user.login", "unknown", "failed"); + let Some(email) = payload.get("email").and_then(Value::as_str) else { + audit::management_mutation("anonymous", "user.login", "unknown", "failed"); return error( StatusCode::BAD_REQUEST, "AUTH001", @@ -4389,7 +5622,7 @@ fn normalize_role_model(payload: &mut Value, create: bool) -> Result<(), ()> { .get("role_name") .and_then(security_setting_string) .ok_or(())?; - if value.is_empty() || value.len() > 50 { + if value.is_empty() || value.chars().count() > 50 { return Err(()); } object.insert("role_name".to_owned(), json!(value)); @@ -4397,7 +5630,7 @@ fn normalize_role_model(payload: &mut Value, create: bool) -> Result<(), ()> { match object.get("role_description") { Some(value) if !value.is_null() => { let value = security_setting_string(value).ok_or(())?; - if value.len() > 255 || (!create && value.is_empty()) { + if value.chars().count() > 255 || (!create && value.is_empty()) { return Err(()); } object.insert("role_description".to_owned(), json!(value)); @@ -4427,7 +5660,7 @@ fn normalize_group_model(payload: &mut Value, create: bool) -> Result<(), ()> { .get("group_name") .and_then(security_setting_string) .ok_or(())?; - if value.is_empty() || value.len() > 50 { + if value.is_empty() || value.chars().count() > 50 { return Err(()); } object.insert("group_name".to_owned(), json!(value)); @@ -4435,7 +5668,7 @@ fn normalize_group_model(payload: &mut Value, create: bool) -> Result<(), ()> { match object.get("group_description") { Some(value) if !value.is_null() => { let value = security_setting_string(value).ok_or(())?; - if value.len() > 255 || (!create && value.is_empty()) { + if value.chars().count() > 255 || (!create && value.is_empty()) { return Err(()); } object.insert("group_description".to_owned(), json!(value)); @@ -4495,7 +5728,7 @@ fn normalize_routing_model(payload: &mut Value, create: bool) -> Result<(), ()> return Err(()); } let value = security_setting_string(value).ok_or(())?; - if value.is_empty() || value.len() > 50 { + if value.is_empty() || value.chars().count() > 50 { return Err(()); } object.insert(field.to_owned(), json!(value)); @@ -4522,7 +5755,7 @@ fn normalize_routing_model(payload: &mut Value, create: bool) -> Result<(), ()> match object.get("routing_description") { Some(value) if !value.is_null() => { let value = security_setting_string(value).ok_or(())?; - if value.len() > 255 { + if value.chars().count() > 255 { return Err(()); } object.insert("routing_description".to_owned(), json!(value)); @@ -4567,9 +5800,19 @@ fn normalize_endpoint_model(payload: &mut Value, create: bool) -> Result<(), ()> | "client_uri" | "api_id" | "endpoint_id" - ) && (create || !value.is_null()) + | "endpoint_client_tls_policy" + | "endpoint_upstream_tls_profile" + | "endpoint_upstream_tls_profiles" + ) && (create + || !value.is_null() + || matches!( + field.as_str(), + "endpoint_client_tls_policy" + | "endpoint_upstream_tls_profile" + | "endpoint_upstream_tls_profiles" + )) }); - for (field, minimum, maximum) in [ + for (field, configured_minimum, configured_maximum) in [ ("api_name", 1, 50), ("api_version", 1, 10), ("endpoint_method", 1, 10), @@ -4579,6 +5822,11 @@ fn normalize_endpoint_model(payload: &mut Value, create: bool) -> Result<(), ()> ("api_id", 1, 255), ("endpoint_id", 1, 255), ] { + let (minimum, maximum) = if create && matches!(field, "api_id" | "endpoint_id") { + (0, usize::MAX) + } else { + (configured_minimum, configured_maximum) + }; if let Some(value) = object.get(field) { if value.is_null() { if create && matches!(field, "client_uri" | "api_id" | "endpoint_id") { @@ -4587,7 +5835,8 @@ fn normalize_endpoint_model(payload: &mut Value, create: bool) -> Result<(), ()> return Err(()); } let value = security_setting_string(value).ok_or(())?; - if value.len() < minimum || value.len() > maximum { + let length = value.chars().count(); + if length < minimum || length > maximum { return Err(()); } object.insert(field.to_owned(), json!(value)); @@ -4611,9 +5860,127 @@ fn normalize_endpoint_model(payload: &mut Value, create: bool) -> Result<(), ()> Some(_) => return Err(()), None => {} } + if create { + // Pydantic's `.dict()` persists unset optional fields as null. + for field in ["endpoint_servers", "client_uri"] { + object.entry(field).or_insert(Value::Null); + } + } + Ok(()) +} + +fn normalize_endpoint_validation_model(payload: &mut Value, create: bool) -> Result<(), ()> { + let object = payload.as_object_mut().ok_or(())?; + object.retain(|field, _| { + matches!( + field.as_str(), + "endpoint_id" | "validation_enabled" | "validation_schema" + ) && (create || field != "endpoint_id") + }); + if create { + let endpoint_id = object + .get("endpoint_id") + .and_then(security_setting_string) + .ok_or(())?; + object.insert("endpoint_id".to_owned(), json!(endpoint_id)); + } + let enabled = object + .get("validation_enabled") + .and_then(security_setting_bool) + .ok_or(())?; + object.insert("validation_enabled".to_owned(), json!(enabled)); + let schema = object + .get("validation_schema") + .and_then(normalize_validation_schema_model) + .ok_or(())?; + object.insert("validation_schema".to_owned(), schema); Ok(()) } +fn normalize_validation_schema_model(value: &Value) -> Option { + let rules = value.as_object()?.get("validation_schema")?.as_object()?; + let mut normalized = Map::new(); + for (path, rule) in rules { + normalized.insert(path.clone(), normalize_field_validation_model(rule)?); + } + Some(json!({"validation_schema": normalized})) +} + +fn normalize_field_validation_model(value: &Value) -> Option { + let input = value.as_object()?; + let mut output = Map::new(); + output.insert( + "required".to_owned(), + json!(security_setting_bool(input.get("required")?)?), + ); + output.insert( + "type".to_owned(), + json!(security_setting_string(input.get("type")?)?), + ); + for field in ["min", "max"] { + let value = match input.get(field) { + Some(Value::Null) | None => Value::Null, + Some(value) => validation_model_number(value)?, + }; + output.insert(field.to_owned(), value); + } + for field in ["pattern", "format", "custom_validator"] { + let value = match input.get(field) { + Some(Value::Null) | None => Value::Null, + Some(value) => Value::String(security_setting_string(value)?), + }; + output.insert(field.to_owned(), value); + } + let enum_values = match input.get("enum") { + Some(Value::Null) | None => Value::Null, + Some(Value::Array(values)) => Value::Array(values.clone()), + Some(_) => return None, + }; + output.insert("enum".to_owned(), enum_values); + let nested = match input.get("nested_schema") { + Some(Value::Null) | None => Value::Null, + Some(Value::Object(fields)) => { + let mut normalized = Map::new(); + for (name, rules) in fields { + normalized.insert(name.clone(), normalize_field_validation_model(rules)?); + } + Value::Object(normalized) + } + Some(_) => return None, + }; + output.insert("nested_schema".to_owned(), nested); + let array_items = match input.get("array_items") { + Some(Value::Null) | None => Value::Null, + Some(value) => normalize_field_validation_model(value)?, + }; + output.insert("array_items".to_owned(), array_items); + Some(Value::Object(output)) +} + +fn validation_model_number(value: &Value) -> Option { + match value { + Value::Bool(value) => Some(json!(i64::from(*value))), + Value::Number(value) if value.is_i64() || value.is_u64() => { + Some(Value::Number(value.clone())) + } + Value::Number(value) => value + .as_f64() + .filter(|value| value.is_finite()) + .map(|value| json!(value as i64)), + Value::String(value) => crate::python_scalar::parse_model_integer(value) + .and_then(|value| i64::try_from(value).ok()) + .map(|value| json!(value)) + .or_else(|| { + value + .parse::() + .ok() + .filter(|value| value.is_finite()) + .map(|value| json!(value)) + }), + _ => None, + } +} + fn normalize_subscription_model(payload: &mut Value) -> Result<(), ()> { let object = payload.as_object_mut().ok_or(())?; object.retain(|field, _| matches!(field.as_str(), "username" | "api_name" | "api_version")); @@ -4626,7 +5993,8 @@ fn normalize_subscription_model(payload: &mut Value) -> Result<(), ()> { .get(field) .and_then(security_setting_string) .ok_or(())?; - if value.len() < minimum || value.len() > maximum { + let length = value.chars().count(); + if length < minimum || length > maximum { return Err(()); } object.insert(field.to_owned(), json!(value)); @@ -4652,7 +6020,7 @@ fn normalize_credit_model(payload: &mut Value, create: bool) -> Result<(), ()> { .get(field) .and_then(security_setting_string) .ok_or(())?; - if field == "api_credit_group" && (value.is_empty() || value.len() > 50) { + if field == "api_credit_group" && (value.is_empty() || value.chars().count() > 50) { return Err(()); } object.insert(field.to_owned(), json!(value)); @@ -4665,8 +6033,14 @@ fn normalize_credit_model(payload: &mut Value, create: bool) -> Result<(), ()> { } continue; } - let value = security_setting_string(value).ok_or(())?; - object.insert(field.to_owned(), json!(value)); + let value = if field == "api_key_rotation_expires" { + tier_model_datetime(value).ok_or(())? + } else { + json!(security_setting_string(value).ok_or(())?) + }; + object.insert(field.to_owned(), value); + } else if create { + object.insert(field.to_owned(), Value::Null); } } let tiers = object @@ -4687,7 +6061,7 @@ fn normalize_credit_model(payload: &mut Value, create: bool) -> Result<(), ()> { .get(field) .and_then(security_setting_string) .ok_or(())?; - if field == "tier_name" && (value.is_empty() || value.len() > 50) { + if field == "tier_name" && (value.is_empty() || value.chars().count() > 50) { return Err(()); } tier.insert(field.to_owned(), json!(value)); @@ -4730,7 +6104,8 @@ fn normalize_user_credit_model(payload: &mut Value) -> Result<(), ()> { .get("username") .and_then(security_setting_string) .ok_or(())?; - if username.len() < 3 || username.len() > 50 { + let username_length = username.chars().count(); + if !(3..=50).contains(&username_length) { return Err(()); } object.insert("username".to_owned(), json!(username)); @@ -4750,7 +6125,7 @@ fn normalize_user_credit_model(payload: &mut Value) -> Result<(), ()> { .get("tier_name") .and_then(security_setting_string) .ok_or(())?; - if tier_name.is_empty() || tier_name.len() > 50 { + if tier_name.is_empty() || tier_name.chars().count() > 50 { return Err(()); } credit.insert("tier_name".to_owned(), json!(tier_name)); @@ -4767,6 +6142,9 @@ fn normalize_user_credit_model(payload: &mut Value) -> Result<(), ()> { let value = security_setting_string(value).ok_or(())?; credit.insert(field.to_owned(), json!(value)); } + if !credit.contains_key(field) { + credit.insert(field.to_owned(), Value::Null); + } } } Ok(()) @@ -4813,14 +6191,15 @@ fn normalize_create_user_model(payload: &mut Value) -> Result<(), ()> { .get(field) .and_then(security_setting_string) .ok_or(())?; - if value.len() < minimum || value.len() > maximum { + let length = value.chars().count(); + if length < minimum || length > maximum { return Err(()); } object.insert(field.to_owned(), json!(value)); } let groups = match object.get("groups") { - None | Some(Value::Null) => json!([]), + None => json!([]), Some(Value::Array(groups)) => Value::Array( groups .iter() @@ -4857,7 +6236,7 @@ fn normalize_create_user_model(payload: &mut Value) -> Result<(), ()> { ] { if let Some(value) = object.get(field).filter(|value| !value.is_null()) { let value = security_setting_string(value).ok_or(())?; - if value.is_empty() || value.len() > maximum { + if value.is_empty() || value.chars().count() > maximum { return Err(()); } object.insert(field.to_owned(), json!(value)); @@ -4866,6 +6245,30 @@ fn normalize_create_user_model(payload: &mut Value) -> Result<(), ()> { if !object.contains_key("bandwidth_limit_window") { object.insert("bandwidth_limit_window".to_owned(), json!("day")); } + for field in [ + "rate_limit_duration", + "rate_limit_duration_type", + "rate_limit_enabled", + "throttle_duration", + "throttle_duration_type", + "throttle_wait_duration", + "throttle_wait_duration_type", + "throttle_queue_limit", + "throttle_enabled", + "custom_attributes", + "bandwidth_limit_bytes", + "bandwidth_limit_enabled", + ] { + if !object.contains_key(field) { + object.insert(field.to_owned(), Value::Null); + } + } + if !object.contains_key("active") { + object.insert("active".to_owned(), json!(true)); + } + if !object.contains_key("ui_access") { + object.insert("ui_access".to_owned(), json!(false)); + } for field in [ "rate_limit_enabled", "throttle_enabled", @@ -4933,7 +6336,8 @@ fn normalize_update_user_model(payload: &mut Value) -> Result<(), ()> { ] { if let Some(value) = object.get(field) { let value = security_setting_string(value).ok_or(())?; - if value.len() < minimum || value.len() > maximum { + let length = value.chars().count(); + if length < minimum || length > maximum { return Err(()); } object.insert(field.to_owned(), json!(value)); @@ -4983,7 +6387,7 @@ fn normalize_update_user_model(payload: &mut Value) -> Result<(), ()> { ] { if let Some(value) = object.get(field) { let value = security_setting_string(value).ok_or(())?; - if value.is_empty() || value.len() > maximum { + if value.is_empty() || value.chars().count() > maximum { return Err(()); } object.insert(field.to_owned(), json!(value)); @@ -5015,6 +6419,23 @@ fn normalize_update_user_model(payload: &mut Value) -> Result<(), ()> { Ok(()) } +/// FastAPI validates UpdatePasswordModel before authentication and route +/// permissions. Pydantic v1 coerces JSON scalars to strings, ignores unknown +/// fields, and measures string bounds in Unicode characters. +fn normalize_update_password_model(payload: &mut Value) -> Result<(), ()> { + let object = payload.as_object_mut().ok_or(())?; + let password = object + .get("new_password") + .and_then(security_setting_string) + .ok_or(())?; + if !(6..=36).contains(&password.chars().count()) { + return Err(()); + } + object.clear(); + object.insert("new_password".to_owned(), json!(password)); + Ok(()) +} + async fn user_by( state: &AppState, field: &str, @@ -5189,48 +6610,56 @@ async fn authorization_routes( } } audit::management_mutation(username, "authorization.invalidate", username, "success"); - let mut response = message(StatusCode::OK, "Token invalidated successfully", request_id); - response.headers_mut().append( - header::SET_COOKIE, - HeaderValue::from_static("access_token_cookie=; Path=/; Max-Age=0; HttpOnly"), + let mut response = message( + StatusCode::OK, + "Your token has been invalidated", + request_id, ); + // Python scopes the deleted cookie to COOKIE_DOMAIN unless the host is a + // local/dev host, and clears both the primary and dev cookie names. + let domain_attribute = cookie_domain(headers) + .map(|domain| format!("; Domain={domain}")) + .unwrap_or_default(); + for cookie_name in ["access_token_cookie", "access_token_cookie_dev"] { + response.headers_mut().append( + header::SET_COOKIE, + HeaderValue::from_str(&format!( + "{cookie_name}=; Path=/; Max-Age=0; HttpOnly{domain_attribute}" + )) + .unwrap(), + ); + } return response; } let parts = path .trim_start_matches("/authorization/admin/") .split('/') .collect::>(); - if parts.len() == 2 && has_permission(state, username, "manage_auth").await { + let admin_action = parts.len() == 2 + && matches!( + (parts[0], &method), + ("status", &Method::GET) + | ("disable", &Method::POST) + | ("enable", &Method::POST) + | ("revoke", &Method::POST) + | ("unrevoke", &Method::POST) + ); + if admin_action { + if !has_permission(state, username, "manage_auth").await { + return error( + StatusCode::FORBIDDEN, + "AUTH900", + "You do not have permission to manage auth", + request_id, + ); + } let target = parts[1]; - if matches!( - parts[0], - "status" | "disable" | "enable" | "revoke" | "unrevoke" - ) { - let Some(storage) = &state.storage else { - return unexpected(request_id); - }; - let Some(target_user) = storage - .find_one("users", &json!({"username": target})) - .await - .ok() - .flatten() - else { - return error( - StatusCode::NOT_FOUND, - "USR002", - "User not found", - request_id, - ); - }; - let target_is_admin = target_user.get("role").and_then(Value::as_str) == Some("admin"); - if target_is_admin && !is_admin_user(state, username).await { - return error( - StatusCode::FORBIDDEN, - "AUTH006", - "Only an administrator can manage an administrator account", - request_id, - ); - } + if is_admin_user(state, target).await && !is_admin_user(state, username).await { + return json_response( + StatusCode::NOT_FOUND, + json!({"error_message": "User not found"}), + request_id, + ); } if parts[0] == "status" { let Some(storage) = &state.storage else { @@ -5242,12 +6671,7 @@ async fn authorization_routes( .ok() .flatten() else { - return error( - StatusCode::NOT_FOUND, - "USR002", - "User not found", - request_id, - ); + return unexpected(request_id); }; let revoked = storage .find_one( @@ -5260,50 +6684,60 @@ async fn authorization_routes( .is_some(); return success( StatusCode::OK, - json!({"username": target, "active": user.get("active").and_then(Value::as_bool).unwrap_or(true), "revoked": revoked}), + json!({"active": user.get("active").and_then(Value::as_bool).unwrap_or(false), "revoked": revoked}), request_id, ); } if parts[0] == "disable" || parts[0] == "enable" { let active = parts[0] == "enable"; if let Some(storage) = &state.storage { - return match storage + let update = storage .update_one( "users", &json!({"username": target}), &json!({"active": active}), ) - .await - { - Ok(Some(_)) => { - audit::management_mutation( - username, - if active { - "authorization.enable" - } else { - "authorization.disable" - }, - target, - "success", - ); - message( - StatusCode::OK, - if active { - "User enabled successfully" - } else { - "User disabled successfully" - }, - request_id, + .await; + if update.is_err() { + return unexpected(request_id); + } + if !active { + let _ = storage + .delete_one( + "revocations", + &json!({"type": "revoke_all", "username": target}), ) + .await; + if let Err(error_value) = storage + .insert_one( + "revocations", + json!({"type": "revoke_all", "username": target, "revoke_all": true, "revoked_at": unix_seconds()}), + ) + .await + && !error_value.is_duplicate_key() + { + return unexpected(request_id); } - Ok(None) => error( - StatusCode::NOT_FOUND, - "USR002", - "User not found", - request_id, - ), - Err(_) => unexpected(request_id), - }; + } + audit::management_mutation( + username, + if active { + "authorization.enable" + } else { + "authorization.disable" + }, + target, + "success", + ); + return message( + StatusCode::OK, + &if active { + format!("User {target} enabled") + } else { + format!("User {target} disabled and tokens revoked") + }, + request_id, + ); } } if parts[0] == "revoke" || parts[0] == "unrevoke" { @@ -5350,10 +6784,10 @@ async fn authorization_routes( ); return message( StatusCode::OK, - if parts[0] == "revoke" { - "All tokens revoked" + &if parts[0] == "revoke" { + format!("All tokens revoked for {target}") } else { - "Token revocation cleared" + format!("Token revocation cleared for {target}") }, request_id, ); @@ -5496,70 +6930,595 @@ fn python_openapi_contract() -> Result<&'static Value, &'static str> { .ok_or("Failed to initialize embedded OpenAPI contract") } -fn platform_docs(path: &str, request_id: &str) -> Response { - let html = if path == "/redoc" { - r#"Doorman API"# - } else { - r#"Doorman API
"# - }; - let mut response = axum::response::Html(html).into_response(); - if let Ok(value) = HeaderValue::from_str(request_id) { - response.headers_mut().insert("x-request-id", value.clone()); - response.headers_mut().insert("request_id", value); - } - response +enum RouteMatch { + Found, + NotFound, + MethodNotAllowed(String), } -async fn authorize( - state: &AppState, - headers: &HeaderMap, - path: &str, - request_id: &str, -) -> Result { - let claims = verify_request_token(headers, &state.config.shared_storage).map_err(|_| { - error( - StatusCode::UNAUTHORIZED, - "AUTH003", - "Unauthorized", - request_id, - ) - })?; - let username = claims.sub.as_deref().unwrap_or(""); - let Some(storage) = &state.storage else { - return Err(unexpected(request_id)); - }; - if state.config.https_only - && path != "/user/admin" - && path != "/authorization" - && path != "/authorization/register" - && !csrf_matches(headers, storage, username).await - { - return Err(error( - StatusCode::UNAUTHORIZED, - "AUTH003", +/// Platform routes FastAPI serves outside the frozen schema +/// (`include_in_schema=False` and framework docs) plus Rust-native additions. +const UNLISTED_PLATFORM_ROUTES: &[(&str, &str)] = &[ + ("GET", "/platform/docs"), + ("GET", "/platform/redoc"), + ("GET", "/platform/openapi.json"), + ("GET", "/platform/monitor/report"), + ("GET", "/platform/logging/logs/download"), + ("POST", "/platform/tools/rate-limit-simulator"), +]; + +/// `Allow` answered by the pinned server for multi-route platform paths. +/// Starlette names only the first registered route that matched the path, and +/// the frozen OpenAPI does not preserve registration order, so these values +/// were read from the pinned server with TRACE requests. +const PYTHON_ALLOW: &[(&str, &str)] = &[ + ("/platform/user", "POST"), + ("/platform/user/{username}", "PUT"), + ("/platform/api", "POST"), + ("/platform/api/{api_name}/{api_version}", "PUT"), + ( + "/platform/endpoint/{endpoint_method}/{api_name}/{api_version}/{endpoint_uri}", + "PUT", + ), + ( + "/platform/endpoint/endpoint/validation/{endpoint_id}", + "PUT", + ), + ("/platform/endpoint/validation/{endpoint_id}", "GET"), + ("/platform/group/{group_name}", "PUT"), + ("/platform/role", "POST"), + ("/platform/role/{role_name}", "PUT"), + ("/platform/routing/{client_key}", "PUT"), + ("/platform/proto/{api_name}/{api_version}", "POST"), + ("/platform/security/settings", "GET"), + ("/platform/credit/{api_credit_group}", "PUT"), + ("/platform/credit/{username}", "PUT"), + ("/platform/vault", "POST"), + ("/platform/vault/{key_name}", "GET"), + ("/platform/tiers/", "POST"), + ("/platform/tiers/{tier_id}", "GET"), + ("/platform/tiers/assignments/{user_id}", "GET"), + ("/platform/rate-limits/", "POST"), + ("/platform/rate-limits/{rule_id}", "GET"), +]; + +/// Route table in registration order: (matcher, methods). +fn platform_route_table() -> &'static [(Regex, Vec)] { + static TABLE: OnceLock)>> = OnceLock::new(); + TABLE.get_or_init(|| { + let mut table: Vec<(String, Vec)> = Vec::new(); + if let Ok(contract) = python_openapi_contract() + && let Some(paths) = contract.get("paths").and_then(Value::as_object) + { + for (template, item) in paths { + if !template.starts_with("/platform") { + continue; + } + let mut methods: Vec = item + .as_object() + .map(|item| { + item.keys() + .map(|method| method.to_ascii_uppercase()) + .collect() + }) + .unwrap_or_default(); + // Keep the pinned `Allow` method first; partial matches report it. + if let Some((_, allow)) = PYTHON_ALLOW.iter().find(|(path, _)| path == template) { + methods.retain(|method| method != allow); + methods.insert(0, (*allow).to_owned()); + } + table.push((template.clone(), methods)); + } + } + for (method, template) in UNLISTED_PLATFORM_ROUTES { + table.push(((*template).to_owned(), vec![(*method).to_owned()])); + } + table + .into_iter() + .filter_map(|(template, methods)| Some((template_regex(&template)?, methods))) + .collect() + }) +} + +/// Compile a Starlette path template: `{name}` is one segment, `{name:path}` +/// is the remainder of the path. +fn template_regex(template: &str) -> Option { + static PARAM: OnceLock = OnceLock::new(); + // FastAPI omits `:path` converters from OpenAPI; the only platform route + // using one is the endpoint URI, which may contain slashes. + let param = PARAM.get_or_init(|| { + Regex::new(r"\{(?:endpoint_uri|[^}:]+(:path))\}|\{[^}:]+\}").expect("route param regex") + }); + let mut pattern = String::from("^"); + let mut last = 0; + for capture in param.captures_iter(template) { + let whole = capture.get(0)?; + pattern.push_str(®ex::escape(&template[last..whole.start()])); + let path_param = capture.get(1).is_some() || whole.as_str() == "{endpoint_uri}"; + pattern.push_str(if path_param { ".*" } else { "[^/]+" }); + last = whole.end(); + } + pattern.push_str(®ex::escape(&template[last..])); + pattern.push('$'); + Regex::new(&pattern).ok() +} + +/// The pinned platform routes let FastAPI render an authentication failure as +/// `{"detail": "Unauthorized"}`, except these, which wrap it in a route error +/// code. Captured from the pinned server with anonymous requests to every +/// frozen operation (routes where the pinned server instead crashes with a 500 +/// keep the 401 and are recorded as approved differences). +const UNAUTHENTICATED_CODES: &[(&str, &str, &str, &str)] = &[ + ("GET", "/platform/api", "API_AUTH", "Unauthorized"), + ("GET", "/platform/api/all", "API_AUTH", "Unauthorized"), + ( + "POST", + "/platform/authorization/invalidate", + "AUTH005", + "Unauthorized", + ), + ( + "GET", + "/platform/authorization/status", + "AUTH005", + "Unauthorized", + ), + ( + "POST", + "/platform/authorization/refresh", + "AUTH003", + "Unable to validate credentials", + ), + ("GET", "/platform/user", "GTW998", "Unauthorized"), + ("GET", "/platform/user/all", "GTW998", "Unauthorized"), + ("GET", "/platform/user/me", "GTW998", "Unauthorized"), + ( + "POST", + "/platform/proto/{api_name}/{api_version}", + "GTW013", + "Unauthorized", + ), + ( + "POST", + "/platform/subscription/subscribe", + "GEN001", + "Unauthorized", + ), + ( + "POST", + "/platform/subscription/unsubscribe", + "GEN002", + "Unauthorized", + ), +]; + +fn unauthenticated(method: &Method, path: &str, _message: &str, request_id: &str) -> Response { + static CODES: OnceLock> = + OnceLock::new(); + let codes = CODES.get_or_init(|| { + UNAUTHENTICATED_CODES + .iter() + .filter_map(|(method, template, code, text)| { + Some((*method, template_regex(template)?, *code, *text)) + }) + .collect() + }); + let full = format!("/platform{path}"); + // The backfill route is registered ahead of the proto upload template. + if let Some((_, _, code, text)) = codes + .iter() + .filter(|_| path != "/proto/descriptors/backfill") + .find(|(allowed, matcher, _, _)| *allowed == method.as_str() && matcher.is_match(&full)) + { + return error(StatusCode::UNAUTHORIZED, code, text, request_id); + } + json_response( + StatusCode::UNAUTHORIZED, + json!({"detail": "Unauthorized"}), + request_id, + ) +} + +/// Frozen operations whose FastAPI signature declares a JSON request model. +fn json_body_operations() -> &'static [(Regex, String)] { + static TABLE: OnceLock> = OnceLock::new(); + TABLE.get_or_init(|| { + let Ok(contract) = python_openapi_contract() else { + return Vec::new(); + }; + let Some(paths) = contract.get("paths").and_then(Value::as_object) else { + return Vec::new(); + }; + let mut table = Vec::new(); + for (template, item) in paths { + let Some(item) = item.as_object() else { + continue; + }; + for (method, operation) in item { + let json = operation + .pointer("/requestBody/content") + .and_then(Value::as_object) + .is_some_and(|content| content.contains_key("application/json")); + if json && let Some(matcher) = template_regex(template) { + table.push((matcher, method.to_ascii_uppercase())); + } + } + } + table + }) +} + +/// Every frozen operation in registration order with its JSON body schema. +fn operation_body_schemas() -> &'static [(Regex, String, Option)] { + static TABLE: OnceLock)>> = OnceLock::new(); + TABLE.get_or_init(|| { + let Ok(contract) = python_openapi_contract() else { + return Vec::new(); + }; + let Some(paths) = contract.get("paths").and_then(Value::as_object) else { + return Vec::new(); + }; + let mut table = Vec::new(); + for (template, item) in paths { + let (Some(item), Some(matcher)) = (item.as_object(), template_regex(template)) else { + continue; + }; + for (method, operation) in item { + let schema = operation + .pointer("/requestBody/content/application~1json/schema") + .cloned(); + table.push((matcher.clone(), method.to_ascii_uppercase(), schema)); + } + } + table + }) +} + +/// FastAPI (pydantic v1) validation of a decoded JSON body against the model +/// the resolved route declares; `true` when the request must fail with 422. +fn body_fails_declared_model(path: &str, method: &Method, payload: &Value) -> bool { + let full = format!("/platform{path}"); + let Some((_, _, Some(schema))) = operation_body_schemas() + .iter() + .find(|(matcher, allowed, _)| allowed == method.as_str() && matcher.is_match(&full)) + else { + return false; + }; + let Ok(contract) = python_openapi_contract() else { + return false; + }; + !pydantic_accepts(contract, schema, payload, 0) +} + +fn pydantic_accepts(contract: &Value, schema: &Value, value: &Value, depth: usize) -> bool { + if depth > 16 { + return true; + } + if let Some(reference) = schema.get("$ref").and_then(Value::as_str) { + let pointer = reference.trim_start_matches('#'); + return contract + .pointer(pointer) + .is_none_or(|resolved| pydantic_accepts(contract, resolved, value, depth + 1)); + } + if let Some(options) = schema.get("anyOf").and_then(Value::as_array) { + return value.is_null() + || options + .iter() + .any(|option| pydantic_accepts(contract, option, value, depth + 1)); + } + if let Some(options) = schema.get("allOf").and_then(Value::as_array) { + return options + .iter() + .all(|option| pydantic_accepts(contract, option, value, depth + 1)); + } + let kind = schema + .get("type") + .and_then(Value::as_str) + .unwrap_or_else(|| { + if schema.get("properties").is_some() { + "object" + } else { + "" + } + }); + let number = |value: &Value| -> Option { + match value { + Value::Number(number) => number.as_f64(), + Value::Bool(flag) => Some(f64::from(u8::from(*flag))), + Value::String(text) => text.trim().parse::().ok(), + _ => None, + } + }; + let within_bounds = |amount: f64| { + let bound = |key: &str| schema.get(key).and_then(Value::as_f64); + bound("minimum").is_none_or(|limit| amount >= limit) + && bound("maximum").is_none_or(|limit| amount <= limit) + && bound("exclusiveMinimum").is_none_or(|limit| amount > limit) + && bound("exclusiveMaximum").is_none_or(|limit| amount < limit) + }; + match kind { + "object" => { + // pydantic v1 builds a dict from a list of key/value pairs too. + if let Some(pairs) = value.as_array() { + return schema.get("properties").is_none() + && pairs + .iter() + .all(|pair| pair.as_array().is_some_and(|pair| pair.len() == 2)); + } + let Some(object) = value.as_object() else { + return false; + }; + let properties = schema.get("properties").and_then(Value::as_object); + let required = schema + .get("required") + .and_then(Value::as_array) + .map(Vec::as_slice) + .unwrap_or_default(); + for name in required.iter().filter_map(Value::as_str) { + if object.get(name).is_none_or(Value::is_null) { + return false; + } + } + if let Some(properties) = properties { + for (name, property) in properties { + match object.get(name) { + None | Some(Value::Null) => {} + Some(item) => { + if !pydantic_accepts(contract, property, item, depth + 1) { + return false; + } + } + } + } + } + if let Some(additional) = schema.get("additionalProperties").filter(|v| v.is_object()) { + let known = properties.map(|map| map.keys().cloned().collect::>()); + for (name, item) in object { + if known.as_ref().is_some_and(|known| known.contains(name)) || item.is_null() { + continue; + } + if !pydantic_accepts(contract, additional, item, depth + 1) { + return false; + } + } + } + true + } + "array" => { + let Some(items) = value.as_array() else { + return false; + }; + let count = items.len() as f64; + if schema + .get("minItems") + .and_then(Value::as_f64) + .is_some_and(|min| count < min) + || schema + .get("maxItems") + .and_then(Value::as_f64) + .is_some_and(|max| count > max) + { + return false; + } + schema.get("items").is_none_or(|item_schema| { + items.iter().all(|item| { + item.is_null() || pydantic_accepts(contract, item_schema, item, depth + 1) + }) + }) + } + "string" => { + // pydantic v1 coerces numbers (and bools, an int subclass) to str. + let text = match value { + Value::String(text) => text.clone(), + Value::Number(number) => number.to_string(), + Value::Bool(flag) => if *flag { "True" } else { "False" }.to_owned(), + _ => return false, + }; + let length = text.chars().count() as f64; + schema + .get("minLength") + .and_then(Value::as_f64) + .is_none_or(|min| length >= min) + && schema + .get("maxLength") + .and_then(Value::as_f64) + .is_none_or(|max| length <= max) + } + "integer" => match value { + Value::String(text) => crate::python_scalar::parse_model_integer(text) + .is_some_and(|n| within_bounds(n as f64)), + other => number(other).is_some_and(|n| within_bounds(n.trunc())), + }, + "number" => number(value).is_some_and(within_bounds), + "boolean" => match value { + Value::Bool(_) => true, + Value::Number(number) => matches!(number.as_f64(), Some(n) if n == 0.0 || n == 1.0), + Value::String(text) => matches!( + text.to_ascii_lowercase().as_str(), + "0" | "1" | "true" | "false" | "t" | "f" | "yes" | "no" | "y" | "n" | "on" | "off" + ), + _ => false, + }, + _ => true, + } +} + +/// Whether the frozen operation declares a JSON request model (FastAPI then +/// rejects an undecodable JSON body with 422 before the handler runs). +fn declares_json_body(path: &str, method: &Method) -> bool { + let full = format!("/platform{path}"); + json_body_operations() + .iter() + .any(|(matcher, allowed)| allowed == method.as_str() && matcher.is_match(&full)) +} + +/// FastAPI only decodes a declared JSON model from a JSON request; any other +/// (or absent) Content-Type on a non-empty body fails validation with 422. +fn rejects_non_json_body(path: &str, method: &Method, headers: &HeaderMap, body: &[u8]) -> bool { + if body.is_empty() || content_type_is_json(headers) { + return false; + } + let full = format!("/platform{path}"); + json_body_operations() + .iter() + .any(|(matcher, allowed)| allowed == method.as_str() && matcher.is_match(&full)) +} + +/// Gateway routes in the pinned registration order, plus the Rust-native +/// `/api/features` probe and gRPC-Web CORS preflight. +const GATEWAY_ROUTES: &[(&str, &[&str])] = &[ + ("/api/status", &["GET"]), + ("/api/health", &["GET"]), + ("/api/caches", &["DELETE"]), + ("/api/caches", &["OPTIONS"]), + ( + "/api/rest/{path:path}", + &["PATCH", "DELETE", "GET", "POST", "PUT"], + ), + ("/api/rest/{path:path}", &["HEAD"]), + ("/api/rest/{path:path}", &["OPTIONS"]), + ("/api/soap/{path:path}", &["GET", "POST"]), + ("/api/grpc/{path:path}", &["GET", "POST"]), + ("/api/soap/{path:path}", &["OPTIONS"]), + ("/api/graphql/{path:path}", &["POST"]), + ("/api/graphql/{path:path}", &["OPTIONS"]), + ("/api/grpc/{path:path}", &["OPTIONS"]), + ("/api/features", &["GET"]), + // CORS preflights bypass this table, so only POST is a registered route. + ("/grpc-web/{api_name}/{service}/{method}", &["POST"]), +]; + +/// Starlette-style route resolution for the data plane: unknown paths are a +/// bare 404 and known paths with other methods a 405 naming the first route. +pub(crate) async fn gateway_route_guard( + request: axum::extract::Request, + next: axum::middleware::Next, +) -> Response { + static TABLE: OnceLock> = OnceLock::new(); + let table = TABLE.get_or_init(|| { + GATEWAY_ROUTES + .iter() + .filter_map(|(template, methods)| Some((template_regex(template)?, *methods))) + .collect() + }); + let path = request + .extensions() + .get::() + .map(|uri| uri.0.path().to_owned()) + .unwrap_or_else(|| request.uri().path().to_owned()); + // CORS preflights are answered by the CORS middleware ahead of routing. + if request.method() == Method::OPTIONS + && request.headers().contains_key(header::ORIGIN) + && request + .headers() + .contains_key(header::ACCESS_CONTROL_REQUEST_METHOD) + { + return next.run(request).await; + } + let request_id = request_id_from(request.headers()); + let mut partial = None; + for (matcher, methods) in table { + if !matcher.is_match(&path) { + continue; + } + if methods.contains(&request.method().as_str()) { + return next.run(request).await; + } + partial = partial.or(Some(*methods)); + } + let Some(methods) = partial else { + return json_response( + StatusCode::NOT_FOUND, + json!({"detail": "Not Found"}), + &request_id, + ); + }; + let mut response = json_response( + StatusCode::METHOD_NOT_ALLOWED, + json!({"detail": "Method Not Allowed"}), + &request_id, + ); + if let Ok(value) = HeaderValue::from_str(&methods.join(", ")) { + response.headers_mut().insert(header::ALLOW, value); + } + response +} + +fn python_route_match(path: &str, method: &Method) -> RouteMatch { + // The TLS administration surface is a Rust-native addition. + if path == "/tls" || path.starts_with("/tls/") { + return RouteMatch::Found; + } + let full = format!("/platform{path}"); + let mut partial: Option<&str> = None; + for (matcher, methods) in platform_route_table() { + if !matcher.is_match(&full) { + continue; + } + if methods.iter().any(|allowed| allowed == method.as_str()) { + return RouteMatch::Found; + } + partial = partial.or(methods.first().map(String::as_str)); + } + match partial { + Some(allow) => RouteMatch::MethodNotAllowed(allow.to_owned()), + None => RouteMatch::NotFound, + } +} + +fn platform_docs(path: &str, request_id: &str) -> Response { + let html = if path == "/redoc" { + r#"Doorman API"# + } else { + r#"Doorman API
"# + }; + let mut response = axum::response::Html(html).into_response(); + if let Ok(value) = HeaderValue::from_str(request_id) { + response.headers_mut().insert("x-request-id", value.clone()); + response.headers_mut().insert("request_id", value); + } + response +} + +async fn authorize( + state: &AppState, + headers: &HeaderMap, + method: &Method, + path: &str, + request_id: &str, +) -> Result { + let claims = verify_request_token(headers, &state.config.shared_storage) + .map_err(|_| unauthenticated(method, path, "Unauthorized", request_id))?; + let username = claims.sub.as_deref().unwrap_or(""); + let Some(storage) = &state.storage else { + return Err(unexpected(request_id)); + }; + if state.config.https_only + && path != "/user/admin" + && path != "/authorization" + && path != "/authorization/register" + && !csrf_matches(headers, storage, username).await + { + return Err(error( + StatusCode::UNAUTHORIZED, + "AUTH003", "Invalid CSRF token", request_id, )); } - if let Ok(Some(revocation)) = storage + if let Ok(Some(_revocation)) = storage .find_one( "revocations", &json!({"type": "revoke_all", "username": username}), ) .await { - let revoked_at = revocation - .get("revoked_at") - .and_then(Value::as_u64) - .unwrap_or(u64::MAX); - if (claims.iat.unwrap_or(0) as u64) <= revoked_at { - return Err(error( - StatusCode::UNAUTHORIZED, - "AUTH003", - "Token has been revoked", - request_id, - )); - } + return Err(unauthenticated( + method, + path, + "Token has been revoked", + request_id, + )); } if let Some(jti) = claims.jti.as_deref() { let filter = json!({"type": "jti", "username": username, "jti": jti}); @@ -5571,9 +7530,9 @@ async fn authorize( if expired { let _ = storage.delete_one("revocations", &filter).await; } else { - return Err(error( - StatusCode::UNAUTHORIZED, - "AUTH003", + return Err(unauthenticated( + method, + path, "Token has been revoked", request_id, )); @@ -5585,9 +7544,9 @@ async fn authorize( .await { Ok(Some(user)) if user.get("active").and_then(Value::as_bool) != Some(false) => Ok(claims), - Ok(Some(_)) => Err(error( - StatusCode::UNAUTHORIZED, - "AUTH003", + Ok(Some(_)) => Err(unauthenticated( + method, + path, "User is inactive", request_id, )), @@ -5620,21 +7579,33 @@ async fn is_admin_user(state: &AppState, username: &str) -> bool { let Some(storage) = &state.storage else { return false; }; - matches!( - storage.find_one("users", &json!({"username": username})).await, - Ok(Some(user)) if user.get("role").and_then(Value::as_str) == Some("admin") - ) + let Ok(Some(user)) = storage + .find_one("users", &json!({"username": username})) + .await + else { + return false; + }; + let Some(role_name) = user.get("role").and_then(Value::as_str) else { + return false; + }; + is_admin_role(state, role_name).await } -async fn role_permissions_within_actor(state: &AppState, username: &str, role: &Value) -> bool { - for permission in RBAC_PERMISSIONS { - if role.get(*permission).and_then(Value::as_bool) == Some(true) - && !has_permission(state, username, permission).await - { - return false; - } +async fn is_admin_role(state: &AppState, role_name: &str) -> bool { + if matches!( + role_name.trim().to_ascii_lowercase().as_str(), + "admin" | "platform admin" + ) { + return true; } - true + let Some(storage) = &state.storage else { + return false; + }; + matches!( + storage.find_one("roles", &json!({"role_name": role_name})).await, + Ok(Some(role)) if role.get("platform_admin").and_then(Value::as_bool) == Some(true) + || role.get("role_name").and_then(Value::as_str).is_some_and(|name| matches!(name.trim().to_ascii_lowercase().as_str(), "admin" | "platform admin")) + ) } async fn memory_dump( @@ -5643,6 +7614,10 @@ async fn memory_dump( username: &str, request_id: &str, ) -> Response { + let path = match memory_request_path(&payload) { + Ok(path) => path, + Err(errors) => return validation_errors(errors, request_id), + }; if !has_permission(state, username, "manage_security").await { return error( StatusCode::FORBIDDEN, @@ -5662,8 +7637,7 @@ async fn memory_dump( request_id, ); } - let path = payload.get("path").and_then(Value::as_str); - match crate::storage::snapshot::dump(storage, path).await { + match crate::storage::snapshot::dump(storage, path.as_deref()).await { Ok(path) => { audit::management_mutation(username, "memory.dump", "memory_snapshot", "success"); success( @@ -5700,6 +7674,10 @@ async fn memory_restore( username: &str, request_id: &str, ) -> Response { + let path = match memory_request_path(&payload) { + Ok(path) => path, + Err(errors) => return validation_errors(errors, request_id), + }; if !has_permission(state, username, "manage_security").await { return error( StatusCode::FORBIDDEN, @@ -5719,8 +7697,7 @@ async fn memory_restore( request_id, ); } - let path = payload.get("path").and_then(Value::as_str); - match crate::storage::snapshot::restore(storage, path).await { + match crate::storage::snapshot::restore(storage, path.as_deref()).await { Ok((version, created_at)) => { audit::management_mutation(username, "memory.restore", "memory_snapshot", "success"); success( @@ -5761,6 +7738,29 @@ async fn memory_restore( } } +fn memory_request_path(payload: &Value) -> Result, Vec> { + if payload.is_null() { + return Ok(None); + } + let Some(values) = payload.as_object() else { + return Err(vec![json!({ + "loc": ["body"], + "msg": "value is not a valid dict", + "type": "type_error.dict" + })]); + }; + match values.get("path") { + None | Some(Value::Null) => Ok(None), + Some(value) => security_setting_string(value).map(Some).ok_or_else(|| { + vec![json!({ + "loc": ["body", "path"], + "msg": "str type expected", + "type": "type_error.str" + })] + }), + } +} + async fn readiness(state: &AppState, privileged: bool, request_id: &str) -> Response { let (mongo_ok, redis_ok, memory_only, missing_grpc_descriptors, grpc_descriptor_errors) = if let Some(storage) = &state.storage { @@ -5807,42 +7807,23 @@ async fn readiness(state: &AppState, privileged: bool, request_id: &str) -> Resp vec![json!({"error": "storage is unavailable"})], ) }; - let memory_snapshot_healthy = state - .runtime - .memory_snapshot_healthy - .load(Ordering::Relaxed); - let metrics_persistence_healthy = state - .runtime - .metrics_persistence_healthy - .load(Ordering::Relaxed); - let revocation_purge_healthy = state - .runtime - .revocation_purge_healthy - .load(Ordering::Relaxed); - let activity_log_healthy = state.runtime.activity_log_healthy.load(Ordering::Relaxed); - let security_audit_log_healthy = state + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|time| time.as_secs() as i64) + .unwrap_or(i64::MAX); + let tls_certificates_valid = state .runtime - .security_audit_log_healthy - .load(Ordering::Relaxed); - let ready = mongo_ok - && redis_ok - && missing_grpc_descriptors == 0 - && memory_snapshot_healthy - && metrics_persistence_healthy - && revocation_purge_healthy - && activity_log_healthy - && security_audit_log_healthy; + .tls_certificate_expiries + .lock() + .map(|expiries| expiries.values().all(|expiry| *expiry > now)) + .unwrap_or(false); + let ready = mongo_ok && redis_ok && missing_grpc_descriptors == 0 && tls_certificates_valid; let status = if ready { "ready" } else { "degraded" }; - let status_code = if ready { - StatusCode::OK - } else { - StatusCode::SERVICE_UNAVAILABLE - }; if !privileged { - return success(status_code, json!({"status": status}), request_id); + return success(StatusCode::OK, json!({"status": status}), request_id); } success( - status_code, + StatusCode::OK, json!({ "status": status, "mongodb": mongo_ok, @@ -5851,11 +7832,7 @@ async fn readiness(state: &AppState, privileged: bool, request_id: &str) -> Resp "cache_backend": if memory_only { "memory" } else { "redis" }, "missing_grpc_descriptors": missing_grpc_descriptors, "grpc_descriptor_errors": grpc_descriptor_errors, - "memory_snapshot_healthy": memory_snapshot_healthy, - "metrics_persistence_healthy": metrics_persistence_healthy, - "revocation_purge_healthy": revocation_purge_healthy, - "activity_log_healthy": activity_log_healthy, - "security_audit_log_healthy": security_audit_log_healthy + "tls_certificates_valid": tls_certificates_valid }), request_id, ) @@ -5975,31 +7952,157 @@ fn format_dashboard_count(value: u64) -> String { formatted } -async fn monitor_metrics(state: &AppState, request_id: &str) -> Response { +async fn monitor_metrics( + state: &AppState, + query: &HashMap, + request_id: &str, +) -> Response { let analytics = global_analytics(); - let series = analytics.get_timeseries(); - let total_requests = series.iter().map(|point| point.requests).sum::(); - let total_errors = series.iter().map(|point| point.errors).sum::(); - let average_response_ms = if total_requests == 0 { - 0.0 - } else { - series - .iter() - .map(|point| point.latency_ms * point.requests as f64) - .sum::() + let minutes = match query.get("range").map(String::as_str).unwrap_or("24h") { + "1h" => 60, + "7d" => 60 * 24 * 7, + "30d" => 60 * 24 * 30, + _ => 60 * 24, + }; + let points = analytics.get_timeseries(); + let selected = points + .into_iter() + .rev() + .take(minutes) + .collect::>() + .into_iter() + .rev() + .collect::>(); + let group_by_day = query + .get("group") + .is_some_and(|value| value.eq_ignore_ascii_case("day")); + let mut series = if group_by_day { + let mut days = std::collections::BTreeMap::::new(); + for point in &selected { + let day = (point.timestamp / 86_400) * 86_400; + let bucket = days.entry(day).or_insert_with(|| AggregatedPoint { + timestamp: day, + ..AggregatedPoint::default() + }); + let prior = bucket.requests; + bucket.requests = bucket.requests.saturating_add(point.requests); + bucket.errors = bucket.errors.saturating_add(point.errors); + bucket.bytes_in = bucket.bytes_in.saturating_add(point.bytes_in); + bucket.bytes_out = bucket.bytes_out.saturating_add(point.bytes_out); + if bucket.requests > 0 { + bucket.latency_ms = ((bucket.latency_ms * prior as f64) + + (point.latency_ms * point.requests as f64)) + / bucket.requests as f64; + } + } + days.into_values() + .map(|point| { + json!({ + "timestamp": point.timestamp, + "count": point.requests, + "error_count": point.errors, + "avg_ms": point.latency_ms, + "bytes_in": point.bytes_in, + "bytes_out": point.bytes_out, + "error_rate": if point.requests == 0 { 0.0 } else { point.errors as f64 / point.requests as f64 }, + }) + }) + .collect::>() + } else { + selected + .iter() + .map(|point| { + json!({ + "timestamp": point.timestamp, + "count": point.requests, + "test_count": 0, + "error_count": point.errors, + "avg_ms": point.latency_ms, + "p95_ms": point.p95_ms(), + "bytes_in": point.bytes_in, + "bytes_out": point.bytes_out, + "error_rate": if point.requests == 0 { 0.0 } else { point.errors as f64 / point.requests as f64 }, + "upstream_timeouts": 0, + "retries": 0, + }) + }) + .collect::>() + }; + if query + .get("sort") + .is_some_and(|value| value.eq_ignore_ascii_case("desc")) + { + series.reverse(); + } + let total_requests = selected.iter().map(|point| point.requests).sum::(); + let total_errors = selected.iter().map(|point| point.errors).sum::(); + let average_response_ms = if total_requests == 0 { + 0.0 + } else { + selected + .iter() + .map(|point| point.latency_ms * point.requests as f64) + .sum::() / total_requests as f64 }; + // Python's MetricsStore.snapshot: top_apis/top_users/unique_users are + // aggregated only from the buckets in the selected range (with a + // documented global fallback when the range attributed no traffic), + // not from the process-global all-time counters. + let (range_start, range_end) = ( + selected.first().map_or(0, |point| point.timestamp), + selected.last().map_or(u64::MAX, |point| point.timestamp), + ); + let range_top_apis = analytics.get_top_apis_in_range(range_start, range_end, 10); + let top_apis: Vec<(String, u64)> = if range_top_apis.is_empty() { + analytics + .get_top_apis(10) + .into_iter() + .map(|entry| (entry.name, entry.count)) + .collect() + } else { + range_top_apis + .into_iter() + .map(|entry| (entry.name, entry.count)) + .collect() + }; + let range_top_users = analytics.get_top_users_in_range(range_start, range_end, 10); + let (top_users, unique_users): (Vec<(String, u64)>, usize) = if range_top_users.is_empty() { + let global = analytics.get_top_users(10); + let count = global.len(); + ( + global + .into_iter() + .map(|entry| (entry.name, entry.count)) + .collect(), + count, + ) + } else { + let count = range_top_users.len(); + ( + range_top_users + .into_iter() + .map(|entry| (entry.name, entry.count)) + .collect(), + count, + ) + }; success( StatusCode::OK, json!({ "uptime_seconds": state.runtime.started_at.elapsed().as_secs(), "active_requests": state.runtime.active_requests.load(std::sync::atomic::Ordering::Relaxed), "total_requests": total_requests, + "total_test_requests": 0, "total_errors": total_errors, "avg_response_ms": average_response_ms, "status_counts": analytics.get_status_distribution(), "series": series, - "top_apis": analytics.get_top_apis(10), + "top_apis": top_apis, + "top_users": top_users, + "unique_users": unique_users, + "total_upstream_timeouts": 0, + "total_retries": 0, "total_bytes_in": state.runtime.total_bytes_in.load(std::sync::atomic::Ordering::Relaxed), "total_bytes_out": state.runtime.total_bytes_out.load(std::sync::atomic::Ordering::Relaxed) }), @@ -6010,91 +8113,333 @@ async fn monitor_metrics(state: &AppState, request_id: &str) -> Response { async fn monitor_report( state: &AppState, query: &HashMap, - _request_id: &str, + request_id: &str, ) -> Response { + let start = query.get("start").map(String::as_str).unwrap_or(""); + let end = query.get("end").map(String::as_str).unwrap_or(""); + let start_ts = match parse_report_timestamp(start) { + Ok(ts) => ts, + Err(message_text) => { + return error(StatusCode::BAD_REQUEST, "MON003", &message_text, request_id); + } + }; + let end_ts = match parse_report_timestamp(end) { + Ok(ts) => ts, + Err(message_text) => { + return error(StatusCode::BAD_REQUEST, "MON003", &message_text, request_id); + } + }; + if end_ts < start_ts { + return error( + StatusCode::BAD_REQUEST, + "MON003", + "End date must be after start date", + request_id, + ); + } + + let (start_date, start_time_str) = report_date_time_utc(start_ts); + let (end_date, end_time_str) = report_date_time_utc(end_ts); + let directory = state + .config + .logs_dir + .clone() + .unwrap_or_else(|| std::path::PathBuf::from("platform-logs")); + let mut log_query = HashMap::new(); + log_query.insert("start_date".to_owned(), start_date); + log_query.insert("end_date".to_owned(), end_date); + log_query.insert("start_time".to_owned(), start_time_str); + log_query.insert("end_time".to_owned(), end_time_str); + // Python pages through up to 100 * 1000 = 100,000 log-service results; + // read_log_records already bounds total bytes read via MAX_LOG_EXPORT_BYTES. + let logs = read_log_records(&directory, &log_query, 100_000).unwrap_or_default(); + + let mut total = 0u64; + let mut errors = 0u64; + let mut total_ms = 0.0f64; + let mut status_counts: std::collections::BTreeMap = std::collections::BTreeMap::new(); + let mut api_totals: std::collections::HashMap = std::collections::HashMap::new(); + let mut api_errors: std::collections::HashMap = std::collections::HashMap::new(); + let mut user_totals: std::collections::HashMap = std::collections::HashMap::new(); + for record in &logs { + let endpoint = record.get("endpoint").and_then(Value::as_str).unwrap_or(""); + if endpoint.is_empty() || endpoint == "/platform" || endpoint.starts_with("/platform/") { + continue; + } + total += 1; + let status_code = record + .get("status_code") + .and_then(Value::as_str) + .and_then(|value| value.parse::().ok()) + .or_else(|| { + record + .get("status_code") + .and_then(Value::as_u64) + .map(|value| value as u16) + }); + let level = record + .get("level") + .and_then(Value::as_str) + .unwrap_or("") + .to_ascii_uppercase(); + let is_error = status_code.is_some_and(|code| code >= 400) + || !matches!(level.as_str(), "INFO" | "DEBUG"); + if is_error { + errors += 1; + } + if let Some(code) = status_code { + *status_counts.entry(code).or_default() += 1; + } + if let Some(response_time) = record.get("response_time").and_then(log_number) { + total_ms += response_time; + } + let api_key = report_api_from_endpoint(endpoint); + *api_totals.entry(api_key.clone()).or_default() += 1; + if is_error { + *api_errors.entry(api_key).or_default() += 1; + } + if let Some(username) = record + .get("user") + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + { + *user_totals.entry(username.to_owned()).or_default() += 1; + } + } + let analytics = global_analytics(); - let points = analytics.get_timeseries(); - let total_requests = points.iter().map(|point| point.requests).sum::(); - let total_errors = points.iter().map(|point| point.errors).sum::(); - let total_ms = points - .iter() - .map(|point| point.latency_ms * point.requests as f64) - .sum::(); - let average_ms = if total_requests == 0 { + let points_in_range = analytics + .get_timeseries() + .into_iter() + .filter(|point| { + point.timestamp >= start_ts.max(0) as u64 && point.timestamp <= end_ts.max(0) as u64 + }) + .collect::>(); + // Python's own defensive fallback: if no log lines matched (e.g. logs + // rotated away), reconstruct totals from the retained metrics buckets + // instead of reporting an empty period. + if total == 0 { + total = points_in_range.iter().map(|point| point.requests).sum(); + errors = points_in_range.iter().map(|point| point.errors).sum(); + total_ms = points_in_range + .iter() + .map(|point| point.latency_ms * point.requests as f64) + .sum(); + for (status, count) in analytics.get_status_distribution() { + if let Ok(code) = status.parse::() { + *status_counts.entry(code).or_default() += count; + } + } + for entry in analytics.get_top_apis_in_range( + start_ts.max(0) as u64, + end_ts.max(0) as u64, + usize::MAX, + ) { + api_totals.insert(entry.name.clone(), entry.count); + if entry.error_count > 0 { + api_errors.insert(entry.name, entry.error_count); + } + } + for entry in analytics.get_top_users_in_range( + start_ts.max(0) as u64, + end_ts.max(0) as u64, + usize::MAX, + ) { + user_totals.insert(entry.name, entry.count); + } + } + + let total_bytes_in: u64 = points_in_range.iter().map(|point| point.bytes_in).sum(); + let total_bytes_out: u64 = points_in_range.iter().map(|point| point.bytes_out).sum(); + let mut daily_bandwidth: std::collections::BTreeMap = + std::collections::BTreeMap::new(); + for point in &points_in_range { + let day_ts = (point.timestamp / 86_400) * 86_400; + let entry = daily_bandwidth.entry(day_ts).or_default(); + entry.0 += point.bytes_in; + entry.1 += point.bytes_out; + } + + let average_ms = if total == 0 { 0.0 } else { - total_ms / total_requests as f64 + total_ms / total as f64 }; - let successes = total_requests.saturating_sub(total_errors); - let success_rate = if total_requests == 0 { + let successes = total.saturating_sub(errors); + let success_rate = if total == 0 { 0.0 } else { - successes as f64 * 100.0 / total_requests as f64 + successes as f64 * 100.0 / total as f64 }; - let start = query.get("start").map(String::as_str).unwrap_or(""); - let end = query.get("end").map(String::as_str).unwrap_or(""); + let mut csv = String::new(); let _ = writeln!(csv, "Report,From,{},To,{}", csv_cell(start), csv_cell(end)); csv.push_str("Overview\n"); - let _ = writeln!(csv, "total_requests,{total_requests}"); - let _ = writeln!(csv, "total_errors,{total_errors}"); + let _ = writeln!(csv, "total_requests,{total}"); + let _ = writeln!(csv, "total_errors,{errors}"); let _ = writeln!(csv, "successes,{successes}"); let _ = writeln!(csv, "success_rate,{success_rate:.2}%"); let _ = writeln!(csv, "avg_response_ms,{average_ms:.2}"); csv.push_str("\nBandwidth Overview\n"); - let bytes_in = state - .runtime - .total_bytes_in - .load(std::sync::atomic::Ordering::Relaxed); - let bytes_out = state - .runtime - .total_bytes_out - .load(std::sync::atomic::Ordering::Relaxed); - let _ = writeln!(csv, "total_bytes_in,{bytes_in}"); - let _ = writeln!(csv, "total_bytes_out,{bytes_out}"); - let _ = writeln!(csv, "total_bytes,{}", bytes_in.saturating_add(bytes_out)); + let _ = writeln!(csv, "total_bytes_in,{total_bytes_in}"); + let _ = writeln!(csv, "total_bytes_out,{total_bytes_out}"); + let _ = writeln!( + csv, + "total_bytes,{}", + total_bytes_in.saturating_add(total_bytes_out) + ); csv.push_str("\nStatus Codes\nstatus,count\n"); - let mut status_counts = analytics - .get_status_distribution() - .into_iter() - .collect::>(); - status_counts.sort_by_key(|(status, _)| status.parse::().unwrap_or(u16::MAX)); - for (status, count) in status_counts { - let _ = writeln!(csv, "{},{}", csv_cell(&status), count); + for (status, count) in &status_counts { + let _ = writeln!(csv, "{status},{count}"); } csv.push_str("\nAPI Usage\napi,total,errors,successes,success_rate\n"); - for api in analytics.get_top_apis(analytics.api_count()) { - let successes = api.count.saturating_sub(api.error_count); - let rate = if api.count == 0 { + let mut apis = api_totals.into_iter().collect::>(); + apis.sort_by(|left, right| right.1.cmp(&left.1).then_with(|| left.0.cmp(&right.0))); + for (api, count) in apis { + let err = api_errors.get(&api).copied().unwrap_or_default(); + let succ = count.saturating_sub(err); + let rate = if count == 0 { 0.0 } else { - successes as f64 * 100.0 / api.count as f64 + succ as f64 * 100.0 / count as f64 }; + let _ = writeln!(csv, "{},{count},{err},{succ},{rate:.2}%", csv_cell(&api)); + } + csv.push_str("\nUser Usage\nusername,requests\n"); + let mut users = user_totals.into_iter().collect::>(); + users.sort_by(|left, right| right.1.cmp(&left.1).then_with(|| left.0.cmp(&right.0))); + for (user, count) in users { + let _ = writeln!(csv, "{},{count}", csv_cell(&user)); + } + csv.push_str("\nBandwidth (per day, UTC)\ndate,bytes_in,bytes_out,total\n"); + for (day_ts, (bytes_in, bytes_out)) in daily_bandwidth { + let (date_str, _) = report_date_time_utc(day_ts as i64); let _ = writeln!( csv, - "{},{},{},{},{rate:.2}%", - csv_cell(&api.name), - api.count, - api.error_count, - successes + "{date_str},{bytes_in},{bytes_out},{}", + bytes_in.saturating_add(bytes_out) ); } - csv.push_str("\nUser Usage\nusername,requests\n"); - for user in analytics.get_top_users(analytics.user_count()) { - let _ = writeln!(csv, "{},{}", csv_cell(&user.name), user.count); + + let mut response = ([(header::CONTENT_TYPE, "text/csv; charset=utf-8")], csv).into_response(); + if let Ok(value) = HeaderValue::from_str(&format!( + "attachment; filename=doorman_report_{start}_to_{end}.csv" + )) { + response + .headers_mut() + .insert(header::CONTENT_DISPOSITION, value); + } + response +} + +/// `api` key derivation matching Python's `_api_from_endpoint` in +/// `monitor_routes.py::generate_report`. +fn report_api_from_endpoint(endpoint: &str) -> String { + if let Some(rest) = endpoint.strip_prefix("/api/rest/") { + let parts = rest.split('/').collect::>(); + return format!("rest:{}", parts.first().copied().unwrap_or("unknown")); } + if let Some(rest) = endpoint.strip_prefix("/api/graphql/") { + let name = rest + .rsplit('/') + .next() + .filter(|value| !value.is_empty()) + .unwrap_or("unknown"); + return format!("graphql:{name}"); + } + if let Some(rest) = endpoint.strip_prefix("/api/soap/") { + let name = rest + .rsplit('/') + .next() + .filter(|value| !value.is_empty()) + .unwrap_or("unknown"); + return format!("soap:{name}"); + } + "platform".to_owned() +} + +/// Python's `_parse_ts`: try `%Y-%m-%dT%H:%M`, then `%Y-%m-%d`, then a +/// best-effort ISO-8601 fallback via `datetime.fromisoformat`. A naive +/// `datetime.strptime(...).timestamp()` in Python interprets the value in the +/// process's local timezone; this deployment (like the pinned reference's +/// container images) runs in UTC, so the two are equivalent here. +fn parse_report_timestamp(value: &str) -> Result { + let invalid = || "Invalid date format. Use YYYY-MM-DD or YYYY-MM-DDTHH:MM".to_owned(); + let parse_date = |text: &str| -> Option { + let mut parts = text.splitn(3, '-'); + let year = parts.next()?.parse::().ok()?; + let month = parts.next()?.parse::().ok()?; + let day = parts.next()?.parse::().ok()?; + time::Date::from_calendar_date(year, time::Month::try_from(month).ok()?, day).ok() + }; + let parse_time = |text: &str| -> Option { + // Strict "%H:%M" callers pass exactly two fields; the fromisoformat + // fallback below may also hand this "%H:%M:%S[.ffffff]", in which + // case the seconds (whole part only; unix timestamps here are + // second-resolution) are preserved rather than silently dropped. + let mut fields = text.splitn(3, ':'); + let hour = fields.next()?; + let minute = fields.next()?; + let second = fields + .next() + .and_then(|value| value.split(['.', ',']).next()) + .unwrap_or("0"); + time::Time::from_hms( + hour.parse().ok()?, + minute.parse().ok()?, + second.parse().ok()?, + ) + .ok() + }; + let to_ts = |date: time::Date, time_value: time::Time| { + time::PrimitiveDateTime::new(date, time_value) + .assume_utc() + .unix_timestamp() + }; + + if let Some((date_part, time_part)) = value.split_once('T') + && let (Some(date), Some(time_value)) = (parse_date(date_part), parse_time(time_part)) + { + return Ok(to_ts(date, time_value)); + } + if let Some(date) = parse_date(value) { + return Ok(to_ts(date, time::Time::MIDNIGHT)); + } + // fromisoformat fallback: accept "date time" (space separator) and an + // optional trailing "Z"/offset the two strict formats above reject. + let normalized = value.trim().replace('Z', "+00:00"); + let date_part = normalized.split(['T', ' ']).next().unwrap_or_default(); + let Some(date) = parse_date(date_part) else { + return Err(invalid()); + }; + let time_part = normalized + .split_once(['T', ' ']) + .map(|(_, rest)| rest) + .map(|rest| rest.split(['+', '-']).next().unwrap_or_default()) + .unwrap_or_default(); + let time_value = if time_part.is_empty() { + time::Time::MIDNIGHT + } else { + parse_time(time_part).ok_or_else(invalid)? + }; + Ok(to_ts(date, time_value)) +} +/// Formats a unix timestamp as UTC `(YYYY-MM-DD, HH:MM)`, matching Python's +/// `datetime.fromtimestamp(ts, timezone.utc).strftime(...)`. +fn report_date_time_utc(timestamp: i64) -> (String, String) { + let Ok(value) = time::OffsetDateTime::from_unix_timestamp(timestamp) else { + return (String::new(), String::new()); + }; ( - [ - (header::CONTENT_TYPE, "text/csv; charset=utf-8"), - ( - header::CONTENT_DISPOSITION, - "attachment; filename=doorman_report.csv", - ), - ], - csv, + format!( + "{:04}-{:02}-{:02}", + value.year(), + value.month() as u8, + value.day() + ), + format!("{:02}:{:02}", value.hour(), value.minute()), ) - .into_response() } fn csv_cell(value: &str) -> String { @@ -6104,6 +8449,80 @@ fn csv_cell(value: &str) -> String { value.to_owned() } } +/// Matches FastAPI's automatic validation of a typed integer query parameter +/// (e.g. `page: int = 1`): a present-but-non-numeric value never reaches the +/// route handler and instead produces a 422 with this `detail` shape, rather +/// than being silently coerced to a default. +fn int_query_validation_error( + query: &HashMap, + fields: &[&str], + request_id: &str, +) -> Option { + for field in fields { + if query + .get(*field) + .is_some_and(|value| value.parse::().is_err()) + { + return Some(validation_errors( + vec![json!({ + "loc": ["query", field], + "msg": "value is not a valid integer", + "type": "type_error.integer" + })], + request_id, + )); + } + } + None +} + +fn analytics_query_error( + query: &HashMap, + has_limit: bool, + request_id: &str, +) -> Option { + if let Some(response) = int_query_validation_error(query, &["start_ts", "end_ts"], request_id) { + return Some(response); + } + if !has_limit { + return None; + } + let raw = query.get("limit")?; + let Ok(limit) = raw.parse::() else { + return Some(validation_errors( + vec![json!({ + "loc": ["query", "limit"], + "msg": "value is not a valid integer", + "type": "type_error.integer" + })], + request_id, + )); + }; + if limit < 1 { + return Some(validation_errors( + vec![json!({ + "loc": ["query", "limit"], + "msg": "ensure this value is greater than or equal to 1", + "type": "value_error.number.not_ge", + "ctx": {"limit_value": 1} + })], + request_id, + )); + } + if limit > 100 { + return Some(validation_errors( + vec![json!({ + "loc": ["query", "limit"], + "msg": "ensure this value is less than or equal to 100", + "type": "value_error.number.not_le", + "ctx": {"limit_value": 100} + })], + request_id, + )); + } + None +} + fn analytics_denied(request_id: &str) -> Response { error( StatusCode::FORBIDDEN, @@ -6113,17 +8532,23 @@ fn analytics_denied(request_id: &str) -> Response { ) } -fn analytics_time_range(query: &HashMap) -> (u64, u64) { +fn analytics_time_range(query: &HashMap) -> (i64, i64) { if let (Some(start), Some(end)) = ( - query.get("start_ts").and_then(|value| value.parse().ok()), - query.get("end_ts").and_then(|value| value.parse().ok()), + query + .get("start_ts") + .and_then(|value| value.parse().ok()) + .filter(|value| *value != 0), + query + .get("end_ts") + .and_then(|value| value.parse().ok()) + .filter(|value| *value != 0), ) { return (start, end); } let end = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() - .as_secs(); + .as_secs() as i64; let duration = match query.get("range").map(String::as_str).unwrap_or("24h") { "1h" => 3_600, "7d" => 604_800, @@ -6141,26 +8566,23 @@ fn analytics_limit(query: &HashMap) -> usize { .clamp(1, 100) } +/// Python's `PercentileMetrics.calculate`: pool every raw latency sample from +/// every bucket (not per-bucket averages), sort, and index each percentile at +/// `max(0, int(p * n) - 1)`. fn analytics_percentiles(points: &[AggregatedPoint]) -> Value { - let mut values = points + let values: Vec = points .iter() - .map(|point| point.latency_ms) - .collect::>(); - values.sort_by(|left, right| left.total_cmp(right)); - let percentile = |fraction: f64| { - if values.is_empty() { - 0.0 - } else { - let index = ((values.len() - 1) as f64 * fraction).round() as usize; - values[index] - } - }; + .flat_map(|point| point.latencies_ms.iter().copied()) + .collect(); + let (min, max) = ( + values.iter().copied().fold(f64::INFINITY, f64::min), + values.iter().copied().fold(f64::NEG_INFINITY, f64::max), + ); + let (p50, p75, p90, p95, p99) = pooled_percentiles(values.iter().copied()); json!({ - "p50": percentile(0.50), - "p75": percentile(0.75), - "p90": percentile(0.90), - "p95": percentile(0.95), - "p99": percentile(0.99) + "p50": p50, "p75": p75, "p90": p90, "p95": p95, "p99": p99, + "min": if values.is_empty() { 0.0 } else { min }, + "max": if values.is_empty() { 0.0 } else { max } }) } @@ -6168,19 +8590,20 @@ fn analytics_entity(entity: &EntityCounter, key: &str) -> Value { json!({key: entity.name, "count": entity.count}) } -fn analytics_endpoint(entity: &EntityCounter) -> Value { +fn analytics_endpoint(entity: &EndpointEntity) -> Value { let error_rate = if entity.count == 0 { 0.0 } else { entity.error_count as f64 / entity.count as f64 }; + let (p50, p75, p90, p95, p99) = entity.percentiles; json!({ "endpoint_uri": entity.name, "count": entity.count, "error_count": entity.error_count, "error_rate": error_rate, - "avg_ms": 0.0, - "percentiles": {"p50": 0.0, "p75": 0.0, "p90": 0.0, "p95": 0.0, "p99": 0.0} + "avg_ms": entity.avg_ms, + "percentiles": {"p50": p50, "p75": p75, "p90": p90, "p95": p95, "p99": p99} }) } @@ -6204,13 +8627,7 @@ fn analytics_series_point(point: &AggregatedPoint, metric_type: Option<&str>) -> value.insert("avg_ms".to_owned(), json!(point.latency_ms)); value.insert( "percentiles".to_owned(), - json!({ - "p50": point.latency_ms, - "p75": point.latency_ms, - "p90": point.latency_ms, - "p95": point.latency_ms, - "p99": point.latency_ms - }), + analytics_percentiles(std::slice::from_ref(point)), ); } Some("bandwidth") => { @@ -6225,13 +8642,7 @@ fn analytics_series_point(point: &AggregatedPoint, metric_type: Option<&str>) -> value.insert("avg_ms".to_owned(), json!(point.latency_ms)); value.insert( "percentiles".to_owned(), - json!({ - "p50": point.latency_ms, - "p75": point.latency_ms, - "p90": point.latency_ms, - "p95": point.latency_ms, - "p99": point.latency_ms - }), + analytics_percentiles(std::slice::from_ref(point)), ); value.insert("bytes_in".to_owned(), json!(point.bytes_in)); value.insert("bytes_out".to_owned(), json!(point.bytes_out)); @@ -6243,7 +8654,8 @@ fn analytics_series_point(point: &AggregatedPoint, metric_type: Option<&str>) -> fn analytics_timeseries(query: &HashMap, request_id: &str) -> Response { let (start_ts, end_ts) = analytics_time_range(query); - let points = global_analytics().get_timeseries_range(start_ts, end_ts); + let points = + global_analytics().get_timeseries_range(start_ts.max(0) as u64, end_ts.max(0) as u64); let metric_type = query.get("metric_type").map(String::as_str); let series = points .iter() @@ -6265,44 +8677,53 @@ fn analytics_top(kind: &str, query: &HashMap, request_id: &str) let (start_ts, end_ts) = analytics_time_range(query); let limit = analytics_limit(query); let analytics = global_analytics(); + let (start_bound, end_bound) = (start_ts.max(0) as u64, end_ts.max(0) as u64); let response = match kind { "api" => { - let entries = analytics - .get_top_apis(limit) + // Reconstructed from only the minute buckets in [start_ts, end_ts], + // matching Python's enhanced_metrics_store.get_snapshot-backed top_apis + // rather than an all-time process-global total. + let all = analytics.get_top_apis_in_range(start_bound, end_bound, usize::MAX); + let entries = all .iter() + .take(limit) .map(|entry| analytics_entity(entry, "api")) .collect::>(); json!({ "time_range": {"start_ts": start_ts, "end_ts": end_ts}, "top_apis": entries, - "total_apis": analytics.api_count() + "total_apis": all.len() }) } "user" => { - let entries = analytics - .get_top_users(limit) + let all = analytics.get_top_users_in_range(start_bound, end_bound, usize::MAX); + let entries = all .iter() + .take(limit) .map(|entry| analytics_entity(entry, "user")) .collect::>(); json!({ "time_range": {"start_ts": start_ts, "end_ts": end_ts}, "top_users": entries, - "total_users": analytics.user_count() + "total_users": all.len() }) } _ => { - let mut entries = analytics - .get_top_endpoints(analytics.endpoint_count()) - .iter() - .map(analytics_endpoint) - .collect::>(); + let all = analytics.get_top_endpoints_in_range(start_bound, end_bound, usize::MAX); + let mut entries = all.iter().map(analytics_endpoint).collect::>(); + let total_endpoints = entries.len(); let sort_by = query.get("sort_by").map(String::as_str).unwrap_or("count"); - if sort_by == "error_rate" { + let sort_key = match sort_by { + "error_rate" => Some("error_rate"), + "avg_ms" => Some("avg_ms"), + _ => None, + }; + if let Some(key) = sort_key { entries.sort_by(|left, right| { - right["error_rate"] + right[key] .as_f64() .unwrap_or_default() - .total_cmp(&left["error_rate"].as_f64().unwrap_or_default()) + .total_cmp(&left[key].as_f64().unwrap_or_default()) }); } entries.truncate(limit); @@ -6310,7 +8731,7 @@ fn analytics_top(kind: &str, query: &HashMap, request_id: &str) "time_range": {"start_ts": start_ts, "end_ts": end_ts}, "sort_by": sort_by, "top_endpoints": entries, - "total_endpoints": analytics.endpoint_count() + "total_endpoints": total_endpoints }) } }; @@ -6328,7 +8749,7 @@ async fn analytics_overview( } let (start_ts, end_ts) = analytics_time_range(query); let analytics = global_analytics(); - let points = analytics.get_timeseries_range(start_ts, end_ts); + let points = analytics.get_timeseries_range(start_ts.max(0) as u64, end_ts.max(0) as u64); let total_requests = points.iter().map(|point| point.requests).sum::(); let total_errors = points.iter().map(|point| point.errors).sum::(); let total_ms = points @@ -6706,13 +9127,13 @@ async fn config_export( query: &HashMap, request_id: &str, ) -> Response { - let permission = match only { - None => "manage_gateway", - Some("apis") => "manage_apis", - Some("endpoints") => "manage_endpoints", - Some("roles") => "manage_roles", - Some("groups") => "manage_groups", - Some("routings") => "manage_routings", + let (permission, permission_code) = match only { + None => ("manage_gateway", "CFG001"), + Some("apis") => ("manage_apis", "CFG002"), + Some("endpoints") => ("manage_endpoints", "CFG007"), + Some("roles") => ("manage_roles", "CFG003"), + Some("groups") => ("manage_groups", "CFG004"), + Some("routings") => ("manage_routings", "CFG005"), Some(_) => { return error( StatusCode::NOT_FOUND, @@ -6725,7 +9146,7 @@ async fn config_export( if !has_permission(state, username, permission).await { return error( StatusCode::FORBIDDEN, - "CFG001", + permission_code, "Insufficient permissions", request_id, ); @@ -6734,11 +9155,9 @@ async fn config_export( return unexpected(request_id); }; if let Some("apis") = only { - if let Some(api_name) = query.get("api_name") { - let api_version = query - .get("api_version") - .cloned() - .unwrap_or_else(|| "v1".to_owned()); + if let (Some(api_name), Some(api_version)) = + (query.get("api_name"), query.get("api_version")) + { let filter = json!({"api_name": api_name, "api_version": api_version}); let api = match storage.find_one("apis", &filter).await { Ok(Some(api)) => api, @@ -6766,33 +9185,64 @@ async fn config_export( } } if let Some(collection) = only { - let identifying_filter = match collection { - "roles" => query - .get("role_name") - .map(|value| json!({"role_name": value})), - "groups" => query - .get("group_name") - .map(|value| json!({"group_name": value})), - "routings" => query - .get("client_key") - .map(|value| json!({"client_key": value})), + let named_export = match collection { + "roles" => query.get("role_name").map(|value| { + ( + json!({"role_name": value}), + "role", + "CFG405", + "Role not found", + ) + }), + "groups" => query.get("group_name").map(|value| { + ( + json!({"group_name": value}), + "group", + "CFG406", + "Group not found", + ) + }), + "routings" => query.get("client_key").map(|value| { + ( + json!({"client_key": value}), + "routing", + "CFG407", + "Routing not found", + ) + }), _ => None, }; - if let Some(filter) = identifying_filter { - match storage.find_one(collection, &filter).await { - Ok(Some(_)) => {} - Ok(None) => { - return error( - StatusCode::NOT_FOUND, - "CFG404", - "Configuration item not found", + if let Some((filter, response_key, missing_code, missing_text)) = named_export { + return match storage.find_one(collection, &filter).await { + Ok(Some(value)) => { + audit::config_export(username, only); + success( + StatusCode::OK, + json!({response_key: strip_internal(value)}), request_id, - ); + ) } - Err(_) => return unexpected(request_id), - } + Ok(None) => error( + StatusCode::NOT_FOUND, + missing_code, + missing_text, + request_id, + ), + Err(_) => unexpected(request_id), + }; } - let values = match storage.find_many(collection, &json!({})).await { + let filter = if collection == "endpoints" { + let mut filter = Map::new(); + for key in ["api_name", "api_version"] { + if let Some(value) = query.get(key) { + filter.insert(key.to_owned(), json!(value)); + } + } + Value::Object(filter) + } else { + json!({}) + }; + let values = match storage.find_many(collection, &filter).await { Ok(values) => values.into_iter().map(strip_internal).collect::>(), Err(_) => return unexpected(request_id), }; @@ -6820,7 +9270,7 @@ async fn config_import( if !has_permission(state, username, "manage_gateway").await { return error( StatusCode::FORBIDDEN, - "CFG001", + "CFG006", "Insufficient permissions", request_id, ); @@ -6828,105 +9278,201 @@ async fn config_import( let Some(storage) = &state.storage else { return unexpected(request_id); }; - let imported = match storage - .import_configuration_atomically( - &payload, + let mut snapshot_data = Map::new(); + for collection in ["apis", "endpoints", "roles", "groups", "routings"] { + let values = match storage.find_many(collection, &json!({})).await { + Ok(values) => values.into_iter().map(strip_internal).collect::>(), + Err(_) => return unexpected(request_id), + }; + snapshot_data.insert(collection.to_owned(), json!(values)); + } + let timestamp = timestamp_now_naive().replace('T', " "); + if storage + .insert_one( + "config_snapshots", json!({ "snapshot_id": Uuid::new_v4().to_string(), - "created_at": timestamp_now(), - "actor": username + "timestamp": timestamp, + "created_by": username, + "data": snapshot_data, }), ) .await + .is_err() { - Ok(imported) => imported, - Err(_) => return unexpected(request_id), - }; + return unexpected(request_id); + } + let mut imported = Map::new(); + for collection in ["apis", "endpoints", "roles", "groups", "routings"] { + let values = match payload.get(collection) { + None => Vec::new(), + Some(value) if !python_truthy_json(value) => Vec::new(), + Some(Value::Array(values)) => values.clone(), + Some(_) => return unexpected(request_id), + }; + let mut count = 0; + for value in values { + let Some(document) = value.as_object() else { + return unexpected(request_id); + }; + if config_upsert(storage, collection, document).await.is_err() { + return unexpected(request_id); + } + count += 1; + } + imported.insert(collection.to_owned(), json!(count)); + } audit::management_mutation(username, "config.import", "configuration", "success"); - success(StatusCode::OK, json!({"imported": imported}), request_id) + success( + StatusCode::OK, + json!({"imported": Value::Object(imported)}), + request_id, + ) } -async fn config_rollback( - state: &AppState, - username: &str, - payload: Value, +async fn config_upsert( + storage: &SharedStorage, + collection: &str, + document: &Map, +) -> Result<(), crate::storage::runtime::StorageError> { + let required: &[&str] = match collection { + "apis" => &["api_name", "api_version"], + "endpoints" => &["api_name", "api_version", "endpoint_method", "endpoint_uri"], + "roles" => &["role_name"], + "groups" => &["group_name"], + "routings" => &["client_key"], + _ => return Ok(()), + }; + if required.iter().any(|field| { + document + .get(*field) + .is_none_or(|value| !python_truthy_json(value)) + }) { + return Ok(()); + } + let filter = Value::Object( + required + .iter() + .map(|field| ((*field).to_owned(), document[*field].clone())) + .collect(), + ); + let mut value = Value::Object(document.clone()); + strip_mongo_id(&mut value); + if collection == "endpoints" { + if let Some(api) = storage + .find_one( + "apis", + &json!({ + "api_name": document["api_name"], + "api_version": document["api_version"], + }), + ) + .await? + && let Some(api_id) = api.get("api_id") + { + value["api_id"] = api_id.clone(); + } + if value.get("endpoint_id").is_none() { + value["endpoint_id"] = json!(Uuid::new_v4().to_string()); + } + } + let existing = storage.find_one(collection, &filter).await?; + if collection == "apis" { + if existing.is_none() { + if value.get("api_id").is_none() { + value["api_id"] = json!(Uuid::new_v4().to_string()); + } + if value.get("api_path").is_none() { + value["api_path"] = json!(format!( + "/{}/{}", + python_string(&document["api_name"]), + python_string(&document["api_version"]), + )); + } + } else if value.get("api_id").is_none() + && let Some(api_id) = existing.as_ref().and_then(|api| api.get("api_id")) + { + value["api_id"] = api_id.clone(); + } + } + if existing.is_some() { + storage.update_one(collection, &filter, &value).await?; + } else { + storage.insert_one(collection, value).await?; + } + Ok(()) +} + +fn python_string(value: &Value) -> String { + security_setting_string(value).unwrap_or_else(|| value.to_string()) +} + +async fn config_rollback( + state: &AppState, + username: &str, + _payload: Value, request_id: &str, ) -> Response { if !has_permission(state, username, "manage_gateway").await { - return error( - StatusCode::FORBIDDEN, - "CFG006", - "Insufficient permissions", + return http_detail( + StatusCode::INTERNAL_SERVER_ERROR, + "Rollback failed", request_id, ); } let Some(storage) = &state.storage else { return unexpected(request_id); }; - let requested_snapshot_id = payload.get("snapshot_id").and_then(Value::as_str); - let snapshot = if let Some(snapshot_id) = requested_snapshot_id { - match storage - .find_one("config_snapshots", &json!({"snapshot_id": snapshot_id})) - .await - { - Ok(Some(snapshot)) => snapshot, - Ok(None) => { - return error( - StatusCode::NOT_FOUND, - "CFG404", - "Configuration snapshot not found", - request_id, - ); - } - Err(_) => return unexpected(request_id), - } - } else { - let snapshots = match storage.find_many("config_snapshots", &json!({})).await { - Ok(snapshots) => snapshots, - Err(_) => return unexpected(request_id), - }; - let Some(snapshot) = snapshots.into_iter().max_by(|left, right| { - left.get("created_at") - .and_then(Value::as_str) - .cmp(&right.get("created_at").and_then(Value::as_str)) - }) else { - return error( - StatusCode::BAD_REQUEST, - "CFG007", - "No configuration snapshot available", + let snapshots = match storage.find_many("config_snapshots", &json!({})).await { + Ok(snapshots) => snapshots, + Err(_) => { + return http_detail( + StatusCode::INTERNAL_SERVER_ERROR, + "Rollback failed", request_id, ); - }; - snapshot + } }; - let Some(data) = snapshot.get("data").and_then(Value::as_object) else { + let Some(snapshot) = snapshots.into_iter().max_by(|left, right| { + left.get("timestamp") + .and_then(Value::as_str) + .cmp(&right.get("timestamp").and_then(Value::as_str)) + }) else { return error( - StatusCode::BAD_REQUEST, - "CFG007", - "Configuration snapshot is invalid", + StatusCode::NOT_FOUND, + "CFG404", + "No snapshot found", + request_id, + ); + }; + let Some(data) = snapshot.get("data").and_then(Value::as_object) else { + return http_detail( + StatusCode::INTERNAL_SERVER_ERROR, + "Rollback failed", request_id, ); }; - let mut replacements = Vec::new(); for collection in ["apis", "endpoints", "roles", "groups", "routings"] { let Some(values) = data.get(collection).and_then(Value::as_array) else { - return error( - StatusCode::BAD_REQUEST, - "CFG007", - "Configuration snapshot is invalid", + return http_detail( + StatusCode::INTERNAL_SERVER_ERROR, + "Rollback failed", request_id, ); }; - replacements.push((collection.to_owned(), values.clone())); - } - if storage - .replace_collections_atomically(&replacements, None) - .await - .is_err() - { - return unexpected(request_id); + if storage + .replace_collection(collection, values.clone()) + .await + .is_err() + { + return http_detail( + StatusCode::INTERNAL_SERVER_ERROR, + "Rollback failed", + request_id, + ); + } } - let restored_to = snapshot.get("created_at").cloned().unwrap_or(Value::Null); + let restored_to = snapshot.get("timestamp").cloned().unwrap_or(Value::Null); let snapshot_id = snapshot.get("snapshot_id").cloned().unwrap_or(Value::Null); audit::management_mutation( username, @@ -6936,7 +9482,7 @@ async fn config_rollback( ); success( StatusCode::OK, - json!({"message": format!("Configuration rolled back to {}", restored_to.as_str().unwrap_or("latest")), "restored_to": restored_to, "snapshot_id": snapshot_id}), + json!({"message": format!("Configuration rolled back to {}", restored_to.as_str().unwrap_or("latest"))}), request_id, ) } @@ -6978,7 +9524,15 @@ async fn analytics_detail( endpoint.name.starts_with(&endpoint_prefix) || endpoint.name.starts_with(&gateway_prefix) }) - .map(analytics_endpoint) + .map(|endpoint| { + analytics_endpoint(&EndpointEntity { + name: endpoint.name.clone(), + count: endpoint.count, + error_count: endpoint.error_count, + avg_ms: 0.0, + percentiles: (0.0, 0.0, 0.0, 0.0, 0.0), + }) + }) .collect::>(); return success( StatusCode::OK, @@ -7087,43 +9641,72 @@ fn restart_required_keys() -> Value { ) } -async fn demo_seed(state: &AppState, username: &str, request_id: &str) -> Response { - if username != "admin" { +async fn demo_seed( + state: &AppState, + username: &str, + query: &HashMap, + request_id: &str, +) -> Response { + // Python: `if not await is_admin_user(username)` -- a role-based admin + // check, not a literal username comparison. + if !is_admin_user(state, username).await { return error( StatusCode::FORBIDDEN, - "AUTH006", - "Admin access required", + "DEMO001", + "Permission denied to run seeder", request_id, ); } let Some(storage) = &state.storage else { return unexpected(request_id); }; + // Python has no such gate and will happily seed a real MongoDB; Rust + // deliberately refuses outside memory mode as an additional safety guard + // against seeding synthetic data into a production database. This is a + // documented approved addition, not a parity gap, so it uses its own + // error code rather than colliding with DEMO001 above. if !storage.is_memory() { return error( StatusCode::BAD_REQUEST, - "DEMO001", + "DEMO002", "Demo seed is available only in memory mode", request_id, ); } - for (name, version, server) in [ - ("customers", "v1", "http://localhost:8080"), - ("orders", "v1", "http://localhost:8081"), - ("weather", "v1", "http://localhost:8082"), - ] { - if storage - .find_one("apis", &json!({"api_name": name, "api_version": version})) - .await - .ok() - .flatten() - .is_none() - { - let _ = storage.insert_one("apis", json!({"api_name": name, "api_version": version, "api_id": Uuid::new_v4().to_string(), "api_path": format!("/{name}/{version}"), "api_servers": [server], "api_type": "REST", "api_public": false, "api_auth_required": true, "active": true})).await; - let _ = storage.insert_one("endpoints", json!({"api_name": name, "api_version": version, "endpoint_id": Uuid::new_v4().to_string(), "endpoint_method": "GET", "endpoint_uri": "/status", "client_uri": "/status", "endpoint_description": "Demo status"})).await; + let query_usize = |key: &str, default: usize| { + query + .get(key) + .and_then(|value| value.parse::().ok()) + .unwrap_or(default) + }; + let options = crate::demo_seed::SeedOptions { + users: query_usize("users", 40), + apis: query_usize("apis", 15), + endpoints: query_usize("endpoints", 6), + groups: query_usize("groups", 8), + protos: query_usize("protos", 6), + logs: query_usize("logs", 1500), + seed: query + .get("seed") + .and_then(|value| value.parse::().ok()), + }; + match crate::demo_seed::run_seed(storage, &options).await { + Ok(mut result) => { + if let Some(object) = result.as_object_mut() { + object.insert("message".to_owned(), json!("Seed completed")); + } + success(StatusCode::OK, result, request_id) + } + Err(error_value) => { + tracing::error!(error = %error_value, "demo seed error"); + error( + StatusCode::INTERNAL_SERVER_ERROR, + "DEMO999", + "Failed to seed demo data", + request_id, + ) } } - message(StatusCode::OK, "Demo data seeded successfully", request_id) } #[derive(Debug, Clone, PartialEq, Eq)] @@ -7176,14 +9759,12 @@ fn cors_check_config_from(get: impl Fn(&str) -> Option) -> CorsCheckConf if !methods.iter().any(|method| method == "OPTIONS") { methods.push("OPTIONS".to_owned()); } - let raw_headers = { - let value = get("ALLOW_HEADERS") - .filter(|value| !value.trim().is_empty()) - .unwrap_or_else(|| "*".to_owned()); - csv(value) - }; - let used_wildcard_headers = raw_headers.iter().any(|header| header == "*"); - let headers = if used_wildcard_headers { + let raw_headers_value = get("ALLOW_HEADERS") + .filter(|value| !value.trim().is_empty()) + .unwrap_or_else(|| "*".to_owned()); + let used_wildcard_headers = raw_headers_value.split(',').any(|header| header == "*"); + let raw_headers = csv(raw_headers_value); + let headers = if raw_headers.iter().any(|header| header == "*") { ["Accept", "Content-Type", "X-CSRF-Token", "Authorization"] .into_iter() .map(str::to_owned) @@ -7229,6 +9810,10 @@ fn cors_check_config_from(get: impl Fn(&str) -> Option) -> CorsCheckConf } fn cors_check(payload: Value, request_id: &str) -> Response { + let payload = match normalize_cors_check(payload) { + Ok(payload) => payload, + Err(errors) => return validation_errors(errors, request_id), + }; cors_check_with_config( payload, request_id, @@ -7236,6 +9821,121 @@ fn cors_check(payload: Value, request_id: &str) -> Response { ) } +fn normalize_cors_check(payload: Value) -> Result> { + let Value::Object(values) = payload else { + return Err(vec![json!({ + "loc": ["body"], + "msg": "value is not a valid dict", + "type": "type_error.dict" + })]); + }; + let mut errors = Vec::new(); + let mut normalized = Map::new(); + for field in ["origin", "method"] { + match values.get(field) { + None => errors.push(json!({"loc": ["body", field], "msg": "field required", "type": "value_error.missing"})), + Some(Value::Null) => errors.push(json!({"loc": ["body", field], "msg": "none is not an allowed value", "type": "type_error.none.not_allowed"})), + Some(value) => match security_setting_string(value) { + Some(value) => { + normalized.insert(field.to_owned(), json!(value)); + } + None => errors.push(json!({"loc": ["body", field], "msg": "str type expected", "type": "type_error.str"})), + }, + } + } + match values.get("request_headers") { + None | Some(Value::Null) => {} + Some(Value::Array(headers)) => { + let mut parsed = Vec::with_capacity(headers.len()); + for (index, header) in headers.iter().enumerate() { + if header.is_null() { + errors.push(json!({"loc": ["body", "request_headers", index], "msg": "none is not an allowed value", "type": "type_error.none.not_allowed"})); + } else if let Some(header) = security_setting_string(header) { + parsed.push(header); + } else { + errors.push(json!({"loc": ["body", "request_headers", index], "msg": "str type expected", "type": "type_error.str"})); + } + } + normalized.insert("request_headers".to_owned(), json!(parsed)); + } + Some(_) => errors.push(json!({"loc": ["body", "request_headers"], "msg": "value is not a valid list", "type": "type_error.list"})), + } + match values.get("with_credentials") { + None | Some(Value::Null) => {} + Some(value) => match security_setting_bool(value) { + Some(value) => { + normalized.insert("with_credentials".to_owned(), json!(value)); + } + None => errors.push(json!({"loc": ["body", "with_credentials"], "msg": "value could not be parsed to a boolean", "type": "type_error.bool"})), + }, + } + if errors.is_empty() { + Ok(Value::Object(normalized)) + } else { + Err(errors) + } +} + +fn normalize_chaos_toggle(payload: &Value) -> Result<(String, bool, i64), Vec> { + let Some(values) = payload.as_object() else { + return Err(vec![json!({ + "loc": ["body"], + "msg": "value is not a valid dict", + "type": "type_error.dict" + })]); + }; + let mut errors = Vec::new(); + let backend = match values.get("backend") { + None => { + errors.push(json!({"loc": ["body", "backend"], "msg": "field required", "type": "value_error.missing"})); + None + } + Some(Value::Null) => { + errors.push(json!({"loc": ["body", "backend"], "msg": "none is not an allowed value", "type": "type_error.none.not_allowed"})); + None + } + Some(value) => match security_setting_string(value) { + Some(value) => Some(value), + None => { + errors.push(json!({"loc": ["body", "backend"], "msg": "str type expected", "type": "type_error.str"})); + None + } + }, + }; + let enabled = match values.get("enabled") { + None => { + errors.push(json!({"loc": ["body", "enabled"], "msg": "field required", "type": "value_error.missing"})); + None + } + Some(Value::Null) => { + errors.push(json!({"loc": ["body", "enabled"], "msg": "none is not an allowed value", "type": "type_error.none.not_allowed"})); + None + } + Some(value) => match security_setting_bool(value) { + Some(value) => Some(value), + None => { + errors.push(json!({"loc": ["body", "enabled"], "msg": "value could not be parsed to a boolean", "type": "type_error.bool"})); + None + } + }, + }; + let duration_ms = match values.get("duration_ms") { + None | Some(Value::Null) => Some(0), + Some(value) => match rate_rule_integer(value) { + Some(value) => Some(value), + None => { + errors.push(json!({"loc": ["body", "duration_ms"], "msg": "value is not a valid integer", "type": "type_error.integer"})); + None + } + }, + }; + if errors.is_empty() { + Ok((backend.unwrap(), enabled.unwrap(), duration_ms.unwrap())) + } else { + Err(errors) + } +} + fn cors_check_with_config(payload: Value, request_id: &str, config: &CorsCheckConfig) -> Response { let origin = payload .get("origin") @@ -7302,14 +10002,14 @@ fn cors_check_with_config(payload: Value, request_id: &str, config: &CorsCheckCo } let preflight_headers = json!({ - "Access-Control-Allow-Origin": if origin_allowed { &origin } else { "" }, + "Access-Control-Allow-Origin": if origin_allowed { json!(&origin) } else { Value::Null }, "Access-Control-Allow-Methods": config.methods.join(", "), "Access-Control-Allow-Headers": config.headers.join(", "), "Access-Control-Allow-Credentials": if with_credentials && config.credentials { "true" } else { "false" }, "Vary": "Origin", }); let actual_headers = json!({ - "Access-Control-Allow-Origin": if origin_allowed { &origin } else { "" }, + "Access-Control-Allow-Origin": if origin_allowed { json!(&origin) } else { Value::Null }, "Access-Control-Allow-Credentials": if with_credentials && config.credentials { "true" } else { "false" }, "Vary": "Origin", }); @@ -7399,18 +10099,21 @@ async fn subscription_routes( request_id, ); } - let apis = storage + let document = storage .find_one("subscriptions", &json!({"username": target})) .await .ok() - .flatten() - .and_then(|doc| doc.get("apis").cloned()) - .unwrap_or_else(|| json!([])); - return success( - StatusCode::OK, - json!({"apis": apis, "subscriptions": {"apis": apis}}), - request_id, - ); + .flatten(); + // The pinned service echoes the list under `subscriptions` once a + // subscription document exists. + let body = match document { + Some(doc) => { + let apis = doc.get("apis").cloned().unwrap_or_else(|| json!([])); + json!({"apis": apis.clone(), "subscriptions": {"apis": apis}}) + } + None => json!({"apis": []}), + }; + return success(StatusCode::OK, body, request_id); } let operation = if method == Method::POST && path.ends_with("/subscribe") { Some(true) @@ -7457,10 +10160,12 @@ async fn subscription_routes( { Ok(Some(api)) => api, _ => { + // group_required raises HTTPException(404, 'API not found'), + // which the route re-renders under its generic error code. return error( StatusCode::NOT_FOUND, - if subscribe { "SUB003" } else { "SUB005" }, - "API does not exist for the requested name and version", + if subscribe { "GEN001" } else { "GEN002" }, + "API not found", request_id, ); } @@ -7468,18 +10173,29 @@ async fn subscription_routes( // Python's group_required runs before SubscriptionService and evaluates // the user named in the request (which can differ from the actor). // Preserve that policy gate for both subscribing and unsubscribing. + let generic_code = if subscribe { "GEN001" } else { "GEN002" }; let target_user = match storage .find_one("users", &json!({"username": target})) .await { Ok(Some(user)) => user, - _ => return unexpected(request_id), + Ok(None) => { + return error( + StatusCode::NOT_FOUND, + generic_code, + "User not found", + request_id, + ); + } + Err(_) => return unexpected(request_id), }; + // group_required raises HTTPException(401, ...) rather than returning + // False, so the pinned route answers 401 with its generic error code. if enforce_group_access(&api_document, &target_user).is_err() { return error( - StatusCode::FORBIDDEN, - if subscribe { "SUB007" } else { "SUB008" }, - "You do not have the correct group access", + StatusCode::UNAUTHORIZED, + generic_code, + "You do not have the correct group for this", request_id, ); } @@ -7553,18 +10269,57 @@ async fn subscription_routes( ) } -fn public_credit_definition(mut value: Value) -> Value { - let key_present = value - .get("api_key") - .or_else(|| value.get("api_key_new")) - .and_then(Value::as_str) - .is_some_and(|key| !key.is_empty()); - value["api_key_present"] = json!(key_present); - if let Some(value) = value.as_object_mut() { - value.remove("api_key"); - value.remove("api_key_new"); +/// The pinned service projects exactly these four fields, with +/// `api_key_present = bool(doc.get('api_key'))`. +fn public_credit_definition(value: Value) -> Value { + json!({ + "api_credit_group": value.get("api_credit_group").cloned().unwrap_or(Value::Null), + "api_key_header": value.get("api_key_header").cloned().unwrap_or(Value::Null), + "api_key_present": value.get("api_key").is_some_and(python_truthy_json), + "credit_tiers": value.get("credit_tiers").cloned().unwrap_or(Value::Null), + }) +} + +fn encrypt_credit_definition(value: &mut Value) { + let Some(object) = value.as_object_mut() else { + return; + }; + for field in ["api_key", "api_key_new"] { + let encrypted = object + .get(field) + .and_then(Value::as_str) + .and_then(|value| crate::storage::field_encryption::encrypt_value(Some(value))); + if let Some(encrypted) = encrypted { + object.insert(field.to_owned(), json!(encrypted)); + } + } +} + +fn transform_user_credit_keys(value: &mut Value, encrypt: bool) { + let Some(credits) = value + .get_mut("users_credits") + .and_then(Value::as_object_mut) + else { + return; + }; + for credit in credits.values_mut() { + let Some(credit) = credit.as_object_mut() else { + continue; + }; + let transformed = credit + .get("user_api_key") + .and_then(Value::as_str) + .and_then(|value| { + if encrypt { + crate::storage::field_encryption::encrypt_value(Some(value)) + } else { + crate::storage::field_encryption::decrypt_value(Some(value)) + } + }); + if let Some(transformed) = transformed { + credit.insert("user_api_key".to_owned(), json!(transformed)); + } } - strip_internal(value) } async fn credit_routes( @@ -7572,6 +10327,7 @@ async fn credit_routes( path: &str, method: &Method, mut payload: Value, + query: &HashMap, username: &str, request_id: &str, ) -> Response { @@ -7603,38 +10359,109 @@ async fn credit_routes( request_id, ); }; - let Some(mut credits) = storage + let existing = storage .find_one("user_credits", &json!({"username": username})) .await .ok() - .flatten() - else { + .flatten(); + let mut credits = existing + .clone() + .unwrap_or_else(|| json!({"username": username, "users_credits": {}})); + let first = Uuid::new_v4(); + let second = Uuid::new_v4(); + let mut random = [0_u8; 32]; + random[..16].copy_from_slice(first.as_bytes()); + random[16..].copy_from_slice(second.as_bytes()); + let key = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(random); + let encrypted = crate::storage::field_encryption::encrypt_value(Some(&key)) + .unwrap_or_else(|| key.clone()); + let users_credits = credits + .as_object_mut() + .and_then(|value| value.get_mut("users_credits")) + .and_then(Value::as_object_mut); + let Some(users_credits) = users_credits else { + return unexpected(request_id); + }; + let group_credit = users_credits + .entry(group.to_owned()) + .or_insert_with(|| json!({})); + if !group_credit.is_object() { + *group_credit = json!({ + "available_credits": 0, + "tier_name": "default" + }); + } + group_credit["user_api_key"] = json!(encrypted); + let result = if existing.is_some() { + storage + .update_one("user_credits", &json!({"username": username}), &credits) + .await + .map(|_| ()) + } else { + storage + .insert_one("user_credits", credits) + .await + .map(|_| ()) + }; + return match result { + Ok(()) => success(StatusCode::OK, json!({"api_key": key}), request_id), + Err(_) => unexpected(request_id), + }; + } + if method == Method::GET && suffix == "defs" { + if !has_permission(state, username, "manage_credits").await { return error( - StatusCode::NOT_FOUND, - "CRD005", - "Credits not found", + StatusCode::FORBIDDEN, + "CRD002", + "Unable to retrieve credits", request_id, ); - }; - let key = Uuid::new_v4().to_string().replace('-', ""); - credits["users_credits"][group]["api_key"] = json!(key); - return match storage - .update_one("user_credits", &json!({"username": username}), &credits) - .await - { - Ok(Some(_)) => success(StatusCode::OK, json!({"api_key": key}), request_id), - _ => unexpected(request_id), - }; - } - if method == Method::GET && (suffix == "defs" || suffix.is_empty()) { - let values = storage - .find_many("credit_defs", &json!({})) + } + let mut values = storage + .find_many("credit_defs", &json!({})) .await .unwrap_or_default() .into_iter() .map(public_credit_definition) .collect::>(); - return success(StatusCode::OK, json!(values), request_id); + values.sort_by(|left, right| { + left.get("api_credit_group") + .and_then(Value::as_str) + .cmp(&right.get("api_credit_group").and_then(Value::as_str)) + }); + // Python's `list_credit_definitions(page: int = 1, page_size: int = 50)` + // typed FastAPI query params: a non-numeric value produces a 422. + if let Some(response) = + int_query_validation_error(query, &["page", "page_size"], request_id) + { + return response; + } + if let Err(message_text) = validate_pagination(query) { + return error(StatusCode::BAD_REQUEST, "PAG001", &message_text, request_id); + } + let page = query + .get("page") + .and_then(|value| value.parse::().ok()) + .unwrap_or(1); + let page_size = query + .get("page_size") + .and_then(|value| value.parse::().ok()) + .unwrap_or(50); + let total = values.len(); + let start = (page - 1).saturating_mul(page_size); + let has_next = start.saturating_add(page_size) < total; + values = values.into_iter().skip(start).take(page_size).collect(); + return success( + StatusCode::OK, + json!({ + "items": values, + "page": page, + "page_size": page_size, + "has_next": has_next, + "total": total + }), + request_id, + ); } if method == Method::GET && suffix == "all" { if !has_permission(state, username, "manage_credits").await { @@ -7645,14 +10472,77 @@ async fn credit_routes( request_id, ); } - let values = storage + // Python's `get_all_users_credits(page: int = 1, page_size: int = 10, ...)` + // typed FastAPI query params: a non-numeric value produces a 422. + if let Some(response) = + int_query_validation_error(query, &["page", "page_size"], request_id) + { + return response; + } + if let Err(message_text) = validate_pagination(query) { + return error(StatusCode::BAD_REQUEST, "PAG001", &message_text, request_id); + } + let page = query + .get("page") + .and_then(|value| value.parse::().ok()) + .unwrap_or(1); + let page_size = query + .get("page_size") + .and_then(|value| value.parse::().ok()) + .unwrap_or(10); + let search = query + .get("search") + .map(|value| value.trim().to_ascii_lowercase()) + .unwrap_or_default(); + let mut values = storage .find_many("user_credits", &json!({})) .await .unwrap_or_default() .into_iter() - .map(strip_internal) + .filter(|value| { + search.is_empty() + || value + .get("username") + .and_then(Value::as_str) + .is_some_and(|value| value.to_ascii_lowercase().contains(&search)) + || value + .get("users_credits") + .and_then(Value::as_object) + .is_some_and(|credits| { + credits + .keys() + .any(|group| group.to_ascii_lowercase().contains(&search)) + }) + }) + .map(|mut value| { + transform_user_credit_keys(&mut value, false); + strip_internal(value) + }) .collect::>(); - return success(StatusCode::OK, json!(values), request_id); + values.sort_by(|left, right| { + left.get("username") + .and_then(Value::as_str) + .cmp(&right.get("username").and_then(Value::as_str)) + }); + let total = values.len(); + let start = (page - 1).saturating_mul(page_size); + let has_next = start.saturating_add(page_size) < total; + let values = values + .into_iter() + .skip(start) + .take(page_size) + .collect::>(); + return success( + StatusCode::OK, + json!({ + "user_credits": values, + "page": page, + "page_size": page_size, + "has_next": has_next, + "total": total + }), + request_id, + ); } if method == Method::POST && suffix.is_empty() { if !has_permission(state, username, "manage_credits").await { @@ -7684,6 +10574,7 @@ async fn credit_routes( request_id, ); } + encrypt_credit_definition(&mut payload); return match storage.insert_one("credit_defs", payload).await { Ok(_) => message( StatusCode::CREATED, @@ -7696,13 +10587,21 @@ async fn credit_routes( if let Some(group) = suffix.strip_prefix("defs/") { let filter = json!({"api_credit_group": group}); if method == Method::GET { + if !has_permission(state, username, "manage_credits").await { + return error( + StatusCode::FORBIDDEN, + "CRD002", + "Unable to retrieve credits", + request_id, + ); + } return match storage.find_one("credit_defs", &filter).await { Ok(Some(value)) => { success(StatusCode::OK, public_credit_definition(value), request_id) } _ => error( StatusCode::NOT_FOUND, - "CRD002", + "CRD021", "Credit definition not found", request_id, ), @@ -7747,6 +10646,7 @@ async fn credit_routes( } Err(_) => return unexpected(request_id), } + encrypt_credit_definition(&mut payload); return match storage.update_one("credit_defs", &filter, &payload).await { Ok(Some(_)) => message( StatusCode::OK, @@ -7784,8 +10684,21 @@ async fn credit_routes( request_id, ); } + if payload + .get("username") + .and_then(Value::as_str) + .is_some_and(|body_username| body_username != suffix) + { + return error( + StatusCode::BAD_REQUEST, + "CRD014", + "Username in body does not match path", + request_id, + ); + } let mut value = payload; value["username"] = json!(suffix); + transform_user_credit_keys(&mut value, true); let existing = storage .find_one("user_credits", &json!({"username": suffix})) .await @@ -7817,11 +10730,14 @@ async fn credit_routes( .find_one("user_credits", &json!({"username": suffix})) .await { - Ok(Some(value)) => success(StatusCode::OK, strip_internal(value), request_id), + Ok(Some(mut value)) => { + transform_user_credit_keys(&mut value, false); + success(StatusCode::OK, strip_internal(value), request_id) + } _ => error( StatusCode::NOT_FOUND, - "CRD005", - "Credits not found", + "CRD017", + "User credits not found", request_id, ), }; @@ -8042,10 +10958,6 @@ async fn quota_routes( let Some(storage) = &state.storage else { return unexpected(request_id); }; - let (tier, limits) = match quota_tier_and_limits(storage, username).await { - Ok(value) => value, - Err(_) => return unexpected(request_id), - }; if path == "/quota/usage/history" && method == Method::GET { return success( StatusCode::OK, @@ -8053,6 +10965,10 @@ async fn quota_routes( request_id, ); } + let (tier, limits) = match quota_tier_and_limits(storage, username).await { + Ok(value) => value, + Err(_) => return quota_route_failure(path, request_id), + }; if path == "/quota/usage/export" && method == Method::POST { let Some(limits) = limits.as_ref() else { return http_detail( @@ -8063,7 +10979,7 @@ async fn quota_routes( }; let quotas = match quota_values(storage, username, limits).await { Ok(quotas) => quotas, - Err(_) => return unexpected(request_id), + Err(_) => return quota_route_failure(path, request_id), }; // The pinned Python export intentionally contains request quotas only; // monthly bandwidth is present in the dashboard but not this payload. @@ -8073,7 +10989,7 @@ async fn quota_routes( .collect::>(); let export_data = json!({ "user_id": username, - "export_date": timestamp_now(), + "export_date": timestamp_now_naive(), "quotas": export_quotas.iter().map(|quota| quota_export_value(quota)).collect::>(), }); if query.get("format").is_some_and(|format| format == "csv") { @@ -8114,7 +11030,7 @@ async fn quota_routes( && candidate .get("enabled") .and_then(Value::as_bool) - .unwrap_or(true) + == Some(true) }) .map(|candidate| { json!({ @@ -8125,7 +11041,7 @@ async fn quota_routes( }) }) .collect::>(), - Err(_) => return unexpected(request_id), + Err(_) => return quota_route_failure(path, request_id), }; success( StatusCode::OK, @@ -8187,7 +11103,7 @@ async fn quota_routes( }; let quotas = match quota_values(storage, username, limits).await { Ok(quotas) => quotas, - Err(_) => return unexpected(request_id), + Err(_) => return quota_route_failure(path, request_id), }; let request_quotas = quotas.iter().filter(|quota| { quota["quota_type"] @@ -8233,9 +11149,8 @@ async fn quota_routes( "daily_requests" => "daily_request_quota", "monthly_bandwidth" => "monthly_bandwidth_quota", _ => { - return error( + return http_detail( StatusCode::BAD_REQUEST, - "QUOTA001", &format!("Invalid quota type: {quota_type}"), request_id, ); @@ -8254,7 +11169,7 @@ async fn quota_routes( }; return match quota_status(storage, username, quota_type, limit).await { Ok(status) => success(StatusCode::OK, status, request_id), - Err(_) => unexpected(request_id), + Err(_) => quota_route_failure(path, request_id), }; } error( @@ -8265,6 +11180,17 @@ async fn quota_routes( ) } +fn quota_route_failure(path: &str, request_id: &str) -> Response { + let detail = match path { + "/quota/usage/history" => "Failed to get usage history", + "/quota/usage/export" => "Failed to export usage data", + "/quota/tier/info" => "Failed to get tier info", + "/quota/burst/status" => "Failed to get burst status", + _ => "Failed to get quota status", + }; + http_detail(StatusCode::INTERNAL_SERVER_ERROR, detail, request_id) +} + async fn quota_tier_and_limits( storage: &SharedStorage, username: &str, @@ -8293,6 +11219,8 @@ async fn quota_tier_and_limits( .unwrap_or(false) }) }; + // The pinned TierService applies assignment overrides before checking the + // assignment's effective dates, even when get_user_tier falls back. let limits = assignment .as_ref() .and_then(|assignment| assignment.get("override_limits")) @@ -8338,11 +11266,14 @@ async fn quota_status( limit: u64, ) -> Result { let (counter_type, period_key, reset_at) = quota_period(name); + // QuotaTracker.check_quota deliberately fails open when Redis is + // unavailable, returning zero usage with the configured limit intact. let current_usage = storage .current_counter(&format!( "quota:user:{username}:{counter_type}:month:{period_key}:usage" )) - .await?; + .await + .unwrap_or(0); let percentage_used = current_usage as f64 / limit as f64 * 100.0; Ok(json!({ "quota_type": name, @@ -8478,7 +11409,7 @@ async fn api_discovery_routes( return error( StatusCode::FORBIDDEN, "AUTHZ001", - "Insufficient permissions", + "Not authorized", request_id, ); } @@ -8494,13 +11425,40 @@ async fn api_discovery_routes( } return error(StatusCode::NOT_FOUND, "API001", "API not found", request_id); }; + if kind == "openapi" { + return openapi_discovery_route( + state, storage, &api, &filter, parts, action, method, username, request_id, + ) + .await; + } if kind == "grpc" && action == "services" && method == Method::GET { - use base64::Engine as _; - let Some(raw) = api.get("api_grpc_descriptor_set").and_then(Value::as_str) else { - return success(StatusCode::OK, json!({"services": []}), request_id); - }; - let services = base64::engine::general_purpose::STANDARD.decode(raw).ok().and_then(|bytes| prost_reflect::DescriptorPool::decode(bytes.as_slice()).ok()).map(|pool| pool.services().map(|service| json!({"name": service.name(), "full_name": service.full_name(), "methods": service.methods().map(|method_value| method_value.name().to_owned()).collect::>() })).collect::>()).unwrap_or_default(); - return success(StatusCode::OK, json!({"services": services}), request_id); + // The pinned route reports the configured allow-list rather than + // querying reflection. + if api + .get("api_servers") + .and_then(Value::as_array) + .is_none_or(Vec::is_empty) + { + return error( + StatusCode::NOT_FOUND, + "GRPC001", + "No upstream servers configured", + request_id, + ); + } + return success( + StatusCode::OK, + json!({ + "services": api + .get("api_grpc_allowed_services") + .filter(|value| python_truthy_json(value)) + .cloned() + .unwrap_or_else(|| json!([])), + "reflection_enabled": api.get("api_grpc_reflection_url").is_some_and(python_truthy_json), + "note": "Auto-discovery via reflection not fully implemented in this demo route", + }), + request_id, + ); } if kind == "graphql" { let field = "api_graphql_schema"; @@ -8552,31 +11510,13 @@ async fn api_discovery_routes( return error(StatusCode::BAD_REQUEST, "API003", &message_text, request_id); } }; - let query = json!({"query": "query IntrospectionQuery { __schema { types { name kind } queryType { name } mutationType { name } subscriptionType { name } } }"}); - let schema = match state.proxy_client.post(target).json(&query).send().await { - Ok(response) => match response.json::().await { - Ok(response) => response - .get("data") - .and_then(|data| data.get("__schema")) - .cloned() - .unwrap_or(response), - Err(_) => { - return error( - StatusCode::BAD_GATEWAY, - "GQL002", - "Invalid GraphQL schema response", - request_id, - ); - } - }, - Err(_) => { - return error( - StatusCode::BAD_GATEWAY, - "GQL001", - "Unable to fetch GraphQL schema", - request_id, - ); - } + let Some(schema) = fetch_introspection_schema(state, target).await else { + return error( + StatusCode::BAD_GATEWAY, + "GQL002", + "Failed to fetch schema from upstream", + request_id, + ); }; let _ = storage .update_one("apis", &filter, &json!({field: schema.clone()})) @@ -8607,29 +11547,45 @@ async fn api_discovery_routes( return error(StatusCode::BAD_REQUEST, "API003", &message_text, request_id); } }; - let query = json!({"query": "query IntrospectionQuery { __schema { types { name kind } queryType { name } mutationType { name } } }"}); - return match state.proxy_client.post(target).json(&query).send().await { - Ok(response) => match response.json::().await { - Ok(schema) => { - let _ = storage - .update_one("apis", &filter, &json!({field: schema.clone()})) - .await; - success(StatusCode::OK, schema, request_id) - } - Err(_) => error( - StatusCode::BAD_GATEWAY, - "GQL002", - "Invalid GraphQL schema response", - request_id, - ), - }, - Err(_) => error( + let Some(schema) = fetch_introspection_schema(state, target).await else { + return error( StatusCode::BAD_GATEWAY, - "GQL001", - "Unable to fetch GraphQL schema", + "GQL002", + "Failed to fetch schema", request_id, - ), + ); }; + let _ = storage + .update_one("apis", &filter, &json!({field: schema.clone()})) + .await; + let types_count = schema + .get("types") + .and_then(Value::as_array) + .map(|types| { + types + .iter() + .filter(|item| { + !item + .get("name") + .and_then(Value::as_str) + .unwrap_or("") + .starts_with("__") + }) + .count() + }) + .unwrap_or(0); + let has_subscriptions = schema + .get("subscriptionType") + .is_some_and(|item| item.get("name").is_some_and(|name| !name.is_null())); + return success( + StatusCode::OK, + json!({ + "message": "Schema refreshed successfully", + "types_count": types_count, + "has_subscriptions": has_subscriptions, + }), + request_id, + ); } } let (field, configured_url) = if kind == "openapi" { @@ -8648,24 +11604,29 @@ async fn api_discovery_routes( if let Some(value) = api.get(field).filter(|value| !value.is_null()) { return discovery_document_response(kind, value.clone(), true, request_id); } - let Some(server) = api - .get("api_servers") - .and_then(Value::as_array) - .and_then(|items| items.first()) + // The pinned route checks the configured URL before the servers. + let Some(path) = api + .get(configured_url) .and_then(Value::as_str) + .filter(|path| !path.is_empty()) else { return error( StatusCode::NOT_FOUND, - "API003", - &format!("{} document not found", kind.to_ascii_uppercase()), + "WSDL001", + "No WSDL URL configured for this API", request_id, ); }; - let Some(path) = api.get(configured_url).and_then(Value::as_str) else { + let Some(server) = api + .get("api_servers") + .and_then(Value::as_array) + .and_then(|items| items.first()) + .and_then(Value::as_str) + else { return error( StatusCode::NOT_FOUND, - "API003", - &format!("{} document not found", kind.to_ascii_uppercase()), + "WSDL002", + "No upstream servers configured", request_id, ); }; @@ -8713,32 +11674,30 @@ async fn api_discovery_routes( return discovery_document_response(kind, document, false, request_id); } if method == Method::POST && action == "refresh" { - let Some(server) = api - .get("api_servers") - .and_then(Value::as_array) - .and_then(|items| items.first()) + let Some(path) = api + .get(configured_url) .and_then(Value::as_str) + .filter(|path| !path.is_empty()) else { return error( - StatusCode::BAD_REQUEST, - "API003", - "API server is not configured", + StatusCode::NOT_FOUND, + "WSDL001", + "No WSDL URL configured", request_id, ); }; - let Some(path) = api.get(configured_url).and_then(Value::as_str) else { + // Without a server the pinned route fetches the bare relative URL, + // which fails as an upstream error. + let Some(server) = api + .get("api_servers") + .and_then(Value::as_array) + .and_then(|items| items.first()) + .and_then(Value::as_str) + else { return error( - StatusCode::NOT_FOUND, - if kind == "openapi" { - "OPENAPI001" - } else { - "WSDL001" - }, - if kind == "openapi" { - "OpenAPI URL is not configured" - } else { - "WSDL URL is not configured" - }, + StatusCode::BAD_GATEWAY, + "WSDL003", + "Failed to fetch WSDL", request_id, ); }; @@ -8781,137 +11740,363 @@ async fn api_discovery_routes( return error( StatusCode::FORBIDDEN, "AUTHZ001", - "Insufficient permissions", + "Insufficient permissions", + request_id, + ); + } + if kind == "openapi" && api.get(field).is_none_or(Value::is_null) { + return error( + StatusCode::NOT_FOUND, + "OPENAPI003", + "No OpenAPI available", + request_id, + ); + } + let (candidates, service_name, operations_found) = if kind == "openapi" { + ( + crate::routes::discovery::openapi_endpoints(api.get(field).unwrap_or(&Value::Null)), + String::new(), + 0, + ) + } else { + let Some(content) = api.get(field).and_then(Value::as_str) else { + return error( + StatusCode::NOT_FOUND, + "WSDL003", + "No WSDL available", + request_id, + ); + }; + let parsed = match crate::routes::discovery::parse_wsdl(content) { + Ok(parsed) => parsed, + Err(message) => { + return error(StatusCode::BAD_REQUEST, "WSDL004", &message, request_id); + } + }; + let service_name = parsed + .get("service_name") + .and_then(Value::as_str) + .unwrap_or("") + .to_owned(); + let operations_found = parsed + .get("operations") + .and_then(Value::as_array) + .map(Vec::len) + .unwrap_or(0); + ( + parsed + .get("endpoints") + .and_then(Value::as_array) + .cloned() + .unwrap_or_default(), + service_name, + operations_found, + ) + }; + let endpoints_found = candidates.len(); + let mut imported = 0_u64; + let mut skipped = 0_u64; + for candidate in candidates { + let uri = candidate + .get("endpoint_uri") + .or_else(|| candidate.get("uri")) + .and_then(Value::as_str) + .unwrap_or(""); + let verb = candidate + .get("endpoint_method") + .or_else(|| candidate.get("method")) + .and_then(Value::as_str) + .unwrap_or("POST") + .to_ascii_uppercase(); + if uri.is_empty() { + skipped += 1; + continue; + } + let duplicate = storage + .find_one( + "endpoints", + &json!({ + "api_name": parts[0], + "api_version": parts[1], + "endpoint_method": &verb, + "endpoint_uri": uri, + }), + ) + .await + .ok() + .flatten() + .is_some(); + if duplicate { + skipped += 1; + continue; + } + let endpoint = json!({ + "api_name": parts[0], + "api_version": parts[1], + "api_id": api.get("api_id").cloned().unwrap_or(Value::Null), + "endpoint_id": Uuid::new_v4().to_string(), + "endpoint_method": verb, + "endpoint_uri": uri, + "client_uri": uri, + "endpoint_description": candidate.get("endpoint_description").or_else(|| candidate.get("description")).and_then(Value::as_str).unwrap_or(""), + "endpoint_soap_action": candidate.get("soap_action").cloned().unwrap_or(Value::String(String::new())), + }); + if storage.insert_one("endpoints", endpoint).await.is_ok() { + imported += 1; + } else { + skipped += 1; + } + } + return if kind == "openapi" { + success( + StatusCode::OK, + json!({ + "message": "OpenAPI import completed", + "endpoints_found": endpoints_found, + "endpoints_imported": imported, + "endpoints_skipped": skipped, + }), + request_id, + ) + } else { + success( + StatusCode::OK, + json!({ + "message": "WSDL import completed", + "service_name": service_name, + "operations_found": operations_found, + "endpoints_imported": imported, + "endpoints_skipped": skipped, + }), + request_id, + ) + }; + } + error( + StatusCode::NOT_FOUND, + "GTW003", + "Platform route does not exist", + request_id, + ) +} + +async fn fetch_openapi_spec(state: &AppState, api: &Value, path: &str) -> Option { + let servers = api.get("api_servers").and_then(Value::as_array)?; + for server in servers.iter().filter_map(Value::as_str) { + let Ok(target) = discovery_target(server, path) else { + continue; + }; + let Ok(response) = state.proxy_client.get(target).send().await else { + continue; + }; + if response.status() != StatusCode::OK { + continue; + } + if let Ok(spec) = response.json::().await { + return Some(spec); + } + } + None +} + +fn python_truthy_json(value: &Value) -> bool { + match value { + Value::Null => false, + Value::Bool(value) => *value, + Value::Number(value) => value.as_f64().is_some_and(|value| value != 0.0), + Value::String(value) => !value.is_empty(), + Value::Array(value) => !value.is_empty(), + Value::Object(value) => !value.is_empty(), + } +} + +#[allow(clippy::too_many_arguments)] +async fn openapi_discovery_route( + state: &AppState, + storage: &SharedStorage, + api: &Value, + filter: &Value, + parts: &[&str], + action: &str, + method: &Method, + username: &str, + request_id: &str, +) -> Response { + let cached = api + .get("api_openapi_spec") + .filter(|value| python_truthy_json(value)); + if method == Method::GET && action.is_empty() { + if let Some(spec) = cached { + return success(StatusCode::OK, spec.clone(), request_id); + } + let Some(path) = api.get("api_openapi_url").and_then(Value::as_str) else { + return error( + StatusCode::NOT_FOUND, + "OPENAPI001", + "No OpenAPI URL configured for this API", + request_id, + ); + }; + if api + .get("api_servers") + .and_then(Value::as_array) + .is_none_or(Vec::is_empty) + { + return error( + StatusCode::NOT_FOUND, + "OPENAPI002", + "No upstream servers configured", + request_id, + ); + } + let Some(spec) = fetch_openapi_spec(state, api, path).await else { + return error( + StatusCode::BAD_GATEWAY, + "OPENAPI003", + "Failed to fetch OpenAPI spec from upstream", + request_id, + ); + }; + let _ = storage + .update_one("apis", filter, &json!({"api_openapi_spec": spec.clone()})) + .await; + return success(StatusCode::OK, spec, request_id); + } + if method == Method::POST && action == "refresh" { + let Some(path) = api.get("api_openapi_url").and_then(Value::as_str) else { + return error( + StatusCode::NOT_FOUND, + "OPENAPI001", + "No OpenAPI URL configured", + request_id, + ); + }; + if api + .get("api_servers") + .and_then(Value::as_array) + .is_none_or(Vec::is_empty) + { + return error( + StatusCode::NOT_FOUND, + "OPENAPI002", + "No upstream servers configured", + request_id, + ); + } + let Some(spec) = fetch_openapi_spec(state, api, path).await else { + return error( + StatusCode::BAD_GATEWAY, + "OPENAPI003", + "Failed to fetch OpenAPI spec", + request_id, + ); + }; + let _ = storage + .update_one("apis", filter, &json!({"api_openapi_spec": spec.clone()})) + .await; + return success( + StatusCode::OK, + json!({ + "message": "OpenAPI spec refreshed successfully", + "endpoints_found": spec.get("paths").and_then(Value::as_object).map_or(0, Map::len), + }), + request_id, + ); + } + if method == Method::POST && action == "import" { + if !has_permission(state, username, "manage_endpoints").await { + return error( + StatusCode::FORBIDDEN, + "AUTHZ001", + "Not authorized", request_id, ); } - if kind == "openapi" && api.get(field).is_none_or(Value::is_null) { + let spec = if let Some(spec) = cached.cloned() { + Some(spec) + } else if let Some(path) = api.get("api_openapi_url").and_then(Value::as_str) { + let fetched = fetch_openapi_spec(state, api, path).await; + if let Some(spec) = &fetched { + let _ = storage + .update_one("apis", filter, &json!({"api_openapi_spec": spec.clone()})) + .await; + } + fetched + } else { + None + }; + let Some(spec) = spec else { return error( StatusCode::NOT_FOUND, "OPENAPI003", - "No OpenAPI available", + "No OpenAPI spec available", request_id, ); - } - let (candidates, service_name, operations_found) = if kind == "openapi" { - ( - crate::routes::discovery::openapi_endpoints(api.get(field).unwrap_or(&Value::Null)), - String::new(), - 0, - ) - } else { - let Some(content) = api.get(field).and_then(Value::as_str) else { - return error( - StatusCode::NOT_FOUND, - "WSDL003", - "No WSDL available", - request_id, - ); - }; - let parsed = match crate::routes::discovery::parse_wsdl(content) { - Ok(parsed) => parsed, - Err(message) => { - return error(StatusCode::BAD_REQUEST, "WSDL004", &message, request_id); - } - }; - let service_name = parsed - .get("service_name") - .and_then(Value::as_str) - .unwrap_or("") - .to_owned(); - let operations_found = parsed - .get("operations") - .and_then(Value::as_array) - .map(Vec::len) - .unwrap_or(0); - ( - parsed - .get("endpoints") - .and_then(Value::as_array) - .cloned() - .unwrap_or_default(), - service_name, - operations_found, - ) }; + let candidates = crate::routes::discovery::openapi_endpoints(&spec); let endpoints_found = candidates.len(); + let api_id = api.get("api_id").cloned().unwrap_or(Value::Null); + let existing = storage + .find_many("endpoints", &json!({"api_id": api_id.clone()})) + .await + .unwrap_or_default(); let mut imported = 0_u64; let mut skipped = 0_u64; for candidate in candidates { let uri = candidate .get("endpoint_uri") - .or_else(|| candidate.get("uri")) .and_then(Value::as_str) .unwrap_or(""); - let verb = candidate + let method = candidate .get("endpoint_method") - .or_else(|| candidate.get("method")) .and_then(Value::as_str) - .unwrap_or("POST") - .to_ascii_uppercase(); - if uri.is_empty() { - skipped += 1; - continue; - } - let duplicate = storage - .find_one( - "endpoints", - &json!({ - "api_name": parts[0], - "api_version": parts[1], - "endpoint_method": &verb, - "endpoint_uri": uri, - }), - ) - .await - .ok() - .flatten() - .is_some(); + .unwrap_or(""); + let composite = format!("{method}/{}", uri.trim_start_matches('/')); + let duplicate = existing.iter().any(|endpoint| { + let existing_method = endpoint + .get("endpoint_method") + .and_then(Value::as_str) + .unwrap_or(""); + let existing_uri = endpoint + .get("endpoint_uri") + .and_then(Value::as_str) + .unwrap_or(""); + format!("{existing_method}/{}", existing_uri.trim_start_matches('/')) == composite + }); if duplicate { skipped += 1; continue; } + let description = candidate + .get("endpoint_description") + .and_then(Value::as_str) + .unwrap_or("") + .chars() + .take(127) + .collect::(); let endpoint = json!({ + "api_id": api_id, "api_name": parts[0], "api_version": parts[1], - "api_id": api.get("api_id").cloned().unwrap_or(Value::Null), "endpoint_id": Uuid::new_v4().to_string(), - "endpoint_method": verb, "endpoint_uri": uri, - "client_uri": uri, - "endpoint_description": candidate.get("endpoint_description").or_else(|| candidate.get("description")).and_then(Value::as_str).unwrap_or(""), - "endpoint_soap_action": candidate.get("soap_action").cloned().unwrap_or(Value::String(String::new())), + "endpoint_method": method, + "endpoint_description": description, }); if storage.insert_one("endpoints", endpoint).await.is_ok() { imported += 1; - } else { - skipped += 1; } } - return if kind == "openapi" { - success( - StatusCode::OK, - json!({ - "message": "OpenAPI import completed", - "endpoints_found": endpoints_found, - "endpoints_imported": imported, - "endpoints_skipped": skipped, - }), - request_id, - ) - } else { - success( - StatusCode::OK, - json!({ - "message": "WSDL import completed", - "service_name": service_name, - "operations_found": operations_found, - "endpoints_imported": imported, - "endpoints_skipped": skipped, - }), - request_id, - ) - }; + return success( + StatusCode::OK, + json!({ + "message": "OpenAPI import completed", + "endpoints_found": endpoints_found, + "endpoints_imported": imported, + "endpoints_skipped": skipped, + }), + request_id, + ); } error( StatusCode::NOT_FOUND, @@ -8921,6 +12106,31 @@ async fn api_discovery_routes( ) } +/// Mirror of the pinned `fetch_introspection_schema`: only a 200 response with +/// no GraphQL errors and a `data.__schema` object yields a schema. +async fn fetch_introspection_schema(state: &AppState, target: String) -> Option { + const INTROSPECTION_QUERY: &str = include_str!("graphql_introspection.graphql"); + let response = state + .proxy_client + .post(target) + .header(header::ACCEPT, "application/json") + .json(&json!({"query": INTROSPECTION_QUERY})) + .send() + .await + .ok()?; + if response.status() != reqwest::StatusCode::OK { + return None; + } + let value = response.json::().await.ok()?; + if value.get("errors").is_some_and(python_truthy_json) { + return None; + } + value + .pointer("/data/__schema") + .filter(|schema| schema.is_object() && !schema.as_object().is_some_and(Map::is_empty)) + .cloned() +} + fn graphql_schema_response(schema: Value, cached: bool, request_id: &str) -> Response { let query = schema .get("queryType") @@ -8986,29 +12196,65 @@ pub async fn backfill_grpc_descriptors(storage: &SharedStorage) -> DescriptorBac }; let mut backfill = DescriptorBackfill::default(); for api in apis { - if !api + let grpc_configured = api .get("api_type") .and_then(Value::as_str) .is_some_and(|kind| kind.eq_ignore_ascii_case("GRPC")) - || !bool_field_default(&api, "active", true) - { + || api.get("api_grpc_proto_source").is_some_and(python_truthy) + || api.get("api_grpc_package").is_some_and(python_truthy); + if !grpc_configured || api.get("active") == Some(&Value::Bool(false)) { continue; } - let Some(source) = api.get("api_grpc_proto_source").and_then(Value::as_str) else { - continue; - }; backfill.scanned += 1; if api .get("api_grpc_descriptor_set") - .and_then(Value::as_str) - .is_some_and(|descriptor| !descriptor.trim().is_empty()) + .is_some_and(python_truthy) { backfill.skipped += 1; continue; } - let api_name = api.get("api_name").cloned().unwrap_or(Value::Null); - let api_version = api.get("api_version").cloned().unwrap_or(Value::Null); - match compile_proto(source) { + let api_name = api + .get("api_name") + .and_then(Value::as_str) + .unwrap_or_default(); + let api_version = api + .get("api_version") + .and_then(Value::as_str) + .unwrap_or_default(); + let api_label = format!("{api_name}/{api_version}"); + if api_name.is_empty() || api_version.is_empty() { + backfill.errors.push(json!({ + "api": api_label, + "error": "API name/version is missing", + })); + continue; + } + let Some(source) = api + .get("api_grpc_proto_source") + .and_then(Value::as_str) + .filter(|source| !source.is_empty()) + else { + backfill.errors.push(json!({ + "api": api_label, + "error": "Proto source not found", + })); + continue; + }; + let max_proto_size = env::var("MAX_PROTO_SIZE_BYTES") + .ok() + .and_then(|value| value.parse::().ok()) + .unwrap_or(1024 * 1024); + let source = match validate_proto_content(source.as_bytes(), max_proto_size) { + Ok(source) => source, + Err(error_value) => { + backfill.errors.push(json!({ + "api": api_label, + "error": error_value.chars().take(500).collect::(), + })); + continue; + } + }; + match compile_proto(&source) { Ok((descriptor, digest)) => { let filter = json!({"api_name": api_name, "api_version": api_version}); match storage @@ -9024,18 +12270,18 @@ pub async fn backfill_grpc_descriptors(storage: &SharedStorage) -> DescriptorBac { Ok(Some(_)) => backfill.updated += 1, Ok(None) => backfill.errors.push(json!({ - "api": filter.get("api_name"), + "api": api_label, "error": "API disappeared while descriptor backfill was running", })), Err(error_value) => backfill.errors.push(json!({ - "api": filter.get("api_name"), + "api": api_label, "error": error_value.to_string(), })), } } Err(error_value) => backfill.errors.push(json!({ - "api": api_name, - "error": error_value, + "api": api_label, + "error": error_value.chars().take(500).collect::(), })), } } @@ -9051,13 +12297,34 @@ async fn proto_routes( username: &str, request_id: &str, ) -> Response { + if method == Method::POST { + let max_size = env::var(crate::constants::Defaults::MAX_MULTIPART_SIZE_BYTES_ENV) + .ok() + .and_then(|value| value.parse::().ok()) + .unwrap_or(crate::constants::Defaults::MAX_MULTIPART_SIZE_BYTES_DEFAULT); + if headers + .get(header::CONTENT_LENGTH) + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.parse::().ok()) + .is_some_and(|length| length > max_size) + { + return error( + StatusCode::PAYLOAD_TOO_LARGE, + crate::constants::ErrorCodes::REQUEST_TOO_LARGE, + crate::constants::Messages::FILE_TOO_LARGE, + request_id, + ); + } + } if !has_permission(state, username, "manage_apis").await { - return error( - StatusCode::FORBIDDEN, - "API008", - "You do not have permission to manage proto files", - request_id, - ); + let (code, message_text) = if method == Method::PUT { + ("API008", "You do not have permission to update proto files") + } else if method == Method::DELETE { + ("API008", "You do not have permission to delete proto files") + } else { + ("AUTH001", "User does not have permission to manage APIs") + }; + return error(StatusCode::FORBIDDEN, code, message_text, request_id); } let Some(storage) = &state.storage else { return unexpected(request_id); @@ -9083,6 +12350,16 @@ async fn proto_routes( if parts.len() != 2 { return error(StatusCode::NOT_FOUND, "API003", "API not found", request_id); } + // The pinned handlers derive a proto file name from the path and reject + // unusable names with 400 GTW013. + if let Some(reason) = parts.iter().find_map(|part| proto_file_name_error(part)) { + return error( + StatusCode::BAD_REQUEST, + "GTW013", + &format!("Path validation error: {reason}"), + request_id, + ); + } let filter = json!({"api_name": parts[0], "api_version": parts[1]}); if method == Method::GET { let proto_record = storage @@ -9096,17 +12373,33 @@ async fn proto_routes( .ok() .flatten()); let Some(proto_record) = proto_record else { - return error(StatusCode::NOT_FOUND, "API003", "API not found", request_id); + return error( + StatusCode::NOT_FOUND, + "API002", + &format!("Proto file not found for API {}/{}", parts[0], parts[1]), + request_id, + ); }; return match proto_record .get("api_grpc_proto_source") .and_then(Value::as_str) { - Some(source) => success(StatusCode::OK, json!({"content": source}), request_id), + Some(source) if !source.is_empty() => success( + StatusCode::OK, + // respond_rest drops `message` when a response body is present. + json!({"content": source}), + request_id, + ), None => error( StatusCode::NOT_FOUND, - "API003", - "Proto file not found", + "API002", + &format!("Proto file not found for API {}/{}", parts[0], parts[1]), + request_id, + ), + Some(_) => error( + StatusCode::NOT_FOUND, + "API002", + &format!("Proto file not found for API {}/{}", parts[0], parts[1]), request_id, ), }; @@ -9118,9 +12411,9 @@ async fn proto_routes( "apis", &filter, &json!({ - "api_grpc_proto_source": "", - "api_grpc_descriptor_set": "", - "api_grpc_descriptor_sha256": "" + "api_grpc_proto_source": null, + "api_grpc_descriptor_set": null, + "api_grpc_descriptor_sha256": null }), ) .await, @@ -9130,33 +12423,38 @@ async fn proto_routes( .delete_one("grpc_proto_uploads", &filter) .await .unwrap_or(false); - return if cleared_api || cleared_pending { - message( - StatusCode::OK, - "Proto file deleted successfully", - request_id, - ) - } else { - error( - StatusCode::NOT_FOUND, - "API003", - "Proto file not found", - request_id, - ) - }; + let _ = (cleared_api, cleared_pending); + return message( + StatusCode::OK, + "Proto file and generated files deleted successfully", + request_id, + ); } if method == Method::POST || method == Method::PUT { - let max_size = env::var("MAX_PROTO_SIZE_BYTES") - .ok() - .and_then(|value| value.parse::().ok()) - .unwrap_or(1024 * 1024); - let source = match extract_proto_source(headers, body) { - Ok(source) if source.len() <= max_size => source, - Ok(_) => { + let max_proto_size = match env::var("MAX_PROTO_SIZE_BYTES") { + Err(_) => 1024 * 1024, + Ok(value) => match value.trim().parse::() { + Ok(size) => usize::try_from(size).unwrap_or(0), + // The pinned route rejects every upload when the limit is unusable. + Err(_) => { + return error( + StatusCode::BAD_REQUEST, + crate::constants::ErrorCodes::REQUEST_FILE_TYPE, + "Invalid proto file: MAX_PROTO_SIZE_BYTES is not an integer", + request_id, + ); + } + }, + }; + let content = match extract_proto_source(headers, body) { + Ok(content) => content, + // The pinned route declares an UploadFile form field, so FastAPI + // rejects any non-multipart request before the handler runs. + Err(error_value) if error_value == NOT_MULTIPART => { return error( - StatusCode::PAYLOAD_TOO_LARGE, - "GTW013", - "File too large", + StatusCode::UNPROCESSABLE_ENTITY, + "VAL001", + "Validation Error", request_id, ); } @@ -9173,26 +12471,29 @@ async fn proto_routes( ); } }; - if source.contains('`') - || source.contains("$(") - || !(source.contains("syntax") - || source.contains("message") - || source.contains("service")) - { - return error( - StatusCode::BAD_REQUEST, - "REQ002", - "Invalid proto file", - request_id, - ); - } + let source = match validate_proto_content(&content, max_proto_size) { + Ok(source) => source, + Err(error_value) => { + return error( + StatusCode::BAD_REQUEST, + crate::constants::ErrorCodes::REQUEST_FILE_TYPE, + &format!("Invalid proto file: {error_value}"), + request_id, + ); + } + }; let (descriptor, digest) = match compile_proto(&source) { Ok(result) => result, Err(error_value) => { + let message_text = if method == Method::PUT { + "Failed to generate gRPC code from proto file".to_owned() + } else { + format!("Failed to generate gRPC code: {error_value}") + }; return error( StatusCode::INTERNAL_SERVER_ERROR, "API009", - &format!("Failed to generate gRPC descriptor: {error_value}"), + &message_text, request_id, ); } @@ -9209,7 +12510,11 @@ async fn proto_routes( return match result { Ok(Some(_)) => message( StatusCode::OK, - "Proto file uploaded and gRPC code generated successfully", + if method == Method::PUT { + "Proto file updated successfully" + } else { + "Proto file uploaded and gRPC code generated successfully" + }, request_id, ), Ok(None) => { @@ -9243,7 +12548,11 @@ async fn proto_routes( if saved { message( StatusCode::OK, - "Proto file uploaded and gRPC code generated successfully", + if method == Method::PUT { + "Proto file updated successfully" + } else { + "Proto file uploaded and gRPC code generated successfully" + }, request_id, ) } else { @@ -9261,13 +12570,15 @@ async fn proto_routes( ) } -fn extract_proto_source(headers: &HeaderMap, body: &[u8]) -> Result { +const NOT_MULTIPART: &str = "Request is not multipart/form-data"; + +fn extract_proto_source(headers: &HeaderMap, body: &[u8]) -> Result, String> { let content_type = headers .get(header::CONTENT_TYPE) .and_then(|value| value.to_str().ok()) .unwrap_or(""); if !content_type.starts_with("multipart/form-data") { - return String::from_utf8(body.to_vec()).map_err(|_| "Proto file must be UTF-8".to_owned()); + return Err(NOT_MULTIPART.to_owned()); } let boundary = content_type .split("boundary=") @@ -9275,9 +12586,19 @@ fn extract_proto_source(headers: &HeaderMap, body: &[u8]) -> Result Result bool { - filename.ends_with(".proto") - && !filename.is_empty() - && filename.len() <= 255 - && !filename.contains("..") - && !filename.starts_with(['/', '\\']) - && filename.as_bytes().get(1).is_none_or(|byte| *byte != b':') - && filename.chars().all(|character| { - character.is_ascii_alphanumeric() || matches!(character, '_' | '-' | '.') - }) + Ok(content) } fn extract_proto_package(source: &str) -> Option { - for line in source.lines() { - let trimmed = line.trim(); - if let Some(rest) = trimmed.strip_prefix("package") { - let pkg = rest.trim().trim_matches(';').trim(); - if !pkg.is_empty() - && pkg - .chars() - .all(|c| c.is_alphanumeric() || c == '_' || c == '.') - { - return Some(pkg.to_owned()); - } - } + static PACKAGE: OnceLock = OnceLock::new(); + PACKAGE + .get_or_init(|| Regex::new(r"\bpackage\s+([a-zA-Z0-9_.]+)\s*;").unwrap()) + .captures(source) + .and_then(|captures| captures.get(1)) + .map(|package| package.as_str().to_owned()) +} + +fn validate_proto_content(content: &[u8], max_size: usize) -> Result { + if content.len() > max_size { + return Err(format!("File too large (max {max_size} bytes)")); + } + if content.contains(&0) { + return Err("Invalid proto file: binary content detected".to_owned()); + } + let source = std::str::from_utf8(content) + .map_err(|_| "Invalid proto file: not valid UTF-8".to_owned())?; + if !(source.contains("syntax") || source.contains("message") || source.contains("service")) { + return Err("Invalid proto file: missing proto syntax (syntax/message/service)".to_owned()); + } + static SUSPICIOUS: OnceLock = OnceLock::new(); + if SUSPICIOUS + .get_or_init(|| Regex::new(r"`|\$\(|;\s*(?:rm|mv|cp|chmod|cat|wget|curl)").unwrap()) + .is_match(source) + { + return Err("Invalid proto file: suspicious content detected".to_owned()); } - None + Ok(source.to_owned()) } fn compile_proto(source: &str) -> Result<(String, String), String> { @@ -9402,6 +12719,14 @@ async fn logging_routes( username: &str, request_id: &str, ) -> Response { + if !state.config.logs_enabled { + return error( + StatusCode::NOT_FOUND, + "GTW003", + "Platform route does not exist", + request_id, + ); + } if method != Method::GET { return error( StatusCode::METHOD_NOT_ALLOWED, @@ -9416,6 +12741,11 @@ async fn logging_routes( let (code, message_text) = match path { "/logging/logs/export" => ("LOG003", "You do not have permission to export logs"), "/logging/logs/download" => ("LOG004", "You do not have permission to download logs"), + "/logging/logs/files" => ("LOG005", "You do not have permission to view log files"), + "/logging/logs/statistics" => ( + "LOG002", + "You do not have permission to view log statistics", + ), _ => ("LOG001", "You do not have permission to view logs"), }; return error(StatusCode::FORBIDDEN, code, message_text, request_id); @@ -9433,8 +12763,9 @@ async fn logging_routes( Ok(entry) => entry, Err(_) => return unexpected(request_id), }; - if entry.path().is_file() { - files.push(entry.file_name().to_string_lossy().into_owned()); + let name = entry.file_name().to_string_lossy().into_owned(); + if entry.path().is_file() && is_log_file_name(&name) { + files.push(name); } } } @@ -9560,6 +12891,13 @@ fn log_export( )) } +/// The pinned LoggingService only reads `doorman.log*` and +/// `doorman-trail.log*`; other files in the directory (such as the persisted +/// metrics documents) are not logs. +fn is_log_file_name(name: &str) -> bool { + name.starts_with("doorman.log") || name.starts_with("doorman-trail.log") +} + fn read_log_records( directory: &std::path::Path, query: &HashMap, @@ -9567,25 +12905,25 @@ fn read_log_records( ) -> Result, LogExportError> { let mut paths = Vec::new(); let entries = match fs::read_dir(directory) { - Ok(entries) => entries, - Err(error_value) if error_value.kind() == std::io::ErrorKind::NotFound => { - return Ok(Vec::new()); - } + Ok(entries) => Some(entries), + Err(error_value) if error_value.kind() == std::io::ErrorKind::NotFound => None, Err(_) => return Err(LogExportError::Read), }; - for entry in entries { - let entry = entry.map_err(|_| LogExportError::Read)?; - let file_type = entry.file_type().map_err(|_| LogExportError::Read)?; - if !file_type.is_file() { - continue; + if let Some(entries) = entries { + for entry in entries { + let entry = entry.map_err(|_| LogExportError::Read)?; + let file_type = entry.file_type().map_err(|_| LogExportError::Read)?; + if !file_type.is_file() || !is_log_file_name(&entry.file_name().to_string_lossy()) { + continue; + } + let metadata = entry.metadata().map_err(|_| LogExportError::Read)?; + paths.push(( + metadata.modified().unwrap_or(UNIX_EPOCH), + metadata.len(), + entry.path(), + )); } - let metadata = entry.metadata().map_err(|_| LogExportError::Read)?; - paths.push(( - metadata.modified().unwrap_or(UNIX_EPOCH), - metadata.len(), - entry.path(), - )); - } + }; paths.sort_by(|left, right| right.0.cmp(&left.0)); let mut read_bytes = 0_u64; @@ -9612,12 +12950,26 @@ fn read_log_records( break; } } + for line in crate::observability::logging::memory_log_snapshot() + .into_iter() + .rev() + .take(max_entries) + { + let Some(mut record) = parse_log_record(&line) else { + continue; + }; + if log_record_matches(&record, query) { + audit::redact_record(&mut record); + logs.push(record); + } + } logs.sort_by(|left, right| { right .get("timestamp") .and_then(Value::as_str) .cmp(&left.get("timestamp").and_then(Value::as_str)) }); + logs.truncate(max_entries); Ok(logs) } @@ -9709,17 +13061,140 @@ fn valid_log_date(value: &str) -> bool { } fn parse_log_record(line: &str) -> Option { - let mut record = serde_json::from_str::(line).ok()?; - let values = record.as_object_mut()?; - let timestamp = values - .remove("time") - .or_else(|| values.get("timestamp").cloned())?; - let source = values - .remove("name") - .unwrap_or(Value::String(String::new())); - values.insert("timestamp".to_owned(), timestamp); - values.insert("source".to_owned(), source); - Some(record) + let line = line.trim(); + if let Ok(mut record) = serde_json::from_str::(line) { + let values = record.as_object_mut()?; + let timestamp = values + .remove("time") + .or_else(|| values.get("timestamp").cloned()) + .unwrap_or_else(|| Value::String(time::OffsetDateTime::now_utc().to_string())); + let source = values + .remove("name") + .unwrap_or(Value::String(String::new())); + let message = values + .get("message") + .and_then(Value::as_str) + .unwrap_or_default(); + let structured = extract_log_fields(message); + for (key, value) in structured { + values.entry(key).or_insert(value); + } + values.insert("timestamp".to_owned(), timestamp); + values.insert("source".to_owned(), source); + return Some(record); + } + + let mut parts = line.splitn(4, " - "); + let timestamp = parts.next()?; + let source = parts.next()?; + let level = parts.next()?; + let full_message = parts.next()?; + let (request_id, message) = full_message + .split_once(" | ") + .map_or((None, full_message), |(request_id, message)| { + (Some(request_id), message) + }); + let mut values = extract_log_fields(message); + if let Some(request_id) = request_id { + values.insert("request_id".to_owned(), json!(request_id)); + } + values.insert( + "timestamp".to_owned(), + json!(timestamp.replace(' ', "T").replace(',', ".")), + ); + values.insert( + "level".to_owned(), + json!(if request_id.is_some() { level } else { "DEBUG" }), + ); + values.insert("message".to_owned(), json!(message)); + values.insert("source".to_owned(), json!(source)); + Some(Value::Object(values)) +} + +/// Compile each log-parsing pattern once; log queries parse up to thousands of +/// records, and compiling per record made them take tens of seconds. +fn cached_regex(pattern: &'static str) -> Option { + static CACHE: OnceLock>> = OnceLock::new(); + let mut cache = CACHE.get_or_init(Default::default).lock().ok()?; + if let Some(regex) = cache.get(pattern) { + return Some(regex.clone()); + } + let regex = Regex::new(pattern).ok()?; + cache.insert(pattern, regex.clone()); + Some(regex) +} + +fn extract_log_fields(message: &str) -> Map { + let mut values = Map::new(); + let capture = |pattern: &'static str, group: usize| { + cached_regex(pattern)? + .captures(message)? + .get(group) + .map(|value| value.as_str().to_owned()) + }; + if let Some(value) = capture(r"(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})", 1) { + values.insert("request_id".to_owned(), json!(value)); + } + if let Some(value) = capture(r"Username: (\w+)", 1) { + values.insert("user".to_owned(), json!(value)); + } + if let Some(value) = capture(r"(?:effective_ip|client_ip)=([A-Fa-f0-9:.]+)", 1) { + values.insert("ip_address".to_owned(), json!(value)); + } else if let Some(mut value) = capture(r"From:\s+(.+?)$", 1) { + if value.matches(':').count() > 1 { + if let Some((host, _)) = value.rsplit_once(':') { + value = host.to_owned(); + } + } else if let Some((host, _)) = value.split_once(':') { + value = host.to_owned(); + } + values.insert("ip_address".to_owned(), json!(value)); + } + if let Some(pattern) = cached_regex(r"Endpoint: (\w+) (.+)") + && let Some(captures) = pattern.captures(message) + { + values.insert("method".to_owned(), json!(&captures[1])); + values.insert("endpoint".to_owned(), json!(&captures[2])); + } + if let Some(value) = capture(r"Total time: ([\d.]+)ms", 1) { + values.insert("response_time".to_owned(), json!(value)); + } + if (message.contains("Status check failed") + || message.to_ascii_lowercase().contains("status_code")) + && let Some(value) = capture(r"(?i)status_code[:\s]+(\d+)", 1) + { + values.insert("status_code".to_owned(), json!(value)); + } + let endpoint = values + .get("endpoint") + .and_then(Value::as_str) + .unwrap_or_default() + .to_ascii_lowercase(); + let lower = message.to_ascii_lowercase(); + let request_type = if ["/rest", "/soap", "/graphql"] + .iter() + .any(|part| endpoint.contains(part)) + || [ + "rest gateway", + "soap gateway", + "graphql gateway", + "upstream", + ] + .iter() + .any(|part| lower.contains(part)) + { + "gateway" + } else if endpoint.contains("/authorization") + || ["login", "register", "token", "permission"] + .iter() + .any(|part| lower.contains(part)) + { + "auth" + } else { + "platform" + }; + values.insert("type".to_owned(), json!(request_type)); + values } fn log_record_matches(record: &Value, query: &HashMap) -> bool { @@ -9734,6 +13209,24 @@ fn log_record_matches(record: &Value, query: &HashMap) -> bool { { return false; } + let timestamp = value("timestamp"); + let timestamp_time = timestamp + .get(11..16) + .filter(|value| value.as_bytes().get(2) == Some(&b':')); + if let Some(start_time) = query + .get("start_time") + .filter(|value| !value.trim().is_empty()) + && timestamp_time.is_none_or(|value| value < start_time.as_str()) + { + return false; + } + if let Some(end_time) = query + .get("end_time") + .filter(|value| !value.trim().is_empty()) + && timestamp_time.is_none_or(|value| value > end_time.as_str()) + { + return false; + } for key in [ "user", "api", @@ -9744,17 +13237,23 @@ fn log_record_matches(record: &Value, query: &HashMap) -> bool { "level", "type", ] { - let Some(expected) = query.get(key) else { + let Some(expected) = query.get(key).filter(|value| !value.trim().is_empty()) else { continue; }; - if key == "level" { - if !value(key).eq_ignore_ascii_case(expected) { - return false; - } - } else if value(key) != expected { + if !value(key) + .to_ascii_lowercase() + .contains(&expected.trim().to_ascii_lowercase()) + { return false; } } + if let Some(expected) = query + .get("status_code") + .filter(|value| !value.trim().is_empty()) + && value("status_code") != expected.trim() + { + return false; + } let response_time = record.get("response_time").and_then(log_number); if let Some(minimum) = query .get("min_response_time") @@ -9772,6 +13271,20 @@ fn log_record_matches(record: &Value, query: &HashMap) -> bool { { return false; } + if let Some(excluded) = query + .get("exclude_type") + .filter(|value| !value.trim().is_empty()) + { + let excluded = excluded.trim().to_ascii_lowercase(); + if value("type").eq_ignore_ascii_case(&excluded) + || (excluded == "platform" + && value("endpoint") + .to_ascii_lowercase() + .contains("/platform/")) + { + return false; + } + } true } @@ -9907,13 +13420,16 @@ async fn discovery_parse( }; } - let content = payload - .get("content") - .or_else(|| payload.get("wsdl")) - .and_then(Value::as_str) - .map(str::to_owned) - .or_else(|| payload.as_str().map(str::to_owned)) - .unwrap_or_else(|| String::from_utf8_lossy(raw_body).into_owned()); + // The pinned route parses the raw request body as the WSDL document. + let content = String::from_utf8_lossy(raw_body).into_owned(); + if let Err(message) = crate::routes::discovery::validate_wsdl(&content) { + let message = if content.trim().is_empty() { + message + } else { + format!("Invalid WSDL: {message}") + }; + return error(StatusCode::BAD_REQUEST, "WSDL004", &message, request_id); + } match crate::routes::discovery::parse_wsdl(&content) { Ok(parsed) => success( StatusCode::OK, @@ -9958,7 +13474,17 @@ fn sign_token( }); header.kid = kid; if algorithm.eq_ignore_ascii_case("RS256") { - if let Some(key) = entry.get("private_key").and_then(Value::as_str) { + let key = entry + .get("private_key_path") + .and_then(Value::as_str) + .and_then(|path| fs::read_to_string(path).ok()) + .or_else(|| { + entry + .get("private_key") + .and_then(Value::as_str) + .map(str::to_owned) + }); + if let Some(key) = key { return encode( &header, claims, @@ -10018,24 +13544,34 @@ fn password_hash(user: &Value) -> Option { } } -fn self_update_fields_are_safe(payload: &Value) -> bool { - matches!( - payload.as_object(), - Some(fields) if fields.keys().all(|field| { - !matches!(field.as_str(), "role" | "groups" | "active" | "username") +/// Restricted fields the caller attempted to set, sorted like Python's +/// `sorted(attempted_restricted)`. Python only counts a field as "attempted" +/// when its incoming value is not null, so a null-valued restricted field is +/// treated as safe here too. +fn restricted_self_update_fields(payload: &Value) -> Vec { + let Some(fields) = payload.as_object() else { + return Vec::new(); + }; + let mut restricted: Vec = fields + .iter() + .filter(|(field, value)| { + matches!(field.as_str(), "role" | "groups" | "active" | "username") && !value.is_null() }) - ) + .map(|(field, _)| field.clone()) + .collect(); + restricted.sort(); + restricted } fn bootstrap_admin_update_fields_are_safe(payload: &Value) -> bool { matches!( payload.as_object(), - Some(fields) if fields.keys().all(|field| { + // Null values are not updates; bandwidth_limit_enabled is not operational. + Some(fields) if fields.iter().filter(|(_, value)| !value.is_null()).all(|(field, _)| { matches!( field.as_str(), "bandwidth_limit_bytes" | "bandwidth_limit_window" - | "bandwidth_limit_enabled" | "rate_limit_duration" | "rate_limit_duration_type" | "rate_limit_enabled" @@ -10051,10 +13587,10 @@ fn bootstrap_admin_update_fields_are_safe(payload: &Value) -> bool { } fn secure_password(password: &str) -> bool { - password.len() >= 16 - && password.chars().any(|c| c.is_ascii_uppercase()) - && password.chars().any(|c| c.is_ascii_lowercase()) - && password.chars().any(|c| c.is_ascii_digit()) + password.chars().count() >= 16 + && password.chars().any(char::is_uppercase) + && password.chars().any(char::is_lowercase) + && password.chars().any(char::is_numeric) && password .chars() .any(|c| "!@#$%^&*()-_=+[]{};:,.<>?/".contains(c)) @@ -10065,27 +13601,46 @@ fn password_policy() -> &'static str { } fn cookie_secure(headers: &HeaderMap) -> bool { - env::var("COOKIE_SECURE") + let https_only = env_bool("HTTPS_ONLY", false); + let inferred_secure = headers + .get("x-forwarded-proto") + .and_then(|value| value.to_str().ok()) + .is_some_and(|value| value.eq_ignore_ascii_case("https")); + let secure = env::var("COOKIE_SECURE") .ok() .map(|value| value.eq_ignore_ascii_case("true")) - .unwrap_or_else(|| { - env_bool("HTTPS_ONLY", false) - || headers - .get("x-forwarded-proto") - .and_then(|value| value.to_str().ok()) - .is_some_and(|value| value.eq_ignore_ascii_case("https")) - }) + .unwrap_or(https_only || inferred_secure); + // Plain-HTTP local hosts never get Secure cookies unless HTTPS_ONLY is set, + // so local runs stay usable (pinned behaviour). + let host = headers + .get("x-forwarded-host") + .or_else(|| headers.get(header::HOST)) + .and_then(|value| value.to_str().ok()) + .map(|value| { + value + .rsplit_once(':') + .filter(|(_, port)| port.chars().all(|c| c.is_ascii_digit())) + .map_or(value, |(name, _)| name) + .to_ascii_lowercase() + }); + if !inferred_secure + && !https_only + && host + .is_some_and(|host| matches!(host.as_str(), "localhost" | "127.0.0.1" | "testserver")) + { + return false; + } + secure } fn cookie_same_site(secure: bool) -> &'static str { - match env::var("COOKIE_SAMESITE") - .unwrap_or_else(|_| "strict".to_owned()) - .to_ascii_lowercase() - .as_str() - { + let raw = env::var("COOKIE_SAMESITE").unwrap_or_default(); + let raw = raw.trim().to_ascii_lowercase(); + match raw.as_str() { + "" | "strict" => "Strict", "none" if secure => "None", - "lax" | "none" => "Lax", - _ => "Strict", + // Unknown values and None without Secure fall back to Lax. + _ => "Lax", } } @@ -10348,34 +13903,48 @@ fn content_type_is_json(headers: &HeaderMap) -> bool { }) } -fn is_typed_json_mutation(path: &str, method: &Method) -> bool { - if !matches!(method, &Method::POST | &Method::PUT | &Method::PATCH) { - return false; +/// Routes whose Python counterpart declares a required (non-Optional) typed +/// Pydantic body parameter, so FastAPI's global RequestValidationError +/// handler returns 422 VAL001 for a request with no body at all -- before +/// authentication, permission, or existence checks run -- regardless of +/// Content-Type. Confirmed against the pinned reference for every listed +/// route; routes with an `Optional`/defaulted body (memory dump/restore, +/// security settings) are deliberately excluded. +/// Python's service-layer message for a missing entity; collections without a +/// dedicated service message use the generic one. +fn not_found_message(collection: &str) -> &'static str { + match collection { + "roles" => "Role does not exist", + "groups" => "Group does not exist", + "routings" => "Routing does not exist", + _ => "Resource not found", + } +} + +fn requires_nonempty_json_body(path: &str, method: &Method) -> bool { + match *method { + Method::POST => { + path == "/credit/rotate-key" + || (path.starts_with("/proto/") && path != "/proto/descriptors/backfill") + || (path.starts_with("/rate-limits/") && path.ends_with("/duplicate")) + } + Method::PUT => { + path.starts_with("/api/") + || path.starts_with("/endpoint/") + || path.starts_with("/group/") + || path.starts_with("/proto/") + || (path.starts_with("/rate-limits/") && !path.ends_with("/duplicate")) + || path.starts_with("/role/") + || path.starts_with("/routing/") + || ((path.starts_with("/user/") || path.starts_with("/users/")) + && !path.ends_with("/update-password")) + || path.starts_with("/vault/") + || (path + .strip_prefix("/tiers/") + .is_some_and(|suffix| !suffix.is_empty() && !suffix.contains('/'))) + } + _ => false, } - [ - "/api", - "/apis", - "/endpoint", - "/endpoints", - "/credit", - "/config/import", - "/group", - "/memory/dump", - "/memory/restore", - "/role", - "/routing", - "/security/settings", - "/tiers", - "/tools/chaos/toggle", - "/tools/cors/check", - "/rate-limits", - "/subscription", - "/user", - "/users", - "/vault", - ] - .iter() - .any(|prefix| path == *prefix || path.starts_with(&format!("{prefix}/"))) } fn is_subscription_mutation(path: &str, method: &Method) -> bool { @@ -10386,6 +13955,13 @@ fn is_subscription_mutation(path: &str, method: &Method) -> bool { ) } +fn is_update_password_path(path: &str) -> bool { + ["/user/", "/users/"].iter().any(|prefix| { + path.strip_prefix(prefix) + .is_some_and(|suffix| !suffix.is_empty() && suffix.ends_with("/update-password")) + }) +} + fn subscription_payload_has_required_fields(payload: &Value) -> bool { ["username", "api_name", "api_version"] .iter() @@ -10422,30 +13998,56 @@ fn paginate(items: Vec, query: &HashMap) -> Value { /// Validate client pagination before applying the page window. This prevents /// invalid values from being silently normalized and honors the configured /// maximum on each request. -fn validate_pagination(query: &HashMap) -> Result<(), &'static str> { +fn validate_pagination(query: &HashMap) -> Result<(), String> { if query .get("page") .is_some_and(|value| value.parse::().ok().is_none_or(|number| number == 0)) { - return Err("page must be a positive integer"); + return Err("page must be >= 1".to_owned()); } let Some(page_size) = query.get("page_size") else { return Ok(()); }; let Some(page_size) = page_size.parse::().ok().filter(|number| *number > 0) else { - return Err("page_size must be a positive integer"); + return Err("page_size must be >= 1".to_owned()); }; - if let Some(maximum) = env::var("MAX_PAGE_SIZE") - .ok() - .and_then(|value| value.parse::().ok()) - .filter(|number| *number > 0) - && page_size > maximum - { - return Err("page_size exceeds the configured maximum"); + if page_size > configured_max_page_size() { + return Err(format!( + "page_size must be <= {}", + configured_max_page_size() + )); } Ok(()) } +fn configured_max_page_size() -> usize { + env::var("MAX_PAGE_SIZE") + .ok() + .filter(|value| !value.trim().is_empty()) + .and_then(|value| value.parse::().ok()) + .map(|value| value.max(1) as usize) + .unwrap_or(200) +} + +/// The page window (default 100 per page) without any envelope. +fn paginate_items(items: Vec, query: &HashMap) -> Vec { + let page = query + .get("page") + .and_then(|value| value.parse::().ok()) + .unwrap_or(1) + .max(1); + let page_size = query + .get("page_size") + .and_then(|value| value.parse::().ok()) + .unwrap_or(100) + .clamp(1, 1000); + items + .into_iter() + .skip((page - 1).saturating_mul(page_size)) + .take(page_size) + .collect() +} + fn paginate_named(items: Vec, query: &HashMap, name: &str) -> Value { let page = query .get("page") @@ -10502,6 +14104,9 @@ fn public_user(mut value: Value) -> Value { fn schedule_restart() -> Result<(), (&'static str, &'static str)> { let mut candidates = Vec::new(); + if let Some(path) = env::var_os("PID_FILE") { + candidates.push(std::path::PathBuf::from(path)); + } if let Some(path) = env::var_os("DOORMAN_PID_FILE") { candidates.push(std::path::PathBuf::from(path)); } @@ -10578,10 +14183,33 @@ fn http_detail(status: StatusCode, detail: &str, request_id: &str) -> Response { json_response(status, json!({"detail": detail}), request_id) } +/// FastAPI's request validation failures all pass through the pinned global +/// RequestValidationError handler, which renders the VAL001 envelope; the +/// Pydantic error list is only logged, never returned. fn validation_errors(errors: Vec, request_id: &str) -> Response { - json_response( + let errors = Value::Array(errors); + tracing::debug!(%errors, "request validation failed"); + error( StatusCode::UNPROCESSABLE_ENTITY, - json!({"detail": errors}), + "VAL001", + "Validation Error", + request_id, + ) +} + +/// The pinned tier routes return a bare `ResponseModel`, which FastAPI +/// serializes in full rather than through the respond_rest envelope. +fn response_model_envelope(message_text: &str, request_id: &str) -> Response { + success( + StatusCode::OK, + json!({ + "status_code": 200, + "response_headers": null, + "response": null, + "message": message_text, + "error_code": null, + "error_message": null, + }), request_id, ) } @@ -10595,7 +14223,7 @@ fn unexpected(request_id: &str) -> Response { ) } -fn json_response(status: StatusCode, payload: Value, request_id: &str) -> Response { +pub(crate) fn json_response(status: StatusCode, payload: Value, request_id: &str) -> Response { let body = serde_json::to_vec(&payload).unwrap_or_else(|_| b"{}".to_vec()); let length = body.len().to_string(); let mut response = ( @@ -10629,12 +14257,6 @@ fn env_bool(name: &str, default: bool) -> bool { .unwrap_or(default) } -fn timestamp_now() -> String { - time::OffsetDateTime::now_utc() - .format(&time::format_description::well_known::Rfc3339) - .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_owned()) -} - // VaultService stores `datetime.now(UTC).isoformat()` directly. Unlike the // RFC3339 formatter used elsewhere, that Python spelling ends in `+00:00`. fn timestamp_now_python_utc() -> String { @@ -10687,6 +14309,337 @@ fn unix_seconds() -> u64 { mod tests { use super::*; + #[test] + fn only_pinned_log_file_patterns_are_read_as_logs() { + assert!(is_log_file_name("doorman.log")); + assert!(is_log_file_name("doorman.log.3")); + assert!(is_log_file_name("doorman-trail.log")); + assert!(!is_log_file_name("metrics.json")); + assert!(!is_log_file_name("enhanced_metrics.json")); + } + + #[test] + fn parse_report_timestamp_matches_pythons_strptime_cascade() { + // "%Y-%m-%dT%H:%M" + assert_eq!( + parse_report_timestamp("2024-01-15T10:30").unwrap(), + 1_705_314_600 + ); + // "%Y-%m-%d" (midnight UTC) + assert_eq!(parse_report_timestamp("2024-01-15").unwrap(), 1_705_276_800); + // fromisoformat fallback: space separator and a trailing Z. + assert_eq!( + parse_report_timestamp("2024-01-15T10:30:00Z").unwrap(), + 1_705_314_600 + ); + // Seconds in the fromisoformat fallback are preserved, not truncated. + assert_eq!( + parse_report_timestamp("2024-01-15T10:30:45Z").unwrap(), + 1_705_314_645 + ); + assert!(parse_report_timestamp("not-a-date").is_err()); + assert_eq!( + parse_report_timestamp("garbage").unwrap_err(), + "Invalid date format. Use YYYY-MM-DD or YYYY-MM-DDTHH:MM" + ); + } + + #[test] + fn report_date_time_utc_matches_python_strftime() { + assert_eq!( + report_date_time_utc(1_705_314_600), + ("2024-01-15".to_owned(), "10:30".to_owned()) + ); + } + + #[test] + fn report_api_from_endpoint_matches_python_classification() { + assert_eq!( + report_api_from_endpoint("/api/rest/orders/v1/list"), + "rest:orders" + ); + // Matches Python: `ep.split('/')` on "/api/rest/" yields a trailing + // empty segment, so `parts[3]` is "" rather than ever reaching the + // `len(parts) > 3` "unknown" fallback (unreachable via this prefix). + assert_eq!(report_api_from_endpoint("/api/rest/"), "rest:"); + assert_eq!( + report_api_from_endpoint("/api/graphql/catalog"), + "graphql:catalog" + ); + assert_eq!( + report_api_from_endpoint("/api/soap/billing"), + "soap:billing" + ); + assert_eq!(report_api_from_endpoint("/platform/users"), "platform"); + } + + #[test] + fn analytics_percentiles_pools_raw_samples_like_python_percentile_metrics() { + let mut a = AggregatedPoint { + timestamp: 0, + requests: 2, + ..Default::default() + }; + a.latencies_ms.extend([10.0, 20.0]); + let mut b = AggregatedPoint { + timestamp: 60, + requests: 1, + ..Default::default() + }; + b.latencies_ms.extend([100.0]); + // Pooled sorted samples: [10, 20, 100], n = 3. + // k = max(0, int(p*3) - 1): p50 -> int(1.5)-1=0 -> 10; p95 -> int(2.85)-1=1 -> 20. + let result = analytics_percentiles(&[a, b]); + assert_eq!(result["p50"], json!(10.0)); + assert_eq!(result["p95"], json!(20.0)); + assert_eq!(result["min"], json!(10.0)); + assert_eq!(result["max"], json!(100.0)); + + let empty = analytics_percentiles(&[]); + assert_eq!(empty["p50"], json!(0.0)); + assert_eq!(empty["min"], json!(0.0)); + } + + #[test] + fn restricted_self_update_fields_matches_python_attempted_set() { + // Null-valued restricted fields are not "attempted" in Python, since it + // filters `v is not None` before intersecting with the restricted set. + assert!(restricted_self_update_fields(&json!({"email": "a@b.com"})).is_empty()); + assert!(restricted_self_update_fields(&json!({"role": null})).is_empty()); + assert_eq!( + restricted_self_update_fields(&json!({ + "username": "new", "active": true, "role": "admin", "email": "a@b.com" + })), + vec!["active", "role", "username"] + ); + } + + #[test] + fn tier_request_models_coerce_scalars_and_apply_defaults() { + let mut create = json!({ + "tier_id": 7, + "name": "not-an-enum", + "display_name": true, + "limits": {"requests_per_minute": "12", "enable_throttling": "yes"}, + "price_monthly": "19.5", + "features": [1, false], + "enabled": "off", + "ignored": "field" + }); + normalize_tier_request("/tiers", &Method::POST, &mut create).unwrap(); + assert_eq!(create["tier_id"], "7"); + assert_eq!(create["name"], "not-an-enum"); + assert_eq!(create["display_name"], "True"); + assert_eq!(create["limits"]["requests_per_minute"], 12); + assert_eq!(create["limits"]["burst_per_second"], 0); + assert_eq!(create["limits"]["max_queue_time_ms"], 5000); + assert_eq!(create["limits"]["enable_throttling"], true); + assert_eq!(create["price_monthly"], 19.5); + assert_eq!(create["features"], json!(["1", "False"])); + assert_eq!(create["is_default"], false); + assert_eq!(create["enabled"], false); + assert!(create.get("ignored").is_none()); + + let mut action = json!({ + "user_id": 42, + "new_tier_id": false, + "grace_period_days": "3", + "ignored": true + }); + normalize_tier_request("/tiers/downgrade", &Method::POST, &mut action).unwrap(); + assert_eq!( + action, + json!({"user_id": "42", "new_tier_id": "False", "grace_period_days": 3}) + ); + + let mut assignment = json!({ + "user_id": "dated", + "tier_id": "pro", + "effective_from": 0, + "effective_until": "2020-1-2 3:04:05.12Z" + }); + normalize_tier_request("/tiers/assignments", &Method::POST, &mut assignment).unwrap(); + assert_eq!(assignment["effective_from"], "1970-01-01T00:00:00+00:00"); + assert_eq!( + assignment["effective_until"], + "2020-01-02T03:04:05.120000+00:00" + ); + } + + #[test] + fn tier_request_models_reject_invalid_shapes_and_drop_update_nulls() { + for mut invalid in [ + json!({"tier_id": "x", "name": "free", "display_name": "X", "limits": []}), + json!({"user_id": "x", "temp_tier_id": "pro", "duration_days": null}), + json!({"user_id": "x", "new_tier_id": "pro", "immediate": "sometimes"}), + json!({"user_id": "x", "tier_id": "pro", "effective_from": "not-a-date"}), + json!({"tier_ids": ["free"]}), + ] { + let (path, method) = if invalid.get("effective_from").is_some() { + ("/tiers/assignments", Method::POST) + } else if invalid.get("tier_id").is_some() { + ("/tiers", Method::POST) + } else if invalid.get("temp_tier_id").is_some() { + ("/tiers/temporary-upgrade", Method::POST) + } else if invalid.get("new_tier_id").is_some() { + ("/tiers/upgrade", Method::POST) + } else { + ("/tiers/compare", Method::POST) + }; + assert!(normalize_tier_request(path, &method, &mut invalid).is_err()); + } + + let mut update = json!({ + "display_name": null, + "limits": null, + "enabled": null, + "features": null, + "price_monthly": null, + "description": null + }); + normalize_tier_request("/tiers/pro", &Method::PUT, &mut update).unwrap(); + assert_eq!(update, json!({})); + + for (value, expected) in [ + ("true", Some(true)), + ("YES", Some(true)), + ("on", Some(true)), + ("1", Some(true)), + ("false", Some(false)), + ("No", Some(false)), + ("off", Some(false)), + ("0", Some(false)), + (" true ", None), + ] { + assert_eq!(pydantic_bool_string(value), expected); + } + } + + #[tokio::test] + async fn tier_exception_envelopes_match_each_python_route() { + for (operation, detail) in [ + ("get", "Failed to get tier"), + ("update", "Failed to update tier"), + ("delete", "Failed to delete tier"), + ("assign", "Failed to assign user"), + ("get_assignment", "Failed to get assignment"), + ("get_user_tier", "Failed to get user tier"), + ("remove_assignment", "Failed to remove assignment"), + ("list_users", "Failed to list users"), + ("upgrade", "Failed to upgrade tier"), + ("downgrade", "Failed to downgrade tier"), + ("temporary-upgrade", "Failed to create temporary upgrade"), + ("trial/start", "Failed to start trial"), + ("payment/failure", "Failed to handle payment failure"), + ("compare", "Failed to compare tiers"), + ] { + let response = tier_detail_failure(operation, "tier-error"); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + let body = axum::body::to_bytes(response.into_body(), 1024) + .await + .unwrap(); + assert_eq!( + serde_json::from_slice::(&body).unwrap()["detail"], + detail + ); + } + + let response = tier_statistics_failure("tier-error"); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + let body = axum::body::to_bytes(response.into_body(), 1024) + .await + .unwrap(); + let body: Value = serde_json::from_slice(&body).unwrap(); + assert_eq!(body["error_code"], "TIER999"); + assert_eq!(body["error_message"], "Failed to get statistics"); + } + + #[tokio::test] + async fn rate_rule_exception_envelopes_match_each_python_route() { + for (operation, detail) in [ + ("create", "Failed to create rule"), + ("list", "Failed to list rules"), + ("search", "Failed to search rules"), + ("get", "Failed to get rule"), + ("update", "Failed to update rule"), + ("delete", "Failed to delete rule"), + ("enable", "Failed to enable rule"), + ("disable", "Failed to disable rule"), + ("bulk/delete", "Failed to delete rules"), + ("bulk/enable", "Failed to enable rules"), + ("bulk/disable", "Failed to disable rules"), + ("duplicate", "Failed to duplicate rule"), + ("statistics", "Failed to get statistics"), + ("status", "Failed to get rate limit status"), + ] { + let response = rate_rule_failure(operation, "rule-error"); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + let body = axum::body::to_bytes(response.into_body(), 1024) + .await + .unwrap(); + assert_eq!( + serde_json::from_slice::(&body).unwrap()["detail"], + detail + ); + } + } + + #[tokio::test] + async fn quota_exception_envelopes_match_each_python_route() { + for (path, detail) in [ + ("/quota/status", "Failed to get quota status"), + ( + "/quota/status/monthly_requests", + "Failed to get quota status", + ), + ("/quota/usage/history", "Failed to get usage history"), + ("/quota/usage/export", "Failed to export usage data"), + ("/quota/tier/info", "Failed to get tier info"), + ("/quota/burst/status", "Failed to get burst status"), + ] { + let response = quota_route_failure(path, "quota-error"); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + let body = axum::body::to_bytes(response.into_body(), 1024) + .await + .unwrap(); + assert_eq!( + serde_json::from_slice::(&body).unwrap()["detail"], + detail + ); + } + } + + #[test] + fn log_parsing_and_filters_match_python_service() { + let record = parse_log_record( + "2026-09-26 10:11:12,345 - doorman.gateway - INFO - 12345678-1234-1234-1234-123456789012 | Username: Alice Endpoint: GET /rest/pets Total time: 12.5ms status_code: 200 From: 127.0.0.1:1234", + ) + .unwrap(); + assert_eq!(record["timestamp"], "2026-09-26T10:11:12.345"); + assert_eq!(record["user"], "Alice"); + assert_eq!(record["method"], "GET"); + assert_eq!(record["type"], "gateway"); + assert_eq!(record["ip_address"], "127.0.0.1"); + + let filters = HashMap::from([ + ("user".to_owned(), "ali".to_owned()), + ("endpoint".to_owned(), "/REST".to_owned()), + ("start_time".to_owned(), "10:00".to_owned()), + ("end_time".to_owned(), "11:00".to_owned()), + ("status_code".to_owned(), "200".to_owned()), + ]); + assert!(log_record_matches(&record, &filters)); + assert!(!log_record_matches( + &record, + &HashMap::from([("exclude_type".to_owned(), "gateway".to_owned())]) + )); + + let debug = + parse_log_record("2026-09-26 10:11:12 - doorman.gateway - ERROR - startup complete") + .unwrap(); + assert_eq!(debug["level"], "DEBUG"); + } + // Oracle: pinned models/security_settings_model.py, Pydantic 1.10.26. #[test] fn security_settings_scalar_coercion_matches_python_model() { @@ -10952,13 +14905,13 @@ mod tests { #[test] fn self_updates_without_user_management_cannot_change_authorization_fields() { - assert!(self_update_fields_are_safe( + assert!(restricted_self_update_fields( &json!({"email": "user@example.com", "custom_attributes": {}, "rate_limit_duration": 1}) - )); - assert!(!self_update_fields_are_safe(&json!({"groups": ["admin"]}))); - assert!(!self_update_fields_are_safe(&json!({"role": "admin"}))); - assert!(!self_update_fields_are_safe(&json!({"active": true}))); - assert!(!self_update_fields_are_safe(&json!({"username": "other"}))); + ).is_empty()); + assert!(!restricted_self_update_fields(&json!({"groups": ["admin"]})).is_empty()); + assert!(!restricted_self_update_fields(&json!({"role": "admin"})).is_empty()); + assert!(!restricted_self_update_fields(&json!({"active": true})).is_empty()); + assert!(!restricted_self_update_fields(&json!({"username": "other"})).is_empty()); } #[test] @@ -10991,6 +14944,25 @@ mod tests { Uuid::new_v4().simple().to_string().to_uppercase() ); assert!(!secure_password(&without_special)); + + // 16 characters but 18 bytes passes; 14 characters (16 bytes) must not. + assert!(secure_password(&non_ascii_test_password(16))); + assert!(!secure_password(&non_ascii_test_password(14))); + } + + fn non_ascii_test_password(chars: usize) -> String { + let bytes = Uuid::new_v4().into_bytes(); + let uppercase = char::from_u32(0xC0 + u32::from(bytes[0] % 0x17)).unwrap(); + let lowercase = char::from_u32(0xE0 + u32::from(bytes[1] % 0x17)).unwrap(); + let digit = char::from(bytes[2] % 10 + b'0'); + let special = char::from(bytes[3] % 15 + b'!'); + let padding = Uuid::new_v4() + .simple() + .to_string() + .chars() + .take(chars - 4) + .collect::(); + format!("{uppercase}{lowercase}{digit}{special}{padding}") } fn test_password() -> String { @@ -11115,7 +15087,7 @@ mod tests { assert_eq!(wildcard_blocked["actual"]["allowed"], false); assert_eq!( wildcard_blocked["preflight"]["response_headers"]["Access-Control-Allow-Origin"], - "" + Value::Null ); assert_eq!( wildcard_blocked["preflight"]["response_headers"]["Access-Control-Allow-Credentials"], diff --git a/gateway-rs/src/routes/rest.rs b/gateway-rs/src/routes/rest.rs index d13d39e8..75a0ecd0 100644 --- a/gateway-rs/src/routes/rest.rs +++ b/gateway-rs/src/routes/rest.rs @@ -11,7 +11,7 @@ use axum::{ response::{IntoResponse, Response}, }; use http::{HeaderMap, HeaderName, HeaderValue, StatusCode, header}; -use serde_json::Value; +use serde_json::{Value, json}; use crate::{ error::GatewayError, @@ -20,12 +20,14 @@ use crate::{ transforms::{transform_request, transform_response}, }, middleware::{ - body_limit::BodyLimits, + body_limit::{BodyLimits, NoCompression}, cors::{apply_actual_response, preflight_response}, }, policy::{ PolicyErrorBody, - evaluator::{PolicyRequest, PolicyRuntime, evaluate_rest_policy, evaluate_shared_effects}, + evaluator::{ + PolicyRequest, PolicyRuntime, evaluate_rest_policy_staged, evaluate_shared_effects, + }, }, state::AppState, }; @@ -42,6 +44,73 @@ pub enum DataPlaneProtocol { #[derive(Clone, Debug)] pub struct PolicyPath(pub String); +/// The pinned tier middleware runs before the body-size middleware, which +/// rejects a declared oversized body before routing and endpoint policy. +pub(crate) async fn early_body_limit( + state: &AppState, + path: &str, + headers: &HeaderMap, + is_preflight: bool, + protocol: DataPlaneProtocol, +) -> Option { + let limits = BodyLimits::from_env(); + let limit = match protocol { + DataPlaneProtocol::Rest => limits.rest, + DataPlaneProtocol::Graphql => limits.graphql, + DataPlaneProtocol::Soap => limits.soap, + DataPlaneProtocol::Grpc | DataPlaneProtocol::GrpcWeb => limits.grpc, + }; + let limit = BodyLimits::for_path(path, limit); + let declared = headers + .get(header::CONTENT_LENGTH) + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.trim().parse::().ok())?; + if declared <= limit as u64 { + return None; + } + let tier_username = (!state.config.shared_storage.skip_tier_rate_limit && !is_preflight) + .then(|| { + crate::policy::auth::verify_request_token(headers, &state.config.shared_storage) + .ok() + .and_then(|claims| claims.sub) + }) + .flatten(); + if let (Some(storage), Some(username)) = (&state.storage, tier_username) { + let injected_snapshot = state + .policy_documents + .as_ref() + .map(|injected| injected.lock().ok().map(|documents| documents.clone())); + let documents = match injected_snapshot { + Some(snapshot) => snapshot, + None => storage.load_policy_documents().await.ok(), + }; + if let Some(documents) = documents + && let Err(failure) = crate::policy::tier::enforce( + &documents, + storage, + &username, + now_millis() / 1_000, + true, + ) + .await + && let Some(tier_limit) = failure.tier_limit + { + let (body, status) = *tier_limit; + let mut response = (failure.status, Json(body)).into_response(); + apply_tier_headers(&mut response, Some(&status)); + response.extensions_mut().insert(NoCompression); + return Some(response); + } + } + let mut response = policy_error_response( + StatusCode::PAYLOAD_TOO_LARGE, + "REQ001", + &format!("Request entity too large (max: {limit} bytes)"), + ); + response.extensions_mut().insert(NoCompression); + Some(response) +} + pub async fn rest_policy_then_proxy( State(state): State, request: Request, @@ -63,6 +132,17 @@ pub async fn rest_policy_then_proxy( .get::() .copied() .unwrap_or(DataPlaneProtocol::Rest); + if let Some(response) = early_body_limit( + &state, + request.uri().path(), + request.headers(), + request.method() == http::Method::OPTIONS, + protocol, + ) + .await + { + return Ok(response); + } let path = request .extensions() .get::() @@ -77,7 +157,13 @@ pub async fn rest_policy_then_proxy( let peer = request .extensions() .get::>() - .map(|value| value.0.ip()); + .map(|value| value.0.ip()) + .or_else(|| { + request + .extensions() + .get::>() + .map(|value| value.0.peer.ip()) + }); let headers = request.headers().clone(); let content_length = headers .get(header::CONTENT_LENGTH) @@ -90,6 +176,12 @@ pub async fn rest_policy_then_proxy( .and_then(|value| value.to_str().ok()) .and_then(|value| http::Method::from_bytes(value.as_bytes()).ok()) .unwrap_or(http::Method::OPTIONS) + } else if matches!(protocol, DataPlaneProtocol::Grpc | DataPlaneProtocol::Soap) + && request.method() == http::Method::GET + { + // The pinned JSON gRPC and SOAP gateways match endpoints as POST + // (`'POST/' + endpoint_uri` for SOAP) whatever the request method. + http::Method::POST } else { request.method().clone() }; @@ -100,6 +192,20 @@ pub async fn rest_policy_then_proxy( direct_ip: peer, now_millis: now_millis(), content_length, + peer_certificates: request + .extensions() + .get::>() + .map(|info| info.0.peer_certificates.clone()) + .unwrap_or_default(), + native_tls: request + .extensions() + .get::>() + .is_some(), + is_preflight: request.method() == http::Method::OPTIONS, + group_version_missing: matches!(protocol, DataPlaneProtocol::Grpc) + && !request.headers().contains_key("x-api-version"), + route_endpoint_check: protocol == DataPlaneProtocol::Rest, + endpoint_required: matches!(protocol, DataPlaneProtocol::Rest | DataPlaneProtocol::Soap), }; let documents = if let Some(injected) = &state.policy_documents { @@ -116,36 +222,71 @@ pub async fn rest_policy_then_proxy( Err("shared policy storage is unavailable".to_owned()) }; let result = match documents { - Ok(mut documents) => match evaluate_rest_policy( - &mut documents, - &policy_request, - &state.config.shared_storage, - &PolicyRuntime::default(), - ) { - Ok(Some(mut decision)) => { - if let Some(storage) = &state.storage { - evaluate_shared_effects( - &documents, - &policy_request, - &mut decision, - storage, - true, + Ok(mut documents) => { + // The pinned tier middleware runs before routing, for every gateway + // request whose token verifies. + let mut tier_status = None; + let tier_user = (!state.config.shared_storage.skip_tier_rate_limit + && !policy_request.is_preflight) + .then(|| { + crate::policy::auth::verify_request_token( + &policy_request.headers, + &state.config.shared_storage, ) - .await - .map(|()| Some(decision)) - } else if state.policy_documents.is_some() { - Ok(Some(decision)) - } else { - Err(crate::policy::PolicyFailure::new( - crate::policy::PolicyStage::Resolution, - StatusCode::SERVICE_UNAVAILABLE, - "GTW006", - "Gateway state store unavailable", - )) + .ok() + .and_then(|claims| claims.sub) + }) + .flatten(); + let tier_result = match (&state.storage, tier_user.as_deref()) { + (Some(storage), Some(user)) => crate::policy::tier::enforce( + &documents, + storage, + user, + policy_request.now_millis / 1_000, + true, + ) + .await + .map(|status| tier_status = status), + _ => Ok(()), + }; + match tier_result.and_then(|()| { + evaluate_rest_policy_staged( + &mut documents, + &policy_request, + &state.config.shared_storage, + &PolicyRuntime { + tls_profiles: state.tls_snapshot().profiles.clone(), + ..PolicyRuntime::default() + }, + ) + }) { + Ok(Some(mut decision)) => { + decision.tier_rate_limit_enabled = false; + decision.tier_limit_status = tier_status.clone(); + if let Some(storage) = &state.storage { + evaluate_shared_effects( + &documents, + &policy_request, + &mut decision, + storage, + true, + ) + .await + .map(|()| Some(decision)) + } else if state.policy_documents.is_some() { + Ok(Some(decision)) + } else { + Err(crate::policy::PolicyFailure::new( + crate::policy::PolicyStage::Resolution, + StatusCode::SERVICE_UNAVAILABLE, + "GTW006", + "Gateway state store unavailable", + )) + } } + other => other, } - other => other, - }, + } Err(error) => { tracing::error!(error = %error, "rust policy storage unavailable"); Err(crate::policy::PolicyFailure::new( @@ -156,6 +297,13 @@ pub async fn rest_policy_then_proxy( )) } }; + // Tier limits (applied above as shared effects) precede the service-level checks. + let result = result.and_then(|decision| match decision { + Some(decision) if decision.deferred_failure.is_some() => { + Err(decision.deferred_failure.expect("checked above")) + } + other => Ok(other), + }); match result { Ok(Some(decision)) => { if request.method() == http::Method::OPTIONS { @@ -193,6 +341,13 @@ pub async fn rest_policy_then_proxy( }); Ok(response) } + // The pinned SOAP service renders its not-found error as XML too. + Ok(None) if protocol == DataPlaneProtocol::Soap => Ok(( + StatusCode::NOT_FOUND, + [(header::CONTENT_TYPE, "application/xml")], + "GTW001API does not exist for the requested name and version", + ) + .into_response()), Ok(None) => Ok(( StatusCode::NOT_FOUND, Json(PolicyErrorBody { @@ -211,7 +366,7 @@ pub async fn rest_policy_then_proxy( "1" | "true" | "yes" | "on" ) }) - && failure.error_code == "GTW003" + && matches!(failure.error_code.as_str(), "GTW002" | "GTW003") { return Ok(StatusCode::METHOD_NOT_ALLOWED.into_response()); } @@ -221,6 +376,22 @@ pub async fn rest_policy_then_proxy( apply_tier_headers(&mut response, Some(&status)); return Ok(response); } + // The pinned SOAP service renders its own 400/403/404 errors as + // an XML document (process_soap_response). + if protocol == DataPlaneProtocol::Soap + && failure.stage == crate::policy::PolicyStage::Resolution + && matches!(failure.status.as_u16(), 400 | 403 | 404) + { + return Ok(( + failure.status, + [(header::CONTENT_TYPE, "application/xml")], + format!( + "{}{}", + failure.error_code, failure.error_message + ), + ) + .into_response()); + } Ok(( failure.status, Json(PolicyErrorBody { @@ -322,11 +493,40 @@ async fn execute_rest( ) .into_response()); }; + if !matches!( + protocol, + DataPlaneProtocol::Grpc | DataPlaneProtocol::GrpcWeb + ) && decision + .upstream_tls_profile_id + .as_deref() + .and_then(|id| state.tls_snapshot().profiles.upstreams.get(id).cloned()) + .is_some_and(|profile| profile.server_name.is_some()) + { + return Ok(( + StatusCode::SERVICE_UNAVAILABLE, + Json(PolicyErrorBody { + error_code: "TLS004".to_owned(), + error_message: "HTTP upstream TLS server-name override is unsupported".to_owned(), + }), + ) + .into_response()); + } + let Some(proxy_client) = state.proxy_client_for(decision.upstream_tls_profile_id.as_deref()) + else { + return Ok(( + StatusCode::SERVICE_UNAVAILABLE, + Json(PolicyErrorBody { + error_code: "TLS004".to_owned(), + error_message: "Upstream TLS profile is unavailable".to_owned(), + }), + ) + .into_response()); + }; if !circuit_allows(&state.runtime.circuits, &circuit_key) { return Ok(( StatusCode::SERVICE_UNAVAILABLE, Json(PolicyErrorBody { - error_code: "GTW006".to_owned(), + error_code: "GTW999".to_owned(), error_message: "Upstream circuit open".to_owned(), }), ) @@ -513,8 +713,7 @@ async fn execute_rest( let mut attempt = 0_u32; let upstream = loop { attempt += 1; - let result = state - .proxy_client + let result = proxy_client .request(parts.method.clone(), target.clone()) .headers(headers.clone()) .timeout(std::time::Duration::from_millis( @@ -533,6 +732,15 @@ async fn execute_rest( retry_backoff(attempt).await; } Ok(response) => break response, + Err(error) if error.is_timeout() && attempt < attempts => { + record_failure(&state.runtime.circuits, &circuit_key); + state.runtime.retries_total.fetch_add(1, Ordering::Relaxed); + state + .runtime + .upstream_timeouts_total + .fetch_add(1, Ordering::Relaxed); + retry_backoff(attempt).await; + } Err(_) if attempt < attempts => { record_failure(&state.runtime.circuits, &circuit_key); state.runtime.retries_total.fetch_add(1, Ordering::Relaxed); @@ -565,7 +773,7 @@ async fn execute_rest( } else { record_success(&state.runtime.circuits, &circuit_key); } - if status == StatusCode::NOT_FOUND { + if status == StatusCode::NOT_FOUND && protocol != DataPlaneProtocol::Graphql { return Ok(( StatusCode::NOT_FOUND, Json(PolicyErrorBody { @@ -608,32 +816,52 @@ async fn execute_rest( status, decision.response_transform.as_ref(), ); - // GraphQL clients expect execution failures in a valid `errors` envelope, - // even when an upstream incorrectly reports that envelope with a 5xx status. - // Preserve transport failures and malformed/non-GraphQL responses as errors. - let status = if protocol == DataPlaneProtocol::Graphql - && status.is_server_error() - && serde_json::from_slice::(&bytes) - .ok() - .and_then(|body| body.get("errors").and_then(Value::as_array).cloned()) - .is_some_and(|errors| !errors.is_empty()) - { - StatusCode::OK + // The pinned GraphQL gateway always answers 200 with a GraphQL envelope: + // an upstream error status without `errors` is wrapped, and a body that is + // not JSON becomes a BAD_RESPONSE error. + let (status, bytes) = if protocol == DataPlaneProtocol::Graphql { + let envelope = match serde_json::from_slice::(&bytes) { + Ok(value) if status != StatusCode::OK && value.get("errors").is_none() => json!({ + "errors": [{ + "message": value + .get("message") + .and_then(Value::as_str) + .map(str::to_owned) + .unwrap_or_else(|| format!("HTTP {}", status.as_u16())), + "extensions": {"code": format!("HTTP_{}", status.as_u16())}, + }] + }), + Ok(value) => value, + Err(error) => json!({ + "errors": [{ + "message": format!("Invalid JSON from upstream: {error}"), + "extensions": {"code": "BAD_RESPONSE"}, + }] + }), + }; + ( + StatusCode::OK, + serde_json::to_vec(&envelope).unwrap_or_default(), + ) } else { - status + (status, bytes) }; let is_json = upstream_headers .get(header::CONTENT_TYPE) .and_then(|value| value.to_str().ok()) .is_some_and(|value| value.to_ascii_lowercase().contains("application/json")); + // A HEAD (or any bodiless) reply legitimately carries a JSON content type + // with no payload; only a non-empty body can be malformed. if protocol == DataPlaneProtocol::Rest && is_json + && parts.method != http::Method::HEAD + && !bytes.is_empty() && serde_json::from_slice::(&bytes).is_err() { return Ok(policy_error_response( StatusCode::INTERNAL_SERVER_ERROR, "GTW006", - "Invalid JSON response from upstream", + "Malformed JSON from upstream", )); } let upstream_content_type = upstream_headers @@ -646,10 +874,12 @@ async fn execute_rest( }) .to_owned(); let (body, content_type) = match protocol { - DataPlaneProtocol::Soap - | DataPlaneProtocol::Graphql - | DataPlaneProtocol::Grpc - | DataPlaneProtocol::GrpcWeb => (bytes.to_vec(), upstream_content_type), + // The pinned SOAP gateway always answers with application/xml. + DataPlaneProtocol::Soap => (bytes.to_vec(), "application/xml".to_owned()), + DataPlaneProtocol::Graphql => (bytes.to_vec(), "application/json".to_owned()), + DataPlaneProtocol::Grpc | DataPlaneProtocol::GrpcWeb => { + (bytes.to_vec(), upstream_content_type) + } DataPlaneProtocol::Rest if !is_json => ( serde_json::to_vec(&String::from_utf8_lossy(&bytes)) .unwrap_or_else(|_| b"null".to_vec()), @@ -743,7 +973,10 @@ async fn execute_crud( } } http::Method::POST => { - let mut value = parse_crud_body(&body)?; + let mut value = parse_crud_body(&body); + if !value.is_object() { + return Ok(crud_internal_error(&crud_post_non_object_message(&value))); + } if value.get("_id").is_none() { value["_id"] = Value::String(uuid::Uuid::new_v4().to_string()); } @@ -761,7 +994,36 @@ async fn execute_crud( "Resource ID required for update", )); }; - let value = parse_crud_body(&body)?; + let value = parse_crud_body(&body); + if !value.is_object() { + let has_schema = decision + .crud_schema + .as_ref() + .and_then(Value::as_object) + .is_some_and(|schema| !schema.is_empty()); + if has_schema { + return Ok(crud_internal_error(&format!( + "'{}' object has no attribute 'get'", + python_type_name(&value) + ))); + } + // Python checks existence first, then `$set`s the raw body. + let Some(existing) = storage.crud_find_one(collection, resource_id).await? + else { + return Ok(policy_error_response( + StatusCode::NOT_FOUND, + "CRUD404", + "Resource not found", + )); + }; + if python_truthy(&value) { + return Ok(crud_internal_error(&format!( + "'{}' object has no attribute 'items'", + python_type_name(&value) + ))); + } + return Ok(crud_success(StatusCode::OK, existing)); + } validate_crud_schema(decision.crud_schema.as_ref(), &value, true)?; storage .crud_update(collection, resource_id, &value) @@ -814,11 +1076,14 @@ async fn execute_crud( Ok(response) => Ok(response), Err(crate::storage::runtime::StorageError::InvalidDocument(error)) => { tracing::debug!(error = %error, "CRUD validation failed"); - Ok(policy_error_response( + Ok(( StatusCode::BAD_REQUEST, - "CRUD400", - "Validation failed", - )) + Json(serde_json::json!({ + "error_code": "CRUD400", + "error_message": "Validation failed", + })), + ) + .into_response()) } Err(error) => { tracing::error!(error = %error, "CRUD storage operation failed"); @@ -850,14 +1115,59 @@ fn crud_resource_id(path: &str) -> Option<&str> { (endpoint.len() > 1).then(|| *endpoint.last().expect("endpoint is non-empty")) } -fn parse_crud_body(body: &[u8]) -> Result { - let value: Value = serde_json::from_slice(body)?; - if !value.is_object() { - return Err(crate::storage::runtime::StorageError::InvalidDocument( - "CRUD request body must be a JSON object".to_owned(), - )); +fn parse_crud_body(body: &[u8]) -> Value { + // Python treats request.json() failures as an empty object for CRUD REST. + serde_json::from_slice(body).unwrap_or_else(|_| serde_json::json!({})) +} + +fn python_type_name(value: &Value) -> &'static str { + match value { + Value::Null => "NoneType", + Value::Bool(_) => "bool", + Value::Number(number) if number.is_f64() => "float", + Value::Number(_) => "int", + Value::String(_) => "str", + Value::Array(_) => "list", + Value::Object(_) => "dict", + } +} + +fn python_truthy(value: &Value) -> bool { + match value { + Value::Null => false, + Value::Bool(flag) => *flag, + Value::Number(number) => number.as_f64().is_none_or(|number| number != 0.0), + Value::String(text) => !text.is_empty(), + Value::Array(items) => !items.is_empty(), + Value::Object(map) => !map.is_empty(), + } +} + +/// The exception text Python's `CrudService.handle_rest` raises for a +/// non-object POST body (`'_id' not in body`, then `body['_id'] = ...`). +fn crud_post_non_object_message(value: &Value) -> String { + let has_id = match value { + Value::Array(items) => items.iter().any(|item| item.as_str() == Some("_id")), + Value::String(text) => text.contains("_id"), + _ => false, + }; + match value { + Value::Array(_) => "list indices must be integers or slices, not str".to_owned(), + Value::String(_) if has_id => "string indices must be integers, not 'str'".to_owned(), + Value::String(_) => "'str' object does not support item assignment".to_owned(), + other => format!( + "argument of type '{}' is not iterable", + python_type_name(other) + ), } - Ok(value) +} + +fn crud_internal_error(message: &str) -> Response { + policy_error_response( + StatusCode::INTERNAL_SERVER_ERROR, + "CRUD999", + &format!("Internal CRUD error: {message}"), + ) } pub(crate) fn validate_crud_schema( @@ -911,8 +1221,12 @@ fn validate_crud_fields( let expected = rules.get("type").and_then(Value::as_str); let valid_type = match expected { Some("string") => field_value.is_string(), - Some("number") => field_value.is_number(), - Some("integer") => field_value.as_i64().is_some() || field_value.as_u64().is_some(), + Some("number") => field_value.is_number() || field_value.is_boolean(), + Some("integer") => { + field_value.as_i64().is_some() + || field_value.as_u64().is_some() + || field_value.is_boolean() + } Some("boolean") => field_value.is_boolean(), Some("array") => field_value.is_array(), Some("object") => field_value.is_object(), @@ -931,19 +1245,61 @@ fn validate_crud_fields( .and_then(Value::as_u64) .is_some_and(|min| text.chars().count() < min as usize) { - errors.push(format!("Field '{path}' is shorter than min_length")); + errors.push(format!( + "Field '{path}' must be at least {} characters", + rules["min_length"] + )); } if rules .get("max_length") .and_then(Value::as_u64) .is_some_and(|max| text.chars().count() > max as usize) { - errors.push(format!("Field '{path}' is longer than max_length")); + errors.push(format!( + "Field '{path}' must be at most {} characters", + rules["max_length"] + )); + } + if let Some(pattern) = rules.get("pattern").and_then(Value::as_str) + && let Some(pattern) = + crate::validation::json::cached_pattern(&format!("^(?:{pattern})")) + && !pattern.is_match(text) + { + errors.push(format!( + "Field '{path}' does not match pattern {}", + rules["pattern"].as_str().unwrap_or_default() + )); + } + if let Some(allowed) = rules.get("enum").and_then(Value::as_array) + && !allowed.contains(field_value) + { + errors.push(format!( + "Field '{path}' must be one of: {}", + allowed + .iter() + .map(|value| value + .as_str() + .map_or_else(|| value.to_string(), str::to_owned)) + .collect::>() + .join(", ") + )); } } - if let Some(allowed) = rules.get("enum").and_then(Value::as_array) { - if !allowed.contains(field_value) { - errors.push(format!("Field '{path}' is not an allowed value")); + let numeric_value = field_value.as_f64().or_else(|| { + field_value + .as_bool() + .map(|value| if value { 1.0 } else { 0.0 }) + }); + if let Some(numeric_value) = numeric_value { + if let Some(minimum) = rules.get("min_value").and_then(Value::as_f64) + && numeric_value < minimum + { + errors.push(format!("Field '{path}' must be >= {}", rules["min_value"])); + } + if let Some(maximum) = rules.get("max_value").and_then(Value::as_f64) + && numeric_value > maximum + { + errors.push(format!("Field '{path}' must be <= {}", rules["max_value"])); } } if let (Some(properties), Some(object)) = ( @@ -1090,9 +1446,11 @@ fn validate_protocol_request_with_registry( } fn graphql_validation_schema(schema: &Value, query: &str) -> Value { - let Some(operation) = regex::Regex::new(r"(?:query|mutation)\s+(\w+)") - .ok() - .and_then(|regex| regex.captures(query)) + static OPERATION: std::sync::LazyLock = std::sync::LazyLock::new(|| { + regex::Regex::new(r"(?:query|mutation)\s+(\w+)").expect("static GraphQL operation regex") + }); + let Some(operation) = OPERATION + .captures(query) .and_then(|captures| captures.get(1)) .map(|value| value.as_str()) else { @@ -1105,13 +1463,17 @@ fn graphql_validation_schema(schema: &Value, query: &str) -> Value { let Some(mapping) = mapping else { return serde_json::json!({}); }; - let prefix = format!("{operation}."); + // Python: `field_path.startswith(operation)` then `field_path[len(operation) + 1:]`, + // which skips one character whether or not it is the `.` separator. Value::Object( mapping .iter() .filter_map(|(path, rules)| { - path.strip_prefix(&prefix) - .map(|path| (path.to_owned(), rules.clone())) + path.strip_prefix(operation).map(|rest| { + let mut rest = rest.chars(); + rest.next(); + (rest.as_str().to_owned(), rules.clone()) + }) }) .collect(), ) @@ -1181,9 +1543,18 @@ async fn retry_backoff(attempt: u32) { .map(|seconds| (seconds * 1000.0) as u64) .unwrap_or(2_000); let delay_ms = base_ms - .saturating_mul(1_u64 << attempt.saturating_sub(1).min(20)) + .saturating_mul(1_u64 << attempt.min(20)) .min(maximum_ms); - tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await; + let jitter_ms = if delay_ms == 0 { + 0 + } else { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .subsec_nanos() as u64 + % (delay_ms + 1) + }; + tokio::time::sleep(std::time::Duration::from_millis(jitter_ms)).await; } fn is_hop_by_hop(name: &HeaderName) -> bool { @@ -1445,7 +1816,7 @@ mod tests { let body: Value = serde_json::from_slice(&to_bytes(response.into_body(), 4096).await.unwrap()).unwrap(); assert_eq!(body["error_code"], "GTW006"); - assert_eq!(body["error_message"], "Invalid JSON response from upstream"); + assert_eq!(body["error_message"], "Malformed JSON from upstream"); server.abort(); } @@ -1792,6 +2163,19 @@ mod tests { ); } + #[test] + fn graphql_scope_uses_python_startswith_and_skips_one_character() { + let schema = json!({"validation_schema": { + "getUser.id": {"required": true}, + "getUserX/name": {"required": true}, + "getOther.id": {"required": true} + }}); + assert_eq!( + graphql_validation_schema(&schema, "query getUser($id: ID){ user }"), + json!({"id": {"required": true}, "/name": {"required": true}}) + ); + } + #[test] fn rejects_unsafe_soap_but_preserves_legacy_passthrough_without_a_schema() { let legacy = br#""#; @@ -1892,6 +2276,30 @@ mod tests { let wrong_type = json!({ "name": "Ada", "count": "two" }); assert!(validate_crud_schema(Some(&schema), &wrong_type, false).is_err()); + + let constraints = json!({ + "code": {"type": "string", "pattern": "[A-Z]{2}", "enum": ["AB", "CD"]}, + "score": {"type": "number", "min_value": 1, "max_value": 5}, + "integer": {"type": "integer"} + }); + assert!( + validate_crud_schema( + Some(&constraints), + &json!({"code": "AB", "score": 3, "integer": true}), + false + ) + .is_ok() + ); + let error = validate_crud_schema( + Some(&constraints), + &json!({"code": "zz", "score": 8, "integer": false}), + false, + ) + .unwrap_err() + .to_string(); + assert!(error.contains("does not match pattern")); + assert!(error.contains("must be one of: AB, CD")); + assert!(error.contains("must be <= 5")); assert_eq!( crud_resource_id("/api/rest/demo/v1/items/resource-1"), Some("resource-1") diff --git a/gateway-rs/src/routes/tls_admin.rs b/gateway-rs/src/routes/tls_admin.rs new file mode 100644 index 00000000..b6695824 --- /dev/null +++ b/gateway-rs/src/routes/tls_admin.rs @@ -0,0 +1,558 @@ +use axum::response::Response; +use http::{Method, StatusCode}; +use serde_json::{Value, json}; + +use crate::{ + config::DownstreamTlsMode, + routes::platform::json_response, + state::AppState, + tls::{profiles::TlsProfiles, secrets, server_config_from_pem_with_roots}, +}; + +fn reply(status: StatusCode, value: Value, request_id: &str) -> Response { + json_response(status, value, request_id) +} + +fn error(status: StatusCode, message: &str, request_id: &str) -> Response { + reply( + status, + json!({"error_code": "TLSA002", "error_message": message}), + request_id, + ) +} + +pub async fn dispatch( + state: &AppState, + path: &str, + method: &Method, + payload: Value, + request_id: &str, +) -> Response { + let Some(storage) = &state.storage else { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "TLS profile storage is unavailable", + request_id, + ); + }; + let _mutation_guard = if matches!(*method, Method::POST | Method::PUT | Method::DELETE) { + match storage.tls_mutation_lock().await { + Ok(guard) => Some(guard), + Err(_) => { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "TLS profile mutation lock is unavailable", + request_id, + ); + } + } + } else { + None + }; + let documents = match storage.find_many("tls_profiles", &json!({})).await { + Ok(documents) => documents, + Err(_) => { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "TLS profile storage is unavailable", + request_id, + ); + } + }; + if path == "/tls/profiles" && *method == Method::GET { + let mut items = Vec::new(); + let file = match TlsProfiles::load_file(state.config.tls_profiles_file.as_deref()) { + Ok(file) => file, + Err(_) => { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "File TLS profiles are unavailable", + request_id, + ); + } + }; + for id in file.client_cas.keys() { + items.push(json!({"id": id, "kind": "client_ca", "source": "file", "read_only": true})); + } + for id in file.upstreams.keys() { + items.push(json!({"id": id, "kind": "upstream", "source": "file", "read_only": true})); + } + for doc in &documents { + if doc.get("kind").and_then(Value::as_str) == Some("listener") { + continue; + } + items.push(public_metadata(doc)); + } + return reply(StatusCode::OK, json!({"profiles": items}), request_id); + } + if path == "/tls/listener" { + if *method == Method::GET { + let active = documents + .iter() + .find(|doc| doc.get("kind").and_then(Value::as_str) == Some("listener")); + let from_admin = state.tls_listener_from_admin(); + return reply( + StatusCode::OK, + json!({"source": if from_admin {"admin"} else {"file"}, "revision": if from_admin {active.and_then(|doc| doc.get("revision"))} else {None}, "has_override": active.is_some()}), + request_id, + ); + } + if *method == Method::DELETE { + let Some(previous) = documents + .iter() + .find(|doc| doc.get("kind").and_then(Value::as_str) == Some("listener")) + else { + return error( + StatusCode::NOT_FOUND, + "Listener override not found", + request_id, + ); + }; + if !matches!( + storage + .delete_one("tls_profiles", &json!({"id": "listener"})) + .await, + Ok(true) + ) { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Listener override could not be removed", + request_id, + ); + } + if state.reload_tls_from_storage().await.is_err() { + let _ = storage.insert_one("tls_profiles", previous.clone()).await; + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Mounted listener certificate could not be activated", + request_id, + ); + } + return reply( + StatusCode::OK, + json!({"message": "Mounted listener certificate activated"}), + request_id, + ); + } + if *method != Method::PUT { + return error( + StatusCode::METHOD_NOT_ALLOWED, + "Method not allowed", + request_id, + ); + } + if state.config.downstream_tls_mode != DownstreamTlsMode::Native { + return error( + StatusCode::BAD_REQUEST, + "Native TLS mode is required", + request_id, + ); + } + let (Some(cert), Some(key)) = ( + payload.get("cert_pem").and_then(Value::as_str), + payload.get("key_pem").and_then(Value::as_str), + ) else { + return error( + StatusCode::BAD_REQUEST, + "Certificate and key PEM are required", + request_id, + ); + }; + if cert.len() > 256 * 1024 || key.len() > 256 * 1024 { + return error( + StatusCode::BAD_REQUEST, + "TLS material is too large", + request_id, + ); + } + if server_config_from_pem_with_roots( + cert.as_bytes(), + key.as_bytes(), + Some(state.tls_snapshot().profiles.combined_client_roots()), + ) + .is_err() + { + return error( + StatusCode::BAD_REQUEST, + "Certificate and key are invalid", + request_id, + ); + } + let sealed_cert = match secrets::seal("listener", "cert_pem", cert.as_bytes()) { + Ok(value) => value, + Err(_) => { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "TLS secret encryption is not configured", + request_id, + ); + } + }; + let sealed_key = match secrets::seal("listener", "key_pem", key.as_bytes()) { + Ok(value) => value, + Err(_) => { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "TLS secret encryption is not configured", + request_id, + ); + } + }; + let previous = documents + .iter() + .find(|doc| doc.get("kind").and_then(Value::as_str) == Some("listener")); + let doc = json!({"id": "listener", "kind": "listener", "cert_pem": sealed_cert, "key_pem": sealed_key, "revision": previous.and_then(|doc| doc.get("revision")).and_then(Value::as_u64).unwrap_or(0) + 1}); + let result = if previous.is_some() { + storage + .update_one("tls_profiles", &json!({"id": "listener"}), &doc) + .await + .map(|result| result.is_some()) + } else { + storage.insert_one("tls_profiles", doc).await.map(|_| true) + }; + if !matches!(result, Ok(true)) { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Listener certificate could not be activated", + request_id, + ); + } + if state.reload_tls_from_storage().await.is_err() { + if let Some(previous) = previous { + let _ = storage + .update_one("tls_profiles", &json!({"id": "listener"}), previous) + .await; + } else { + let _ = storage + .delete_one("tls_profiles", &json!({"id": "listener"})) + .await; + } + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Listener certificate could not be activated", + request_id, + ); + } + return reply( + StatusCode::OK, + json!({"message": "Listener certificate activated"}), + request_id, + ); + } + if path == "/tls/profiles" && *method == Method::POST { + let Some(id) = payload.get("id").and_then(Value::as_str) else { + return error( + StatusCode::BAD_REQUEST, + "Profile ID is required", + request_id, + ); + }; + if documents + .iter() + .any(|doc| doc.get("id").and_then(Value::as_str) == Some(id)) + { + return error( + StatusCode::CONFLICT, + "Profile ID already exists", + request_id, + ); + } + let Some(doc) = seal_profile(&payload) else { + return error( + StatusCode::BAD_REQUEST, + "Invalid profile or TLS secret key", + request_id, + ); + }; + let mut candidate = documents.clone(); + candidate.push(doc.clone()); + if !valid_candidate(state, &candidate) { + return error( + StatusCode::BAD_REQUEST, + "Profile material is invalid or ID collides with a file profile", + request_id, + ); + } + if storage.insert_one("tls_profiles", doc).await.is_err() { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Profile could not be stored", + request_id, + ); + } + if state.reload_tls_from_storage().await.is_err() { + let _ = storage.delete_one("tls_profiles", &json!({"id": id})).await; + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Profile could not be activated", + request_id, + ); + } + return reply(StatusCode::CREATED, json!({"id": id}), request_id); + } + let Some(id) = path + .strip_prefix("/tls/profiles/") + .filter(|id| !id.is_empty() && !id.contains('/')) + else { + return error(StatusCode::NOT_FOUND, "TLS route not found", request_id); + }; + let Some(existing) = documents + .iter() + .find(|doc| doc.get("id").and_then(Value::as_str) == Some(id)) + else { + return error(StatusCode::NOT_FOUND, "Profile not found", request_id); + }; + if existing.get("kind").and_then(Value::as_str) == Some("listener") { + return error(StatusCode::NOT_FOUND, "Profile not found", request_id); + } + if *method == Method::GET { + return reply(StatusCode::OK, public_metadata(existing), request_id); + } + if *method == Method::DELETE { + if profile_referenced(state, id).await { + return error( + StatusCode::CONFLICT, + "Profile is referenced by an API or endpoint", + request_id, + ); + } + if !matches!( + storage.delete_one("tls_profiles", &json!({"id": id})).await, + Ok(true) + ) { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Profile could not be deleted", + request_id, + ); + } + if state.reload_tls_from_storage().await.is_err() { + let _ = storage.insert_one("tls_profiles", existing.clone()).await; + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Profile could not be deleted", + request_id, + ); + } + return reply( + StatusCode::OK, + json!({"message": "Profile deleted"}), + request_id, + ); + } + if *method == Method::PUT { + let mut submitted = payload; + submitted["id"] = json!(id); + let Some(mut replacement) = seal_profile(&submitted) else { + return error( + StatusCode::BAD_REQUEST, + "Invalid profile or TLS secret key", + request_id, + ); + }; + if replacement.get("kind") != existing.get("kind") { + return error( + StatusCode::BAD_REQUEST, + "Profile type cannot be changed", + request_id, + ); + } + replacement["revision"] = json!( + existing + .get("revision") + .and_then(Value::as_u64) + .unwrap_or(0) + + 1 + ); + let mut candidate = documents.clone(); + if let Some(target) = candidate + .iter_mut() + .find(|doc| doc.get("id").and_then(Value::as_str) == Some(id)) + { + *target = replacement.clone(); + } + if !valid_candidate(state, &candidate) { + return error( + StatusCode::BAD_REQUEST, + "Profile material is invalid", + request_id, + ); + } + if !matches!( + storage + .update_one("tls_profiles", &json!({"id": id}), &replacement) + .await, + Ok(Some(_)) + ) { + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Profile could not be activated", + request_id, + ); + } + if state.reload_tls_from_storage().await.is_err() { + let _ = storage + .update_one("tls_profiles", &json!({"id": id}), existing) + .await; + return error( + StatusCode::SERVICE_UNAVAILABLE, + "Profile could not be activated", + request_id, + ); + } + return reply(StatusCode::OK, json!({"id": id}), request_id); + } + error( + StatusCode::METHOD_NOT_ALLOWED, + "Method not allowed", + request_id, + ) +} + +fn seal_profile(payload: &Value) -> Option { + let id = payload.get("id")?.as_str()?; + let kind = payload.get("kind")?.as_str()?; + if !matches!(kind, "client_ca" | "upstream") { + return None; + } + if kind == "client_ca" + && (payload.get("cert_pem").and_then(Value::as_str).is_some() + || payload.get("key_pem").and_then(Value::as_str).is_some() + || payload.get("server_name").and_then(Value::as_str).is_some()) + { + return None; + } + let mut doc = json!({"id": id, "kind": kind, "source": "admin", "revision": 1}); + for field in ["ca_pem", "cert_pem", "key_pem"] { + if let Some(value) = payload.get(field).and_then(Value::as_str) { + if value.len() > 256 * 1024 { + return None; + } + doc[field] = json!(secrets::seal(id, field, value.as_bytes()).ok()?); + } else { + doc[field] = Value::Null; + } + } + if let Some(server_name) = payload.get("server_name").and_then(Value::as_str) { + doc["server_name"] = json!(server_name); + } else { + doc["server_name"] = Value::Null; + } + Some(doc) +} + +fn valid_candidate(state: &AppState, documents: &[Value]) -> bool { + let Ok(mut profiles) = TlsProfiles::load_file(state.config.tls_profiles_file.as_deref()) else { + return false; + }; + profiles.merge_admin_documents(documents).is_ok() +} + +fn public_metadata(doc: &Value) -> Value { + json!({"id": doc.get("id"), "kind": doc.get("kind"), "source": "admin", "revision": doc.get("revision"), "read_only": false}) +} + +async fn profile_referenced(state: &AppState, id: &str) -> bool { + let Some(storage) = &state.storage else { + return true; + }; + for (collection, prefix) in [("apis", "api"), ("endpoints", "endpoint")] { + let Ok(documents) = storage.find_many(collection, &json!({})).await else { + return true; + }; + for doc in documents { + if doc + .get(format!("{prefix}_client_tls_policy")) + .and_then(|policy| policy.get("ca_profile_id")) + .and_then(Value::as_str) + == Some(id) + || doc + .get(format!("{prefix}_upstream_tls_profile")) + .and_then(Value::as_str) + == Some(id) + || doc + .get(format!("{prefix}_upstream_tls_profiles")) + .and_then(Value::as_object) + .is_some_and(|map| map.values().any(|value| value.as_str() == Some(id))) + { + return true; + } + } + } + false +} + +#[cfg(test)] +mod tests { + use std::{ + process::{Command, Stdio}, + sync::Arc, + }; + + use super::*; + use crate::{config::Config, storage::runtime::SharedStorage}; + + #[tokio::test] + async fn invalid_stored_listener_uses_mounted_certificate_and_can_be_reset() { + let _ = tokio_rustls::rustls::crypto::ring::default_provider().install_default(); + let directory = + std::env::temp_dir().join(format!("doorman-tls-recovery-{}", uuid::Uuid::new_v4())); + std::fs::create_dir(&directory).unwrap(); + let cert = directory.join("listener.pem"); + let key = directory.join("listener.key"); + let generated = Command::new("openssl") + .args([ + "req", + "-x509", + "-newkey", + "rsa:2048", + "-nodes", + "-days", + "2", + "-subj", + "/CN=localhost", + "-keyout", + ]) + .arg(&key) + .arg("-out") + .arg(&cert) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .unwrap(); + assert!(generated.success()); + + let mut config = Config::for_test(String::new()); + config.downstream_tls_mode = DownstreamTlsMode::Native; + config.downstream_tls_cert_file = Some(cert); + config.downstream_tls_key_file = Some(key); + let storage = Arc::new( + SharedStorage::connect(&config.shared_storage) + .await + .unwrap(), + ); + storage.insert_one("tls_profiles", json!({"id": "listener", "kind": "listener", "cert_pem": "invalid", "key_pem": "invalid"})).await.unwrap(); + let mut state = AppState::new(config).unwrap(); + state.storage = Some(storage.clone()); + + assert!(state.reload_tls_from_storage().await.unwrap().is_some()); + assert!(!state.tls_listener_from_admin()); + let response = dispatch( + &state, + "/tls/listener", + &Method::DELETE, + Value::Null, + "test", + ) + .await; + assert_eq!(response.status(), StatusCode::OK); + assert!( + storage + .find_one("tls_profiles", &json!({"id": "listener"})) + .await + .unwrap() + .is_none() + ); + std::fs::remove_dir_all(directory).unwrap(); + } +} diff --git a/gateway-rs/src/state.rs b/gateway-rs/src/state.rs index 1c6b8a26..a76c1da2 100644 --- a/gateway-rs/src/state.rs +++ b/gateway-rs/src/state.rs @@ -9,8 +9,13 @@ use std::{ use reqwest::{Client, redirect::Policy}; use serde_json::Value; +use sha2::{Digest, Sha256}; use thiserror::Error; use tokio::sync::watch; +use tokio_rustls::rustls::ServerConfig; +use tonic::transport::{ + Certificate as GrpcCertificate, Channel, ClientTlsConfig, Endpoint, Identity as GrpcIdentity, +}; use crate::{ config::Config, @@ -20,6 +25,12 @@ use crate::{ models::PolicyDocuments, runtime::{SharedStorage, StorageError}, }, + tls::{ + TlsReloadHandle, + profiles::{TlsProfileError, TlsProfiles}, + secrets::TlsSecretError, + server_config_from_pem_with_roots, + }, validation::json::ValidatorRegistry, }; @@ -29,12 +40,27 @@ pub enum StateError { Http(#[from] reqwest::Error), #[error("required shared storage is unavailable: {0}")] Storage(#[from] StorageError), + #[error("TLS profiles could not be loaded: {0}")] + TlsProfiles(#[from] TlsProfileError), + #[error("TLS material could not be read: {0}")] + TlsIo(#[from] std::io::Error), + #[error("TLS secret could not be decrypted: {0}")] + TlsSecret(#[from] TlsSecretError), } +/// Cache key for pooled upstream gRPC channels. +type GrpcChannelKey = (u64, String, String, u64); + #[derive(Clone)] pub struct AppState { pub config: Config, pub proxy_client: Client, + tls_snapshot: Arc>>, + tls_listener_handle: Arc>>, + tls_listener_hash: Arc>>, + tls_listener_from_admin: Arc, + tls_reload_lock: Arc>, + grpc_channels: Arc>>, pub policy_documents: Option>>, pub storage: Option>, pub runtime: Arc, @@ -42,6 +68,13 @@ pub struct AppState { pub validators: Arc, } +pub struct TlsSnapshot { + pub revision: u64, + pub profiles: Arc, + fingerprint: [u8; 32], + http_clients: HashMap, +} + pub struct GatewayRuntime { pub started_at: Instant, pub active_requests: AtomicU64, @@ -59,6 +92,9 @@ pub struct GatewayRuntime { pub revocation_purge_healthy: AtomicBool, pub activity_log_healthy: AtomicBool, pub security_audit_log_healthy: AtomicBool, + pub tls_reload_success_total: AtomicU64, + pub tls_reload_failure_total: AtomicU64, + pub tls_certificate_expiries: Mutex>, memory_autosave_updates: watch::Sender, } @@ -149,6 +185,9 @@ impl Default for GatewayRuntime { revocation_purge_healthy: AtomicBool::new(true), activity_log_healthy: AtomicBool::new(true), security_audit_log_healthy: AtomicBool::new(true), + tls_reload_success_total: AtomicU64::new(0), + tls_reload_failure_total: AtomicU64::new(0), + tls_certificate_expiries: Mutex::new(BTreeMap::new()), memory_autosave_updates, } } @@ -164,18 +203,62 @@ impl GatewayRuntime { } } +/// Upstream client settings from the pinned httpx pool environment +/// (`GatewayService._build_limits`/`get_http_client`): HTTP_MAX_KEEPALIVE +/// (default 50) bounds idle pooled connections, HTTP_KEEPALIVE_EXPIRY +/// (default 30s) expires them, ENABLE_HTTPX_CLIENT_CACHE=false disables +/// connection reuse, and HTTP/2 is only negotiated when HTTP_ENABLE_HTTP2 is +/// exactly `true` (httpx defaults to HTTP/1.1). Unparseable values fall back to +/// the defaults like Python's try/except parsing. +fn upstream_client_builder(connect_timeout: std::time::Duration) -> reqwest::ClientBuilder { + fn env_or(name: &str, default: T) -> T { + std::env::var(name) + .ok() + .and_then(|value| value.trim().parse().ok()) + .unwrap_or(default) + } + let pooled = std::env::var("ENABLE_HTTPX_CLIENT_CACHE") + .map(|value| value.to_lowercase() != "false") + .unwrap_or(true); + let max_keepalive = if pooled { + env_or("HTTP_MAX_KEEPALIVE", 50usize) + } else { + 0 + }; + let keepalive_expiry: f64 = env_or("HTTP_KEEPALIVE_EXPIRY", 30.0); + let mut builder = Client::builder() + .user_agent("doorman-gateway/2.0.0 (compatible; httpx/0.27)") + .connect_timeout(connect_timeout) + .redirect(Policy::none()) + .pool_max_idle_per_host(max_keepalive) + .tcp_keepalive(std::time::Duration::from_secs(60)); + if keepalive_expiry.is_finite() && keepalive_expiry > 0.0 { + builder = builder.pool_idle_timeout(std::time::Duration::from_secs_f64(keepalive_expiry)); + } + if std::env::var("HTTP_ENABLE_HTTP2").map(|value| value.to_lowercase()) != Ok("true".to_owned()) + { + builder = builder.http1_only(); + } + builder +} + impl AppState { pub fn new(config: Config) -> Result { - let proxy_client = Client::builder() - .user_agent("doorman-gateway/2.0.0 (compatible; httpx/0.27)") - .connect_timeout(config.connect_timeout) - .redirect(Policy::none()) - .pool_max_idle_per_host(32) - .tcp_keepalive(std::time::Duration::from_secs(60)) - .build()?; + let proxy_client = upstream_client_builder(config.connect_timeout).build()?; Ok(Self { config, proxy_client, + tls_snapshot: Arc::new(std::sync::RwLock::new(Arc::new(TlsSnapshot { + revision: 0, + profiles: Arc::new(TlsProfiles::default()), + fingerprint: [0; 32], + http_clients: HashMap::new(), + }))), + tls_listener_handle: Arc::new(Mutex::new(None)), + tls_listener_hash: Arc::new(Mutex::new(None)), + tls_listener_from_admin: Arc::new(AtomicBool::new(false)), + tls_reload_lock: Arc::new(tokio::sync::Mutex::new(())), + grpc_channels: Arc::new(Mutex::new(HashMap::new())), policy_documents: None, storage: None, runtime: Arc::new(GatewayRuntime::default()), @@ -186,12 +269,272 @@ impl AppState { pub async fn from_config(config: Config) -> Result { let mut state = Self::new(config)?; + state.publish_tls_profiles(TlsProfiles::load_file( + state.config.tls_profiles_file.as_deref(), + )?)?; let storage = SharedStorage::connect(&state.config.shared_storage).await?; storage.initialize_core().await?; state.storage = Some(Arc::new(storage)); Ok(state) } + pub fn tls_snapshot(&self) -> Arc { + self.tls_snapshot + .read() + .expect("TLS snapshot lock poisoned") + .clone() + } + + pub fn publish_tls_profiles(&self, profiles: TlsProfiles) -> Result<(), StateError> { + let fingerprint = profiles.fingerprint(); + if self.tls_snapshot().fingerprint == fingerprint { + return Ok(()); + } + let expiries = profiles.certificate_expiries(); + let mut http_clients = HashMap::new(); + for (id, profile) in &profiles.upstreams { + let mut builder = upstream_client_builder(self.config.connect_timeout); + if !profile.ca_pem.is_empty() { + for cert in reqwest::Certificate::from_pem_bundle(&profile.ca_pem)? { + builder = builder.add_root_certificate(cert); + } + } + if let (Some(cert), Some(key)) = (&profile.client_cert_pem, &profile.client_key_pem) { + let mut identity = cert.clone(); + identity.push(b'\n'); + identity.extend_from_slice(key); + builder = builder.identity(reqwest::Identity::from_pem(&identity)?); + } + http_clients.insert(id.clone(), builder.build()?); + } + let revision = self.tls_snapshot().revision.saturating_add(1); + let snapshot = Arc::new(TlsSnapshot { + revision, + profiles: Arc::new(profiles), + fingerprint, + http_clients, + }); + *self + .tls_snapshot + .write() + .expect("TLS snapshot lock poisoned") = snapshot; + self.grpc_channels + .lock() + .expect("gRPC cache lock poisoned") + .clear(); + let mut active_expiries = self + .runtime + .tls_certificate_expiries + .lock() + .expect("TLS expiry lock poisoned"); + active_expiries.retain(|name, _| name == "listener"); + active_expiries.extend(expiries); + self.runtime + .tls_reload_success_total + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + Ok(()) + } + + pub fn set_tls_listener_handle(&self, handle: TlsReloadHandle) { + *self + .tls_listener_handle + .lock() + .expect("TLS listener handle lock poisoned") = Some(handle); + } + + pub fn tls_listener_from_admin(&self) -> bool { + self.tls_listener_from_admin + .load(std::sync::atomic::Ordering::Relaxed) + } + + pub async fn reload_tls_from_storage(&self) -> Result, StateError> { + let result = self.reload_tls_from_storage_inner().await; + if result.is_err() { + self.runtime + .tls_reload_failure_total + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + result + } + + async fn reload_tls_from_storage_inner(&self) -> Result, StateError> { + let _guard = self.tls_reload_lock.lock().await; + let mut profiles = TlsProfiles::load_file(self.config.tls_profiles_file.as_deref())?; + let documents = match &self.storage { + Some(storage) => { + storage + .find_many("tls_profiles", &serde_json::json!({})) + .await? + } + None => Vec::new(), + }; + profiles.merge_admin_documents(&documents)?; + let listener = if self.config.downstream_tls_mode + == crate::config::DownstreamTlsMode::Native + { + let admin_listener = documents + .iter() + .find(|document| document.get("kind").and_then(Value::as_str) == Some("listener")); + let roots = profiles.combined_client_roots(); + let admin_material = admin_listener.map(|document| -> Result<_, StateError> { + let id = document + .get("id") + .and_then(Value::as_str) + .unwrap_or("listener"); + let cert = document.get("cert_pem").and_then(Value::as_str).ok_or( + TlsProfileError::Invalid("listener certificate missing".to_owned()), + )?; + let key = document + .get("key_pem") + .and_then(Value::as_str) + .ok_or(TlsProfileError::Invalid("listener key missing".to_owned()))?; + let cert = crate::tls::secrets::open(id, "cert_pem", cert)?; + let key = crate::tls::secrets::open(id, "key_pem", key)?; + let config = server_config_from_pem_with_roots(&cert, &key, Some(roots.clone()))?; + Ok((cert, key, config, true)) + }); + let (cert, key, config, from_admin) = match admin_material { + Some(Ok(material)) => material, + Some(Err(error)) => { + tracing::warn!(%error, "admin TLS listener certificate rejected; using mounted certificate"); + let cert = std::fs::read( + self.config + .downstream_tls_cert_file + .as_deref() + .expect("validated TLS certificate path"), + )?; + let key = std::fs::read( + self.config + .downstream_tls_key_file + .as_deref() + .expect("validated TLS key path"), + )?; + let config = server_config_from_pem_with_roots(&cert, &key, Some(roots))?; + (cert, key, config, false) + } + None => { + let cert = std::fs::read( + self.config + .downstream_tls_cert_file + .as_deref() + .expect("validated TLS certificate path"), + )?; + let key = std::fs::read( + self.config + .downstream_tls_key_file + .as_deref() + .expect("validated TLS key path"), + )?; + let config = server_config_from_pem_with_roots(&cert, &key, Some(roots))?; + (cert, key, config, false) + } + }; + let mut hasher = Sha256::new(); + hasher.update(&cert); + hasher.update(&key); + hasher.update(profiles.fingerprint()); + let hash: [u8; 32] = hasher.finalize().into(); + let expiry = crate::tls::certificate_expiry_timestamp(&cert)?; + Some((config, hash, expiry, from_admin)) + } else { + None + }; + self.publish_tls_profiles(profiles)?; + if let Some((config, hash, expiry, from_admin)) = &listener { + let mut active = self + .tls_listener_hash + .lock() + .expect("TLS listener hash lock poisoned"); + if active.as_ref() != Some(hash) { + if let Some(handle) = self + .tls_listener_handle + .lock() + .expect("TLS listener handle lock poisoned") + .as_ref() + { + handle.publish(config.clone()); + } + *active = Some(*hash); + self.runtime + .tls_reload_success_total + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + self.runtime + .tls_certificate_expiries + .lock() + .expect("TLS expiry lock poisoned") + .insert("listener".to_owned(), *expiry); + self.tls_listener_from_admin + .store(*from_admin, std::sync::atomic::Ordering::Relaxed); + } + Ok(listener.map(|(config, _, _, _)| config)) + } + + pub fn proxy_client_for(&self, profile_id: Option<&str>) -> Option { + match profile_id { + Some(id) => self.tls_snapshot().http_clients.get(id).cloned(), + None => Some(self.proxy_client.clone()), + } + } + + pub async fn grpc_channel( + &self, + endpoint: &str, + profile_id: Option<&str>, + timeout_ms: u64, + ) -> Result { + let snapshot = self.tls_snapshot(); + let key = ( + snapshot.revision, + endpoint.to_owned(), + profile_id.unwrap_or_default().to_owned(), + timeout_ms, + ); + if let Some(channel) = self + .grpc_channels + .lock() + .map_err(|_| "gRPC channel cache unavailable")? + .get(&key) + .cloned() + { + return Ok(channel); + } + let mut target = Endpoint::from_shared(endpoint.to_owned()) + .map_err(|error| error.to_string())? + .connect_timeout(std::time::Duration::from_millis(timeout_ms.max(1))) + .timeout(std::time::Duration::from_millis(timeout_ms.max(1))); + if let Some(id) = profile_id { + let profile = snapshot + .profiles + .upstreams + .get(id) + .ok_or("gRPC TLS profile is unavailable")?; + let mut tls = ClientTlsConfig::new(); + if !profile.ca_pem.is_empty() { + tls = tls.ca_certificate(GrpcCertificate::from_pem(profile.ca_pem.clone())); + } + if let (Some(cert), Some(key)) = (&profile.client_cert_pem, &profile.client_key_pem) { + tls = tls.identity(GrpcIdentity::from_pem(cert.clone(), key.clone())); + } + if let Some(server_name) = &profile.server_name { + tls = tls.domain_name(server_name.clone()); + } + target = target.tls_config(tls).map_err(|error| error.to_string())?; + } + let channel = target.connect().await.map_err(|error| error.to_string())?; + let mut cache = self + .grpc_channels + .lock() + .map_err(|_| "gRPC channel cache unavailable")?; + if cache.len() >= 128 { + if let Some(oldest) = cache.keys().next().cloned() { + cache.remove(&oldest); + } + } + cache.insert(key, channel.clone()); + Ok(channel) + } + pub fn with_policy_documents(mut self, documents: PolicyDocuments) -> Self { self.policy_documents = Some(Arc::new(Mutex::new(documents))); self diff --git a/gateway-rs/src/storage/cache.rs b/gateway-rs/src/storage/cache.rs index 6b5f8238..5f993bc5 100644 --- a/gateway-rs/src/storage/cache.rs +++ b/gateway-rs/src/storage/cache.rs @@ -113,6 +113,10 @@ struct CounterEntry { impl WindowCounter { pub fn incr(&self, key: &str, ttl_seconds: u64, now_seconds: u64) -> u64 { + self.incr_by(key, 1, ttl_seconds, now_seconds) + } + + pub fn incr_by(&self, key: &str, amount: u64, ttl_seconds: u64, now_seconds: u64) -> u64 { let mut inner = self.inner.lock().expect("counter mutex poisoned"); let entry = inner.entry(key.to_owned()).or_insert(CounterEntry { count: 0, @@ -122,7 +126,7 @@ impl WindowCounter { entry.count = 0; entry.expires_at = now_seconds + ttl_seconds; } - entry.count += 1; + entry.count = entry.count.saturating_add(amount); entry.count } diff --git a/gateway-rs/src/storage/field_encryption.rs b/gateway-rs/src/storage/field_encryption.rs new file mode 100644 index 00000000..72f5370e --- /dev/null +++ b/gateway-rs/src/storage/field_encryption.rs @@ -0,0 +1,48 @@ +use std::env; + +use base64::{Engine as _, engine::general_purpose::URL_SAFE}; +use fernet::Fernet; +use sha2::{Digest, Sha256}; + +fn cipher() -> Option { + let key = env::var("TOKEN_ENCRYPTION_KEY") + .ok() + .filter(|value| !value.is_empty()) + .or_else(|| { + env::var("MEM_ENCRYPTION_KEY") + .ok() + .filter(|value| !value.is_empty()) + })?; + Fernet::new(&key).or_else(|| { + let encoded = URL_SAFE.encode(Sha256::digest(key.as_bytes())); + Fernet::new(&encoded) + }) +} + +pub fn encrypt_value(value: Option<&str>) -> Option { + let value = value?; + Some(match cipher() { + Some(cipher) => format!("enc:{}", cipher.encrypt(value.as_bytes())), + None => value.to_owned(), + }) +} + +pub fn decrypt_value(value: Option<&str>) -> Option { + let value = value?; + let Some(token) = value.strip_prefix("enc:") else { + return Some(value.to_owned()); + }; + let cipher = cipher()?; + String::from_utf8(cipher.decrypt(token).ok()?).ok() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn plaintext_values_pass_through_without_an_encryption_marker() { + assert_eq!(decrypt_value(Some("plain")), Some("plain".to_owned())); + assert_eq!(decrypt_value(None), None); + } +} diff --git a/gateway-rs/src/storage/memory.rs b/gateway-rs/src/storage/memory.rs index 1baac34d..a7430958 100644 --- a/gateway-rs/src/storage/memory.rs +++ b/gateway-rs/src/storage/memory.rs @@ -46,6 +46,7 @@ impl MemoryStorage { "user_tier_assignments", "rate_limit_rules", "config_snapshots", + "tls_profiles", ] { collections.insert(name.to_owned(), Vec::new()); } diff --git a/gateway-rs/src/storage/mod.rs b/gateway-rs/src/storage/mod.rs index edf71786..5bb19df2 100644 --- a/gateway-rs/src/storage/mod.rs +++ b/gateway-rs/src/storage/mod.rs @@ -1,5 +1,6 @@ pub mod cache; pub mod configuration; +pub mod field_encryption; pub mod memory; pub mod models; pub mod mongo; diff --git a/gateway-rs/src/storage/runtime.rs b/gateway-rs/src/storage/runtime.rs index 65a01bf4..4356d011 100644 --- a/gateway-rs/src/storage/runtime.rs +++ b/gateway-rs/src/storage/runtime.rs @@ -25,6 +25,29 @@ pub struct SharedStorage { memory: Option, policy_cache: Arc>>, policy_cache_ttl: Duration, + tls_mutation_mutex: Arc>, + tls_mutation_key: String, +} + +pub struct TlsMutationGuard { + _local: tokio::sync::OwnedMutexGuard<()>, + redis: Option<(ConnectionManager, String, String)>, + renewal: Option>, +} + +impl Drop for TlsMutationGuard { + fn drop(&mut self) { + if let Some(renewal) = self.renewal.take() { + renewal.abort(); + } + if let Some((mut redis, key, token)) = self.redis.take() { + tokio::spawn(async move { + let _: redis::RedisResult = redis::cmd("EVAL") + .arg("if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end") + .arg(1).arg(key).arg(token).query_async(&mut redis).await; + }); + } + } } #[derive(Clone)] @@ -82,14 +105,29 @@ impl SharedStorage { memory: Some(MemoryStorage::new()), policy_cache: Arc::new(tokio::sync::RwLock::new(None)), policy_cache_ttl: Duration::from_secs(config.policy_cache_ttl_seconds), + tls_mutation_mutex: Arc::new(tokio::sync::Mutex::new(())), + tls_mutation_key: String::new(), }); } - let client = Client::with_uri_str(config.mongo_uri()).await?; + // Bound server selection so a MongoDB outage fails requests quickly + // (fail closed) instead of stalling them for the 30s driver default. + let mut mongo_options = mongodb::options::ClientOptions::parse(config.mongo_uri()).await?; + mongo_options.server_selection_timeout = Some(Duration::from_secs(5)); + mongo_options.connect_timeout = Some(Duration::from_secs(5)); + let client = Client::with_options(mongo_options)?; let mongo = client.database(&config.mongo_database); mongo.run_command(doc! { "ping": 1 }).await?; + // Without timeouts a Redis outage would hang every request, and an + // unbounded backoff would delay recovery after Redis returns. let redis_client = redis::Client::open(config.redis_url())?; - let mut redis = ConnectionManager::new(redis_client).await?; + let redis_config = redis::aio::ConnectionManagerConfig::new() + .set_connection_timeout(Duration::from_secs(1)) + .set_response_timeout(Duration::from_secs(2)) + .set_number_of_retries(1) + .set_factor(100) + .set_max_delay(500); + let mut redis = ConnectionManager::new_with_config(redis_client, redis_config).await?; let _: String = redis::cmd("PING").query_async(&mut redis).await?; Self::ensure_indexes(&mongo).await?; Ok(Self { @@ -98,16 +136,71 @@ impl SharedStorage { memory: None, policy_cache: Arc::new(tokio::sync::RwLock::new(None)), policy_cache_ttl: Duration::from_secs(config.policy_cache_ttl_seconds), + tls_mutation_mutex: Arc::new(tokio::sync::Mutex::new(())), + tls_mutation_key: format!("doorman:tls-mutation:{}", config.mongo_database), + }) + } + + pub async fn tls_mutation_lock(&self) -> Result { + let local = self.tls_mutation_mutex.clone().lock_owned().await; + let Some(mut redis) = self.redis.clone() else { + return Ok(TlsMutationGuard { + _local: local, + redis: None, + renewal: None, + }); + }; + let key = self.tls_mutation_key.clone(); + let token = uuid::Uuid::new_v4().to_string(); + let deadline = Instant::now() + Duration::from_secs(30); + loop { + let acquired: Option = redis::cmd("SET") + .arg(&key) + .arg(&token) + .arg("NX") + .arg("PX") + .arg(30_000) + .query_async(&mut redis) + .await?; + if acquired.is_some() { + break; + } + if Instant::now() >= deadline { + return Err(StorageError::InvalidDocument( + "TLS mutation lock timed out".to_owned(), + )); + } + tokio::time::sleep(Duration::from_millis(50)).await; + } + let mut renewal_redis = redis.clone(); + let renewal_key = key.clone(); + let renewal_token = token.clone(); + let renewal = tokio::spawn(async move { + loop { + tokio::time::sleep(Duration::from_secs(10)).await; + let renewed: redis::RedisResult = redis::cmd("EVAL") + .arg("if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], 30000) else return 0 end") + .arg(1).arg(&renewal_key).arg(&renewal_token) + .query_async(&mut renewal_redis).await; + if !matches!(renewed, Ok(1)) { + break; + } + } + }); + Ok(TlsMutationGuard { + _local: local, + redis: Some((redis, key, token)), + renewal: Some(renewal), }) } async fn ensure_indexes(database: &Database) -> Result<(), StorageError> { let indexes = [ - ("users", doc! {"username": 1}), - ("users", doc! {"email": 1}), - ("roles", doc! {"role_name": 1}), - ("groups", doc! {"group_name": 1}), - ("apis", doc! {"api_name": 1, "api_version": 1}), + ("users", doc! {"username": 1}, true), + ("users", doc! {"email": 1}, true), + ("roles", doc! {"role_name": 1}, true), + ("groups", doc! {"group_name": 1}, true), + ("apis", doc! {"api_name": 1, "api_version": 1}, false), ( "endpoints", doc! { @@ -116,28 +209,74 @@ impl SharedStorage { "endpoint_method": 1, "endpoint_uri": 1 }, + true, + ), + ("subscriptions", doc! {"username": 1}, true), + ("routings", doc! {"client_key": 1}, true), + ("credit_defs", doc! {"api_credit_group": 1}, true), + ("user_credits", doc! {"username": 1}, true), + ("endpoint_validations", doc! {"endpoint_id": 1}, true), + ("tiers", doc! {"tier_id": 1}, true), + ("tiers", doc! {"name": 1}, false), + ("user_tier_assignments", doc! {"user_id": 1}, true), + ("user_tier_assignments", doc! {"tier_id": 1}, false), + ("vault_entries", doc! {"username": 1, "key_name": 1}, true), + ("vault_entries", doc! {"username": 1}, false), + ("config_snapshots", doc! {"snapshot_id": 1}, true), + ("tls_profiles", doc! {"id": 1}, true), + ( + "revocations", + doc! {"type": 1, "username": 1, "jti": 1}, + true, ), - ("subscriptions", doc! {"username": 1}), - ("routings", doc! {"client_key": 1}), - ("credit_defs", doc! {"api_credit_group": 1}), - ("user_credits", doc! {"username": 1}), - ("tiers", doc! {"tier_name": 1}), - ("user_tier_assignments", doc! {"user_id": 1}), - ("vault_entries", doc! {"username": 1, "key_name": 1}), - ("config_snapshots", doc! {"snapshot_id": 1}), - ("revocations", doc! {"type": 1, "username": 1, "jti": 1}), ]; - for (collection, keys) in indexes { + for (collection, keys, unique) in indexes { database .collection::(collection) .create_index( IndexModel::builder() .keys(keys) - .options(IndexOptions::builder().unique(true).build()) + .options(IndexOptions::builder().unique(unique).build()) .build(), ) .await?; } + // api_id is unique only where present: a proto uploaded before its API + // exists is stored as an API record without an id, and several of + // those must not collide on a null key. Earlier releases created a + // plain unique index, which MongoDB cannot alter in place. + let apis = database.collection::("apis"); + let legacy = apis + .list_index_names() + .await? + .into_iter() + .any(|name| name == "api_id_1"); + if legacy { + // Nodes starting together may race to drop it; "index not found" + // (code 27) means another node already migrated. + if let Err(error) = apis.drop_index("api_id_1").await { + let not_found = matches!( + error.kind.as_ref(), + mongodb::error::ErrorKind::Command(command) if command.code == 27 + ); + if !not_found { + return Err(error.into()); + } + } + } + apis.create_index( + IndexModel::builder() + .keys(doc! {"api_id": 1}) + .options( + IndexOptions::builder() + .name("api_id_present_1".to_owned()) + .unique(true) + .partial_filter_expression(doc! {"api_id": {"$type": "string"}}) + .build(), + ) + .build(), + ) + .await?; Ok(()) } pub async fn initialize_core(&self) -> Result<(), StorageError> { @@ -175,30 +314,74 @@ impl SharedStorage { .await?; } } - if self + let existing_admin = self .find_one("users", &serde_json::json!({"username": "admin"})) - .await? - .is_none() - { - let password = std::env::var("DOORMAN_ADMIN_PASSWORD").map_err(|_| { - StorageError::InvalidDocument( - "DOORMAN_ADMIN_PASSWORD is required for admin initialization".to_owned(), - ) - })?; + .await?; + let admin_credentials = || -> Result<(String, String), StorageError> { + let password = std::env::var("DOORMAN_ADMIN_PASSWORD") + .ok() + .filter(|value| !value.is_empty()) + .ok_or_else(|| { + StorageError::InvalidDocument( + "DOORMAN_ADMIN_PASSWORD is required for admin initialization".to_owned(), + ) + })?; let password = bcrypt::hash(password, bcrypt::DEFAULT_COST).map_err(|error| { StorageError::InvalidDocument(format!("failed to hash admin password: {error}")) })?; - self.insert_one("users", serde_json::json!({ - "username": "admin", - "email": std::env::var("DOORMAN_ADMIN_EMAIL").unwrap_or_else(|_| "admin@doorman.dev".to_owned()), - "password": password, - "role": "admin", "groups": ["ALL", "admin"], "ui_access": true, - "rate_limit_duration": 1, "rate_limit_duration_type": "second", - "throttle_duration": 1, "throttle_duration_type": "second", - "throttle_wait_duration": 0, "throttle_wait_duration_type": "second", - "throttle_queue_limit": 1, "throttle_enabled": null, - "custom_attributes": {"custom_key": "custom_value"}, "active": true - })).await?; + let email = std::env::var("DOORMAN_ADMIN_EMAIL") + .ok() + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| "admin@doorman.dev".to_owned()); + Ok((email, password)) + }; + match existing_admin { + None => { + let (email, password) = admin_credentials()?; + self.insert_one( + "users", + serde_json::json!({ + "username": "admin", "email": email, "password": password, + "role": "admin", "groups": ["ALL", "admin"], "ui_access": true, + "rate_limit_duration": 1, "rate_limit_duration_type": "second", + "throttle_duration": 1, "throttle_duration_type": "second", + "throttle_wait_duration": 0, "throttle_wait_duration_type": "second", + "throttle_queue_limit": 1, "throttle_enabled": null, + "custom_attributes": {"custom_key": "custom_value"}, "active": true + }), + ) + .await?; + } + Some(admin) => { + // Mongo-mode repair: restore only missing bootstrap fields. + let mut repairs = serde_json::Map::new(); + if admin.get("ui_access") != Some(&Value::Bool(true)) { + repairs.insert("ui_access".to_owned(), Value::Bool(true)); + } + let missing = |field: &str| { + admin + .get(field) + .is_none_or(|v| v.is_null() || v.as_str() == Some("")) + }; + if missing("email") { + let email = std::env::var("DOORMAN_ADMIN_EMAIL") + .ok() + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| "admin@doorman.dev".to_owned()); + repairs.insert("email".to_owned(), Value::String(email)); + } + if missing("password") { + repairs.insert("password".to_owned(), Value::String(admin_credentials()?.1)); + } + if !repairs.is_empty() { + self.update_one( + "users", + &serde_json::json!({"username": "admin"}), + &Value::Object(repairs), + ) + .await?; + } + } } Ok(()) } @@ -229,6 +412,33 @@ impl SharedStorage { Ok(()) } + /// Python's InMemoryCollection raises while the chaos Mongo outage flag is set. + fn chaos_mongo_guard(&self) -> Result<(), StorageError> { + if self.memory.is_some() { + Self::chaos_guard("mongo")?; + } + Ok(()) + } + + fn chaos_guard(backend: &str) -> Result<(), StorageError> { + use crate::middleware::chaos::{ + CHAOS_ERROR_BUDGET_BURN, CHAOS_MONGO_OUTAGE, CHAOS_REDIS_OUTAGE, + }; + use std::sync::atomic::Ordering; + let flag = if backend == "redis" { + &CHAOS_REDIS_OUTAGE + } else { + &CHAOS_MONGO_OUTAGE + }; + if flag.load(Ordering::Relaxed) { + CHAOS_ERROR_BUDGET_BURN.fetch_add(1, Ordering::Relaxed); + return Err(StorageError::InvalidDocument(format!( + "chaos: simulated {backend} outage" + ))); + } + Ok(()) + } + pub fn is_memory(&self) -> bool { self.memory.is_some() } @@ -239,6 +449,7 @@ impl SharedStorage { value: Value, ttl_seconds: u64, ) -> Result<(), StorageError> { + Self::chaos_guard("redis")?; if let Some(memory) = &self.memory { memory.set_value(key, value, ttl_seconds).await; return Ok(()); @@ -250,6 +461,7 @@ impl SharedStorage { } pub async fn get_ephemeral(&self, key: &str) -> Result, StorageError> { + Self::chaos_guard("redis")?; if let Some(memory) = &self.memory { return Ok(memory.get_value(key).await); } @@ -620,6 +832,7 @@ impl SharedStorage { collection: &str, filter: &Value, ) -> Result, StorageError> { + self.chaos_mongo_guard()?; if let Some(memory) = &self.memory { let collections = memory.collections.read().await; return Ok(collections @@ -648,6 +861,7 @@ impl SharedStorage { collection: &str, filter: &Value, ) -> Result, StorageError> { + self.chaos_mongo_guard()?; if let Some(memory) = &self.memory { let collections = memory.collections.read().await; return Ok(collections @@ -670,6 +884,7 @@ impl SharedStorage { collection: &str, mut value: Value, ) -> Result { + self.chaos_mongo_guard()?; if value.get("_id").is_none() { value["_id"] = Value::String(uuid::Uuid::new_v4().to_string()); } @@ -700,6 +915,7 @@ impl SharedStorage { filter: &Value, updates: &Value, ) -> Result, StorageError> { + self.chaos_mongo_guard()?; if let Some(memory) = &self.memory { let mut collections = memory.collections.write().await; let Some(item) = collections @@ -749,6 +965,7 @@ impl SharedStorage { filter: &Value, mut replacement: Value, ) -> Result, StorageError> { + self.chaos_mongo_guard()?; if let Some(memory) = &self.memory { let mut collections = memory.collections.write().await; let Some(item) = collections @@ -791,6 +1008,7 @@ impl SharedStorage { } pub async fn delete_one(&self, collection: &str, filter: &Value) -> Result { + self.chaos_mongo_guard()?; if let Some(memory) = &self.memory { let mut collections = memory.collections.write().await; let items = collections.entry(collection.to_owned()).or_default(); @@ -824,6 +1042,11 @@ impl SharedStorage { let records = collections.entry("revocations".to_owned()).or_default(); let before = records.len(); records.retain(|record| { + // Python's purge_expired_tokens treats a missing/malformed expires_at + // as 0 and deletes it, which would silently un-revoke a token on data + // corruption. Rust deliberately keeps such rows instead (approved + // security-safe divergence) and relies on the request-time check to + // keep ignoring them safely. let expired_jti = record.get("type").and_then(Value::as_str) == Some("jti") && record .get("expires_at") @@ -1313,17 +1536,133 @@ fn value_matches(item: &Value, filter: &Value) -> bool { let Some(filter) = filter.as_object() else { return true; }; - filter - .iter() - .all(|(key, expected)| item.get(key) == Some(expected)) + filter.iter().all(|(key, expected)| { + if key == "$or" { + return expected.as_array().is_none_or(|alternatives| { + alternatives + .iter() + .any(|alternative| value_matches(item, alternative)) + }); + } + if let Some(candidates) = expected.get("$in").and_then(Value::as_array) { + return item + .get(key) + .is_some_and(|actual| candidates.contains(actual)); + } + item.get(key) == Some(expected) + }) } fn merge_object(target: &mut Value, updates: &Value) { if let (Some(target), Some(updates)) = (target.as_object_mut(), updates.as_object()) { for (key, value) in updates { - if key != "_id" && !value.is_null() { - target.insert(key.clone(), value.clone()); + if key == "_id" { + continue; + } + let parts = key.split('.').collect::>(); + let mut current = &mut *target; + for part in &parts[..parts.len().saturating_sub(1)] { + let entry = current + .entry((*part).to_owned()) + .or_insert_with(|| Value::Object(serde_json::Map::new())); + if !entry.is_object() { + *entry = Value::Object(serde_json::Map::new()); + } + current = entry.as_object_mut().expect("object inserted above"); } + let leaf = parts.last().copied().unwrap_or(key.as_str()); + current.insert(leaf.to_owned(), value.clone()); } } } + +#[cfg(test)] +mod parity_tests { + use super::*; + use serde_json::json; + + #[tokio::test] + async fn tls_mutation_lock_serializes_cloned_memory_storage() { + let storage = SharedStorage::connect(&crate::config::SharedStorageConfig::default()) + .await + .unwrap(); + let first = storage.tls_mutation_lock().await.unwrap(); + assert!( + tokio::time::timeout( + Duration::from_millis(20), + storage.clone().tls_mutation_lock() + ) + .await + .is_err() + ); + drop(first); + tokio::time::timeout(Duration::from_secs(1), storage.tls_mutation_lock()) + .await + .unwrap() + .unwrap(); + } + + #[test] + fn memory_filters_match_python_or_and_in_contracts() { + let item = json!({"name": "alice", "role": "admin"}); + assert!(value_matches( + &item, + &json!({"$or": [{"name": "missing"}, {"role": "admin"}]}) + )); + assert!(value_matches( + &item, + &json!({"name": {"$in": ["alice", "bob"]}}) + )); + assert!(!value_matches(&item, &json!({"name": {"$in": ["bob"]}}))); + } + + #[tokio::test] + async fn purge_expired_revocations_preserves_malformed_and_missing_expiry() { + // Deliberate divergence from Python's purge_expired_tokens, which treats a + // missing/malformed expires_at as 0 and deletes it -- that would silently + // un-revoke a token on data corruption. Rust keeps these rows; the + // request-time revocation check already ignores them safely. + let config = crate::config::SharedStorageConfig::default(); + let storage = SharedStorage::connect(&config).await.unwrap(); + storage + .insert_one( + "revocations", + json!({"type": "jti", "username": "u", "jti": "no-expiry"}), + ) + .await + .unwrap(); + storage + .insert_one( + "revocations", + json!({"type": "jti", "username": "u", "jti": "bad-expiry", "expires_at": "nope"}), + ) + .await + .unwrap(); + storage + .insert_one( + "revocations", + json!({"type": "jti", "username": "u", "jti": "expired", "expires_at": 1u64}), + ) + .await + .unwrap(); + let removed = storage.purge_expired_revocations(1_000).await.unwrap(); + assert_eq!(removed, 1); + let remaining = storage.find_many("revocations", &json!({})).await.unwrap(); + assert_eq!(remaining.len(), 2); + assert!(remaining.iter().any(|record| record["jti"] == "no-expiry")); + assert!(remaining.iter().any(|record| record["jti"] == "bad-expiry")); + } + + #[test] + fn memory_updates_set_null_and_dotted_fields_like_python() { + let mut item = json!({"profile": "old", "keep": true}); + merge_object( + &mut item, + &json!({"profile.email": "a@example.com", "keep": null}), + ); + assert_eq!( + item, + json!({"profile": {"email": "a@example.com"}, "keep": null}) + ); + } +} diff --git a/gateway-rs/src/storage/snapshot.rs b/gateway-rs/src/storage/snapshot.rs index 36849400..fc6b21e8 100644 --- a/gateway-rs/src/storage/snapshot.rs +++ b/gateway-rs/src/storage/snapshot.rs @@ -8,6 +8,7 @@ use aes_gcm::{ Aes256Gcm, Nonce, aead::{Aead, Generate, KeyInit}, }; +use base64::Engine as _; use hkdf::Hkdf; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -172,13 +173,82 @@ async fn restore_file_with_key( ) -> Result<(u8, String), SnapshotError> { validate_key(key_material)?; let blob = fs::read(path)?; - let payload = decrypt_blob(&blob, key_material)?; + let mut payload = decrypt_blob(&blob, key_material)?; let version = payload.version; let created_at = payload.created_at.clone(); + for records in payload.data.values_mut() { + for record in records { + restore_python_json_values(record); + } + } storage.restore_memory_data(payload.data).await?; + reconcile_bootstrap_admin(storage).await; Ok((version, created_at)) } +fn restore_python_json_values(value: &mut Value) { + match value { + Value::Object(object) + if object.get("__type__").and_then(Value::as_str) == Some("bytes") => + { + let decoded = object + .get("data") + .and_then(Value::as_str) + .and_then(|encoded| { + base64::engine::general_purpose::STANDARD + .decode(encoded) + .ok() + }) + .and_then(|bytes| String::from_utf8(bytes).ok()) + .unwrap_or_default(); + *value = Value::String(decoded); + } + Value::Object(object) => { + for value in object.values_mut() { + restore_python_json_values(value); + } + } + Value::Array(values) => { + for value in values { + restore_python_json_values(value); + } + } + _ => {} + } +} + +async fn reconcile_bootstrap_admin(storage: &SharedStorage) { + let Ok(Some(admin)) = storage + .find_one("users", &serde_json::json!({"username": "admin"})) + .await + else { + return; + }; + let mut updates = serde_json::Map::new(); + if let Ok(email) = env::var("DOORMAN_ADMIN_EMAIL") + && admin.get("email").and_then(Value::as_str) != Some(email.as_str()) + { + updates.insert("email".to_owned(), Value::String(email)); + } + if admin.get("ui_access").and_then(Value::as_bool) != Some(true) { + updates.insert("ui_access".to_owned(), Value::Bool(true)); + } + if let Ok(password) = env::var("DOORMAN_ADMIN_PASSWORD") + && let Ok(password) = bcrypt::hash(password, bcrypt::DEFAULT_COST) + { + updates.insert("password".to_owned(), Value::String(password)); + } + if !updates.is_empty() { + let _ = storage + .update_one( + "users", + &serde_json::json!({"username": "admin"}), + &Value::Object(updates), + ) + .await; + } +} + fn decrypt_blob(blob: &[u8], key_material: &str) -> Result { let plaintext = decrypt_plaintext(blob, key_material)?; let snapshot: Snapshot = serde_json::from_slice(&plaintext)?; @@ -630,6 +700,17 @@ mod tests { assert_eq!(snapshot.data["users"][0]["password"], "hash"); } + #[test] + fn restores_python_byte_wrappers_for_password_hashes() { + let mut value = serde_json::json!({ + "password": {"__type__": "bytes", "data": "JDJiJDEyJGhhc2g="}, + "nested": [{"value": {"__type__": "bytes", "data": "dGV4dA=="}}] + }); + restore_python_json_values(&mut value); + assert_eq!(value["password"], "$2b$12$hash"); + assert_eq!(value["nested"][0]["value"], "text"); + } + #[test] fn rejects_unsupported_snapshot_version() { let payload = Snapshot { diff --git a/gateway-rs/src/storage/vault.rs b/gateway-rs/src/storage/vault.rs index 4d31148e..9211bf01 100644 --- a/gateway-rs/src/storage/vault.rs +++ b/gateway-rs/src/storage/vault.rs @@ -12,6 +12,8 @@ pub enum VaultError { MissingKey, #[error("vault encryption failed")] Encryption, + #[error("vault decryption failed")] + Decryption, } pub fn encrypt(value: &str, email: &str, username: &str) -> Result { @@ -28,6 +30,26 @@ pub fn encrypt(value: &str, email: &str, username: &str) -> Result Result { + let vault_key = env::var("VAULT_KEY") + .ok() + .filter(|value| !value.is_empty()) + .ok_or(VaultError::MissingKey)?; + let combined = format!("{email}:{username}:{vault_key}"); + let salt = Sha256::digest(combined.as_bytes()); + let mut derived = [0_u8; 32]; + pbkdf2_hmac::(vault_key.as_bytes(), &salt, 100_000, &mut derived); + let encoded = URL_SAFE.encode(derived); + let cipher = Fernet::new(&encoded).ok_or(VaultError::Decryption)?; + let token = value.strip_prefix("v1:").unwrap_or(value); + let plaintext = cipher.decrypt(token).map_err(|_| VaultError::Decryption)?; + String::from_utf8(plaintext).map_err(|_| VaultError::Decryption) +} + +pub fn is_configured() -> bool { + env::var("VAULT_KEY").is_ok_and(|value| !value.is_empty()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/gateway-rs/src/tls.rs b/gateway-rs/src/tls.rs new file mode 100644 index 00000000..38cb78c1 --- /dev/null +++ b/gateway-rs/src/tls.rs @@ -0,0 +1,225 @@ +use std::{ + fs, io, + net::SocketAddr, + path::Path, + sync::{Arc, RwLock}, + time::Duration, +}; + +use axum::{ + extract::connect_info::Connected, + serve::{IncomingStream, Listener}, +}; +use futures_util::{StreamExt, future::BoxFuture, stream::FuturesUnordered}; +use sha2::{Digest, Sha256}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_rustls::{ + TlsAcceptor, TlsStream, + rustls::{ + RootCertStore, ServerConfig, + pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject}, + server::WebPkiClientVerifier, + }, +}; +use x509_parser::parse_x509_certificate; + +pub mod policy; +pub mod profiles; +pub mod secrets; + +const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10); +const MAX_PENDING_HANDSHAKES: usize = 256; + +#[derive(Clone, Debug)] +pub struct TlsConnectionInfo { + pub peer: SocketAddr, + pub peer_certificates: Vec>, +} + +impl Connected> for TlsConnectionInfo { + fn connect_info(stream: IncomingStream<'_, TlsListener>) -> Self { + stream.remote_addr().clone() + } +} + +pub fn server_config(cert_file: &Path, key_file: &Path) -> io::Result { + server_config_from_pem(&fs::read(cert_file)?, &fs::read(key_file)?) +} + +pub fn server_config_from_pem(cert_pem: &[u8], key_pem: &[u8]) -> io::Result { + server_config_from_pem_with_roots(cert_pem, key_pem, None) +} + +pub fn server_config_from_pem_with_roots( + cert_pem: &[u8], + key_pem: &[u8], + roots: Option, +) -> io::Result { + let certs = CertificateDer::pem_slice_iter(cert_pem) + .collect::, _>>() + .map_err(invalid_pem)?; + if certs.is_empty() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "TLS certificate file is empty", + )); + } + let (_, leaf) = parse_x509_certificate(certs[0].as_ref()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "TLS certificate is not X.509"))?; + if !leaf.validity().is_valid() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "TLS certificate is not currently valid", + )); + } + let key = PrivateKeyDer::from_pem_slice(key_pem).map_err(invalid_pem)?; + let verifier = match roots { + Some(roots) if !roots.is_empty() => WebPkiClientVerifier::builder(Arc::new(roots)) + .allow_unauthenticated() + .build() + .map_err(invalid_pem)?, + _ => WebPkiClientVerifier::no_client_auth(), + }; + let mut config = ServerConfig::builder() + .with_client_cert_verifier(verifier) + .with_single_cert(certs, key) + .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + Ok(config) +} + +pub fn certificate_expiry_timestamp(pem: &[u8]) -> io::Result { + let mut min_expiry = None; + for cert in CertificateDer::pem_slice_iter(pem) { + let cert = cert.map_err(invalid_pem)?; + let (_, parsed) = parse_x509_certificate(cert.as_ref()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "certificate is not X.509"))?; + let expiry = parsed.validity().not_after.timestamp(); + min_expiry = Some(min_expiry.map_or(expiry, |prior: i64| prior.min(expiry))); + } + min_expiry + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "PEM contains no certificate")) +} + +fn invalid_pem(error: impl std::error::Error + Send + Sync + 'static) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, error) +} + +type PendingHandshake = BoxFuture<'static, io::Result<(TlsStream, TlsConnectionInfo)>>; + +pub struct TlsListener { + listener: TcpListener, + config: Arc>>, + pending: FuturesUnordered, +} + +impl TlsListener { + pub fn new(listener: TcpListener, config: ServerConfig) -> Self { + Self { + listener, + config: Arc::new(RwLock::new(Arc::new(config))), + pending: FuturesUnordered::new(), + } + } + + pub fn reload_handle(&self) -> TlsReloadHandle { + TlsReloadHandle(self.config.clone()) + } +} + +#[derive(Clone)] +pub struct TlsReloadHandle(Arc>>); + +impl TlsReloadHandle { + pub fn publish(&self, config: ServerConfig) { + *self.0.write().expect("TLS configuration lock poisoned") = Arc::new(config); + } +} + +pub fn spawn_file_reload( + cert_file: std::path::PathBuf, + key_file: std::path::PathBuf, + handle: TlsReloadHandle, + roots: Option, +) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(15)); + let mut active_hash = None; + loop { + interval.tick().await; + let materials = + tokio::try_join!(tokio::fs::read(&cert_file), tokio::fs::read(&key_file)); + let (cert_pem, key_pem) = match materials { + Ok(materials) => materials, + Err(error) => { + tracing::warn!(%error, "TLS certificate reload could not read mounted files"); + continue; + } + }; + let mut hasher = Sha256::new(); + hasher.update(&cert_pem); + hasher.update(&key_pem); + let hash: [u8; 32] = hasher.finalize().into(); + if active_hash == Some(hash) { + continue; + } + match server_config_from_pem_with_roots(&cert_pem, &key_pem, roots.clone()) { + Ok(config) => { + handle.publish(config); + active_hash = Some(hash); + tracing::info!("TLS listener certificate reloaded"); + } + Err(error) => { + tracing::warn!(%error, "TLS certificate reload rejected; retaining active certificate") + } + } + } + }) +} + +impl Listener for TlsListener { + type Io = TlsStream; + type Addr = TlsConnectionInfo; + + async fn accept(&mut self) -> (Self::Io, Self::Addr) { + loop { + tokio::select! { + accepted = self.listener.accept(), if self.pending.len() < MAX_PENDING_HANDSHAKES => { + match accepted { + Ok((stream, peer)) => { + let config = self.config.read().expect("TLS configuration lock poisoned").clone(); + let acceptor = TlsAcceptor::from(config); + self.pending.push(Box::pin(async move { + let stream = tokio::time::timeout(HANDSHAKE_TIMEOUT, acceptor.accept(stream)) + .await + .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "TLS handshake timed out"))??; + let certificates = stream.get_ref().1.peer_certificates() + .map(|chain| chain.iter().map(|cert| cert.as_ref().to_vec()).collect()) + .unwrap_or_default(); + Ok((TlsStream::Server(stream), TlsConnectionInfo { peer, peer_certificates: certificates })) + })); + } + Err(error) => { + tracing::warn!(%error, "TLS listener accept failed"); + tokio::time::sleep(Duration::from_millis(100)).await; + } + } + } + completed = self.pending.next(), if !self.pending.is_empty() => { + match completed { + Some(Ok(connected)) => return connected, + Some(Err(error)) => tracing::debug!(%error, "TLS handshake rejected"), + None => {} + } + } + } + } + } + + fn local_addr(&self) -> io::Result { + Ok(TlsConnectionInfo { + peer: self.listener.local_addr()?, + peer_certificates: Vec::new(), + }) + } +} diff --git a/gateway-rs/src/tls/policy.rs b/gateway-rs/src/tls/policy.rs new file mode 100644 index 00000000..45a9405a --- /dev/null +++ b/gateway-rs/src/tls/policy.rs @@ -0,0 +1,216 @@ +use serde::Deserialize; +use serde_json::Value; +use tokio_rustls::rustls::{ + pki_types::{CertificateDer, UnixTime}, + server::WebPkiClientVerifier, +}; +use x509_parser::{extensions::GeneralName, parse_x509_certificate}; + +use super::profiles::TlsProfiles; + +#[derive(Clone, Debug, Default, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ClientTlsMode { + #[default] + Off, + Optional, + Required, +} + +#[derive(Clone, Debug, Default, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ClientTlsPolicy { + pub mode: ClientTlsMode, + pub ca_profile_id: Option, + #[serde(default)] + pub allowed_dns_sans: Vec, + #[serde(default)] + pub allowed_uri_sans: Vec, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ClientTlsFailure { + Missing, + Invalid, + Configuration, +} + +impl ClientTlsPolicy { + pub fn validate_value( + value: &Value, + profiles: &TlsProfiles, + native_tls: bool, + ) -> Result<(), ClientTlsFailure> { + let policy: Self = + serde_json::from_value(value.clone()).map_err(|_| ClientTlsFailure::Configuration)?; + if matches!(policy.mode, ClientTlsMode::Off) { + return Ok(()); + } + if !native_tls + || policy.ca_profile_id.as_deref().is_none_or(str::is_empty) + || (policy.allowed_dns_sans.is_empty() && policy.allowed_uri_sans.is_empty()) + { + return Err(ClientTlsFailure::Configuration); + } + if !profiles + .client_cas + .contains_key(policy.ca_profile_id.as_deref().unwrap_or_default()) + { + return Err(ClientTlsFailure::Configuration); + } + if policy + .allowed_dns_sans + .iter() + .chain(policy.allowed_uri_sans.iter()) + .any(|san| san.is_empty() || san.contains('*')) + { + return Err(ClientTlsFailure::Configuration); + } + Ok(()) + } + + pub fn from_documents(api: &Value, endpoint: Option<&Value>) -> Result { + let value = endpoint + .and_then(|endpoint| endpoint.get("endpoint_client_tls_policy")) + .filter(|value| !value.is_null()) + .or_else(|| { + api.get("api_client_tls_policy") + .filter(|value| !value.is_null()) + }); + let Some(value) = value else { + return Ok(Self::default()); + }; + let policy: Self = + serde_json::from_value(value.clone()).map_err(|_| ClientTlsFailure::Configuration)?; + if !matches!(policy.mode, ClientTlsMode::Off) + && (policy.ca_profile_id.as_deref().is_none_or(str::is_empty) + || (policy.allowed_dns_sans.is_empty() && policy.allowed_uri_sans.is_empty())) + { + return Err(ClientTlsFailure::Configuration); + } + Ok(policy) + } + + pub fn enforce( + &self, + chain: &[Vec], + profiles: &TlsProfiles, + native_tls: bool, + ) -> Result<(), ClientTlsFailure> { + if matches!(self.mode, ClientTlsMode::Off) { + return Ok(()); + } + if !native_tls { + return Err(ClientTlsFailure::Configuration); + } + let roots = profiles + .client_cas + .get(self.ca_profile_id.as_deref().unwrap_or_default()) + .ok_or(ClientTlsFailure::Configuration)?; + if chain.is_empty() { + return if matches!(self.mode, ClientTlsMode::Required) { + Err(ClientTlsFailure::Missing) + } else { + Ok(()) + }; + } + let certs: Vec<_> = chain.iter().cloned().map(CertificateDer::from).collect(); + let verifier = WebPkiClientVerifier::builder(roots.clone()) + .build() + .map_err(|_| ClientTlsFailure::Configuration)?; + verifier + .verify_client_cert(&certs[0], &certs[1..], UnixTime::now()) + .map_err(|_| ClientTlsFailure::Invalid)?; + let (_, leaf) = parse_x509_certificate(&chain[0]).map_err(|_| ClientTlsFailure::Invalid)?; + let sans = leaf + .subject_alternative_name() + .map_err(|_| ClientTlsFailure::Invalid)? + .ok_or(ClientTlsFailure::Invalid)?; + let allowed = sans.value.general_names.iter().any(|name| match name { + GeneralName::DNSName(dns) => self + .allowed_dns_sans + .iter() + .any(|allowed| allowed.eq_ignore_ascii_case(dns)), + GeneralName::URI(uri) => self.allowed_uri_sans.iter().any(|allowed| allowed == uri), + _ => false, + }); + allowed.then_some(()).ok_or(ClientTlsFailure::Invalid) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use serde_json::json; + use tokio_rustls::rustls::RootCertStore; + + use super::*; + + #[test] + fn required_policy_rejects_missing_certificate() { + let mut profiles = TlsProfiles::default(); + profiles + .client_cas + .insert("internal".to_owned(), Arc::new(RootCertStore::empty())); + let policy = ClientTlsPolicy::from_documents( + &json!({"api_client_tls_policy": { + "mode": "required", "ca_profile_id": "internal", "allowed_dns_sans": ["client.example"] + }}), + None, + ).unwrap(); + assert_eq!( + policy.enforce(&[], &profiles, true), + Err(ClientTlsFailure::Missing) + ); + assert_eq!( + policy.enforce(&[], &profiles, false), + Err(ClientTlsFailure::Configuration) + ); + } + + #[test] + fn endpoint_policy_overrides_api_policy() { + let mut profiles = TlsProfiles::default(); + profiles + .client_cas + .insert("internal".to_owned(), Arc::new(RootCertStore::empty())); + let api = json!({"api_client_tls_policy": { + "mode": "required", "ca_profile_id": "internal", "allowed_dns_sans": ["client.example"] + }}); + let endpoint = json!({"endpoint_client_tls_policy": {"mode": "off"}}); + let policy = ClientTlsPolicy::from_documents(&api, Some(&endpoint)).unwrap(); + assert_eq!(policy.enforce(&[], &profiles, true), Ok(())); + let cleared = json!({"endpoint_client_tls_policy": null}); + let policy = ClientTlsPolicy::from_documents(&api, Some(&cleared)).unwrap(); + assert_eq!( + policy.enforce(&[], &profiles, true), + Err(ClientTlsFailure::Missing) + ); + } + + #[test] + fn configured_policy_requires_native_mode_and_san_allowlist() { + let mut profiles = TlsProfiles::default(); + profiles + .client_cas + .insert("internal".to_owned(), Arc::new(RootCertStore::empty())); + let policy = json!({"mode": "required", "ca_profile_id": "internal", "allowed_uri_sans": ["spiffe://example/client"]}); + assert_eq!( + ClientTlsPolicy::validate_value(&policy, &profiles, true), + Ok(()) + ); + assert_eq!( + ClientTlsPolicy::validate_value(&policy, &profiles, false), + Err(ClientTlsFailure::Configuration) + ); + assert_eq!( + ClientTlsPolicy::validate_value( + &json!({"mode": "required", "ca_profile_id": "internal"}), + &profiles, + true + ), + Err(ClientTlsFailure::Configuration) + ); + } +} diff --git a/gateway-rs/src/tls/profiles.rs b/gateway-rs/src/tls/profiles.rs new file mode 100644 index 00000000..3cece817 --- /dev/null +++ b/gateway-rs/src/tls/profiles.rs @@ -0,0 +1,577 @@ +use std::{ + collections::{HashMap, HashSet}, + fs, + path::{Path, PathBuf}, + sync::Arc, +}; + +use serde::Deserialize; +use serde_json::Value; +use sha2::{Digest, Sha256}; +use thiserror::Error; +use tokio_rustls::rustls::{ + RootCertStore, ServerConfig, + pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject}, +}; +use x509_parser::parse_x509_certificate; + +use super::secrets; + +#[derive(Default)] +pub struct TlsProfiles { + pub client_cas: HashMap>, + pub upstreams: HashMap, + client_ca_pem: HashMap>, + client_ca_expiries: HashMap, +} + +#[derive(Clone)] +pub struct UpstreamProfile { + pub ca_pem: Vec, + pub client_cert_pem: Option>, + pub client_key_pem: Option>, + pub server_name: Option, +} + +#[derive(Deserialize, Default)] +#[serde(deny_unknown_fields)] +struct FileProfiles { + #[serde(default)] + client_ca_profiles: Vec, + #[serde(default)] + upstream_profiles: Vec, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +struct FileClientCa { + id: String, + ca_files: Vec, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +struct FileUpstream { + id: String, + #[serde(default)] + ca_files: Vec, + client_cert_file: Option, + client_key_file: Option, + server_name: Option, +} + +#[derive(Debug, Error)] +pub enum TlsProfileError { + #[error("TLS profile file could not be read: {0}")] + Io(#[from] std::io::Error), + #[error("TLS profile YAML is invalid: {0}")] + Yaml(#[from] serde_yaml::Error), + #[error("invalid TLS profile: {0}")] + Invalid(String), + #[error("TLS profile secret could not be decrypted: {0}")] + Secret(#[from] secrets::TlsSecretError), +} + +impl TlsProfiles { + pub fn load_file(path: Option<&Path>) -> Result { + let Some(path) = path else { + return Ok(Self::default()); + }; + let input: FileProfiles = serde_yaml::from_slice(&fs::read(path)?)?; + let mut profiles = Self::default(); + let mut ids = HashSet::new(); + for entry in input.client_ca_profiles { + validate_id(&entry.id, &mut ids)?; + if entry.ca_files.is_empty() { + return Err(TlsProfileError::Invalid(format!( + "{} has no CA files", + entry.id + ))); + } + let ca_pem = read_pem_files(&entry.ca_files)?; + let expiry = crate::tls::certificate_expiry_timestamp(&ca_pem)?; + profiles + .client_cas + .insert(entry.id.clone(), Arc::new(load_roots(&entry.ca_files)?)); + profiles.client_ca_pem.insert(entry.id.clone(), ca_pem); + profiles.client_ca_expiries.insert(entry.id, expiry); + } + for entry in input.upstream_profiles { + validate_id(&entry.id, &mut ids)?; + if entry.client_cert_file.is_some() != entry.client_key_file.is_some() { + return Err(TlsProfileError::Invalid(format!( + "{} needs both client certificate and key", + entry.id + ))); + } + if let Some(server_name) = &entry.server_name { + if server_name.is_empty() || server_name.contains('/') || server_name.contains(':') + { + return Err(TlsProfileError::Invalid(format!( + "{} has invalid server name", + entry.id + ))); + } + } + let ca_pem = read_pem_files(&entry.ca_files)?; + if !entry.ca_files.is_empty() { + load_roots(&entry.ca_files)?; + } + let client_cert_pem = entry.client_cert_file.map(fs::read).transpose()?; + let client_key_pem = entry.client_key_file.map(fs::read).transpose()?; + if let (Some(cert), Some(key)) = (&client_cert_pem, &client_key_pem) { + validate_identity_certificate(cert, key)?; + } + profiles.upstreams.insert( + entry.id, + UpstreamProfile { + ca_pem, + client_cert_pem, + client_key_pem, + server_name: entry.server_name, + }, + ); + } + if profiles.client_cas.len() + profiles.upstreams.len() > 128 { + return Err(TlsProfileError::Invalid( + "at most 128 TLS profiles are supported".to_owned(), + )); + } + Ok(profiles) + } + + pub fn combined_client_roots(&self) -> RootCertStore { + let mut combined = RootCertStore::empty(); + for roots in self.client_cas.values() { + combined.roots.extend(roots.roots.iter().cloned()); + } + combined + } + + pub fn fingerprint(&self) -> [u8; 32] { + let mut hasher = Sha256::new(); + let mut client_ids: Vec<_> = self.client_cas.keys().collect(); + client_ids.sort(); + for id in client_ids { + hasher.update(b"client_ca"); + hasher.update(id.as_bytes()); + if let Some(pem) = self.client_ca_pem.get(id) { + hasher.update(pem); + } + for root in &self.client_cas[id].roots { + hasher.update(root.subject.as_ref()); + hasher.update(root.subject_public_key_info.as_ref()); + } + } + let mut upstream_ids: Vec<_> = self.upstreams.keys().collect(); + upstream_ids.sort(); + for id in upstream_ids { + let profile = &self.upstreams[id]; + hasher.update(b"upstream"); + hasher.update(id.as_bytes()); + hasher.update(&profile.ca_pem); + if let Some(cert) = &profile.client_cert_pem { + hasher.update(cert); + } + if let Some(key) = &profile.client_key_pem { + hasher.update(key); + } + if let Some(name) = &profile.server_name { + hasher.update(name.as_bytes()); + } + } + hasher.finalize().into() + } + + pub fn certificate_expiries(&self) -> std::collections::BTreeMap { + let mut expiries = std::collections::BTreeMap::new(); + for (id, expiry) in &self.client_ca_expiries { + expiries.insert(format!("client_ca/{id}"), *expiry); + } + for (id, profile) in &self.upstreams { + let mut expiry = None; + for pem in [Some(&profile.ca_pem), profile.client_cert_pem.as_ref()] { + if let Some(pem) = pem.filter(|pem| !pem.is_empty()) { + if let Ok(value) = crate::tls::certificate_expiry_timestamp(pem) { + expiry = Some(expiry.map_or(value, |prior: i64| prior.min(value))); + } + } + } + if let Some(expiry) = expiry { + expiries.insert(format!("upstream/{id}"), expiry); + } + } + expiries + } + + pub fn merge_admin_documents(&mut self, documents: &[Value]) -> Result<(), TlsProfileError> { + for document in documents { + let id = document.get("id").and_then(Value::as_str).ok_or_else(|| { + TlsProfileError::Invalid("admin TLS profile has no ID".to_owned()) + })?; + let mut ids = self + .client_cas + .keys() + .chain(self.upstreams.keys()) + .cloned() + .collect(); + validate_id(id, &mut ids)?; + let kind = document + .get("kind") + .and_then(Value::as_str) + .ok_or_else(|| TlsProfileError::Invalid(format!("{id} has no kind")))?; + let decrypt = |field: &str| -> Result>, TlsProfileError> { + document + .get(field) + .and_then(Value::as_str) + .map(|sealed| secrets::open(id, field, sealed).map_err(TlsProfileError::from)) + .transpose() + }; + match kind { + "client_ca" => { + let pem = decrypt("ca_pem")?.ok_or_else(|| { + TlsProfileError::Invalid(format!("{id} has no CA bundle")) + })?; + let expiry = crate::tls::certificate_expiry_timestamp(&pem)?; + self.client_cas + .insert(id.to_owned(), Arc::new(roots_from_pem(&pem)?)); + self.client_ca_pem.insert(id.to_owned(), pem); + self.client_ca_expiries.insert(id.to_owned(), expiry); + } + "upstream" => { + let ca_pem = decrypt("ca_pem")?.unwrap_or_default(); + if !ca_pem.is_empty() { + roots_from_pem(&ca_pem)?; + } + let client_cert_pem = decrypt("cert_pem")?; + let client_key_pem = decrypt("key_pem")?; + if client_cert_pem.is_some() != client_key_pem.is_some() { + return Err(TlsProfileError::Invalid(format!( + "{id} needs both client certificate and key" + ))); + } + if let (Some(cert), Some(key)) = (&client_cert_pem, &client_key_pem) { + validate_identity_certificate(cert, key)?; + } + let server_name = document + .get("server_name") + .and_then(Value::as_str) + .map(str::to_owned); + if server_name.as_deref().is_some_and(|name| { + name.is_empty() || name.contains('/') || name.contains(':') + }) { + return Err(TlsProfileError::Invalid(format!( + "{id} has invalid server name" + ))); + } + self.upstreams.insert( + id.to_owned(), + UpstreamProfile { + ca_pem, + client_cert_pem, + client_key_pem, + server_name, + }, + ); + } + "listener" => {} + _ => return Err(TlsProfileError::Invalid(format!("{id} has invalid kind"))), + } + } + if self.client_cas.len() + self.upstreams.len() > 128 { + return Err(TlsProfileError::Invalid( + "at most 128 TLS profiles are supported".to_owned(), + )); + } + Ok(()) + } +} + +pub fn select_upstream_profile( + api: &Value, + endpoint: Option<&Value>, + upstream: &str, +) -> Result, TlsProfileError> { + let url = url::Url::parse(upstream) + .map_err(|_| TlsProfileError::Invalid("upstream URL is invalid".to_owned()))?; + let host = url + .host_str() + .ok_or_else(|| TlsProfileError::Invalid("upstream host is missing".to_owned()))?; + let port = url + .port() + .or_else(|| match url.scheme() { + "https" | "grpcs" => Some(443), + "http" | "grpc" => Some(80), + _ => None, + }) + .ok_or_else(|| TlsProfileError::Invalid("upstream port is missing".to_owned()))?; + let origin = format!("{}://{}:{}", url.scheme(), host.to_ascii_lowercase(), port); + for (document, field) in [ + (endpoint, "endpoint_upstream_tls_profiles"), + (Some(api), "api_upstream_tls_profiles"), + ] { + if let Some(map) = document + .and_then(|document| document.get(field)) + .filter(|value| !value.is_null()) + { + let entries = map + .as_object() + .ok_or_else(|| TlsProfileError::Invalid(format!("{field} must be an object")))?; + for (key, value) in entries { + let key_url = url::Url::parse(key).map_err(|_| { + TlsProfileError::Invalid(format!("invalid upstream TLS binding: {key}")) + })?; + let key_host = key_url.host_str().ok_or_else(|| { + TlsProfileError::Invalid(format!("invalid upstream TLS binding: {key}")) + })?; + let key_port = key_url + .port() + .or_else(|| match key_url.scheme() { + "https" | "grpcs" => Some(443), + "http" | "grpc" => Some(80), + _ => None, + }) + .ok_or_else(|| { + TlsProfileError::Invalid(format!("invalid upstream TLS binding: {key}")) + })?; + if format!( + "{}://{}:{}", + key_url.scheme(), + key_host.to_ascii_lowercase(), + key_port + ) == origin + { + return value.as_str().map(|id| Some(id.to_owned())).ok_or_else(|| { + TlsProfileError::Invalid(format!("{field} profile ID must be a string")) + }); + } + } + } + } + for (document, field) in [ + (endpoint, "endpoint_upstream_tls_profile"), + (Some(api), "api_upstream_tls_profile"), + ] { + if let Some(value) = document + .and_then(|document| document.get(field)) + .filter(|value| !value.is_null()) + { + return value + .as_str() + .map(|id| Some(id.to_owned())) + .ok_or_else(|| TlsProfileError::Invalid(format!("{field} must be a string"))); + } + } + Ok(None) +} + +pub fn validate_upstream_bindings( + document: &Value, + prefix: &str, + profiles: &TlsProfiles, +) -> Result<(), TlsProfileError> { + let default_field = format!("{prefix}_upstream_tls_profile"); + if let Some(value) = document + .get(&default_field) + .filter(|value| !value.is_null()) + { + let id = value + .as_str() + .ok_or_else(|| TlsProfileError::Invalid(format!("{default_field} must be a string")))?; + if !profiles.upstreams.contains_key(id) { + return Err(TlsProfileError::Invalid(format!( + "unknown upstream TLS profile: {id}" + ))); + } + } + let map_field = format!("{prefix}_upstream_tls_profiles"); + if let Some(value) = document.get(&map_field).filter(|value| !value.is_null()) { + let map = value + .as_object() + .ok_or_else(|| TlsProfileError::Invalid(format!("{map_field} must be an object")))?; + for (origin, id) in map { + let url = url::Url::parse(origin) + .map_err(|_| TlsProfileError::Invalid(format!("invalid TLS origin: {origin}")))?; + if !matches!(url.scheme(), "https" | "grpcs") + || url.host_str().is_none() + || url.path() != "/" + || url.query().is_some() + || url.fragment().is_some() + || !url.username().is_empty() + || url.password().is_some() + { + return Err(TlsProfileError::Invalid(format!( + "TLS binding requires an HTTPS/grpcs origin: {origin}" + ))); + } + let id = id.as_str().ok_or_else(|| { + TlsProfileError::Invalid(format!("{map_field} profile ID must be a string")) + })?; + if !profiles.upstreams.contains_key(id) { + return Err(TlsProfileError::Invalid(format!( + "unknown upstream TLS profile: {id}" + ))); + } + } + } + Ok(()) +} + +fn validate_id(id: &str, ids: &mut HashSet) -> Result<(), TlsProfileError> { + if id.is_empty() + || id.len() > 64 + || !id + .bytes() + .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'-' | b'_')) + { + return Err(TlsProfileError::Invalid(format!( + "invalid profile ID: {id}" + ))); + } + if !ids.insert(id.to_owned()) { + return Err(TlsProfileError::Invalid(format!( + "duplicate profile ID: {id}" + ))); + } + Ok(()) +} + +fn read_pem_files(paths: &[PathBuf]) -> Result, TlsProfileError> { + let mut pem = Vec::new(); + for path in paths { + pem.extend_from_slice(&fs::read(path)?); + pem.push(b'\n'); + } + Ok(pem) +} + +fn load_roots(paths: &[PathBuf]) -> Result { + let mut roots = RootCertStore::empty(); + for path in paths { + let pem = fs::read(path)?; + let loaded = roots_from_pem(&pem)?; + roots.roots.extend(loaded.roots); + } + Ok(roots) +} + +fn roots_from_pem(pem: &[u8]) -> Result { + let mut roots = RootCertStore::empty(); + let certs = CertificateDer::pem_slice_iter(pem) + .collect::, _>>() + .map_err(|error| TlsProfileError::Invalid(error.to_string()))?; + if certs.is_empty() { + return Err(TlsProfileError::Invalid( + "CA bundle contains no certificates".to_owned(), + )); + } + for cert in certs { + let (_, parsed) = parse_x509_certificate(cert.as_ref()) + .map_err(|_| TlsProfileError::Invalid("CA certificate is not X.509".to_owned()))?; + if !parsed.validity().is_valid() { + return Err(TlsProfileError::Invalid( + "CA certificate is not currently valid".to_owned(), + )); + } + roots + .add(cert) + .map_err(|error| TlsProfileError::Invalid(error.to_string()))?; + } + Ok(roots) +} + +fn validate_identity_certificate(pem: &[u8], key_pem: &[u8]) -> Result<(), TlsProfileError> { + let certs = CertificateDer::pem_slice_iter(pem) + .collect::, _>>() + .map_err(|error| TlsProfileError::Invalid(error.to_string()))?; + let cert = certs + .first() + .ok_or_else(|| TlsProfileError::Invalid("client certificate is missing".to_owned()))?; + let (_, parsed) = parse_x509_certificate(cert.as_ref()) + .map_err(|_| TlsProfileError::Invalid("client certificate is not X.509".to_owned()))?; + if !parsed.validity().is_valid() { + return Err(TlsProfileError::Invalid( + "client certificate is not currently valid".to_owned(), + )); + } + let key = PrivateKeyDer::from_pem_slice(key_pem) + .map_err(|error| TlsProfileError::Invalid(error.to_string()))?; + ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(certs, key) + .map_err(|error| { + TlsProfileError::Invalid(format!("client certificate/key mismatch: {error}")) + })?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + #[test] + fn renewed_client_ca_material_changes_fingerprint() { + let mut profiles = TlsProfiles::default(); + profiles + .client_cas + .insert("internal".to_owned(), Arc::new(RootCertStore::empty())); + profiles + .client_ca_pem + .insert("internal".to_owned(), b"original certificate".to_vec()); + let original = profiles.fingerprint(); + profiles.client_ca_pem.insert( + "internal".to_owned(), + b"renewed certificate with the same trust key".to_vec(), + ); + assert_ne!(profiles.fingerprint(), original); + } + + #[test] + fn endpoint_origin_binding_precedes_api_default() { + let api = json!({"api_upstream_tls_profile": "api-default"}); + let endpoint = + json!({"endpoint_upstream_tls_profiles": {"https://service.example:443": "endpoint"}}); + assert_eq!( + select_upstream_profile(&api, Some(&endpoint), "https://service.example/path").unwrap(), + Some("endpoint".to_owned()), + ); + let cleared = + json!({"endpoint_upstream_tls_profile": null, "endpoint_upstream_tls_profiles": null}); + assert_eq!( + select_upstream_profile(&api, Some(&cleared), "https://service.example/path").unwrap(), + Some("api-default".to_owned()), + ); + assert_eq!( + select_upstream_profile( + &json!({"api_upstream_tls_profile": null}), + None, + "https://service.example/path" + ) + .unwrap(), + None, + ); + } + + #[test] + fn bindings_reject_plaintext_origins_and_unknown_profiles() { + let profiles = TlsProfiles::default(); + assert!( + validate_upstream_bindings( + &json!({"api_upstream_tls_profiles": {"http://service.example": "missing"}}), + "api", + &profiles, + ) + .is_err() + ); + assert!( + validate_upstream_bindings( + &json!({"api_upstream_tls_profile": "missing"}), + "api", + &profiles, + ) + .is_err() + ); + } +} diff --git a/gateway-rs/src/tls/secrets.rs b/gateway-rs/src/tls/secrets.rs new file mode 100644 index 00000000..bb162196 --- /dev/null +++ b/gateway-rs/src/tls/secrets.rs @@ -0,0 +1,85 @@ +use aes_gcm::{ + Aes256Gcm, KeyInit, Nonce, + aead::{Aead, Payload}, +}; +use base64::{Engine, engine::general_purpose::STANDARD}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum TlsSecretError { + #[error("TLS_SECRET_ENCRYPTION_KEY must be base64 for exactly 32 random bytes")] + MissingKey, + #[error("TLS secret encryption failed")] + Encryption, + #[error("TLS secret decryption failed")] + Decryption, +} + +fn cipher() -> Result { + let raw = std::env::var("TLS_SECRET_ENCRYPTION_KEY").map_err(|_| TlsSecretError::MissingKey)?; + let key = STANDARD + .decode(raw) + .map_err(|_| TlsSecretError::MissingKey)?; + if key.len() != 32 { + return Err(TlsSecretError::MissingKey); + } + Aes256Gcm::new_from_slice(&key).map_err(|_| TlsSecretError::MissingKey) +} + +pub fn seal(id: &str, field: &str, plaintext: &[u8]) -> Result { + let cipher = cipher()?; + let nonce: [u8; 12] = rand::random(); + let aad = format!("{id}:{field}"); + let nonce_array = Nonce::try_from(nonce.as_slice()).map_err(|_| TlsSecretError::Encryption)?; + let ciphertext = cipher + .encrypt( + &nonce_array, + Payload { + msg: plaintext, + aad: aad.as_bytes(), + }, + ) + .map_err(|_| TlsSecretError::Encryption)?; + let mut blob = Vec::with_capacity(nonce.len() + ciphertext.len()); + blob.extend_from_slice(&nonce); + blob.extend_from_slice(&ciphertext); + Ok(format!("v1:{}", STANDARD.encode(blob))) +} + +pub fn open(id: &str, field: &str, sealed: &str) -> Result, TlsSecretError> { + let value = sealed + .strip_prefix("v1:") + .ok_or(TlsSecretError::Decryption)?; + let blob = STANDARD + .decode(value) + .map_err(|_| TlsSecretError::Decryption)?; + if blob.len() < 28 { + return Err(TlsSecretError::Decryption); + } + let aad = format!("{id}:{field}"); + let nonce_array = Nonce::try_from(&blob[..12]).map_err(|_| TlsSecretError::Decryption)?; + cipher()? + .decrypt( + &nonce_array, + Payload { + msg: &blob[12..], + aad: aad.as_bytes(), + }, + ) + .map_err(|_| TlsSecretError::Decryption) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn requires_an_explicit_secret_key() { + if std::env::var_os("TLS_SECRET_ENCRYPTION_KEY").is_none() { + assert!(matches!( + seal("profile", "key", b"private"), + Err(TlsSecretError::MissingKey) + )); + } + } +} diff --git a/gateway-rs/src/validation/graphql.rs b/gateway-rs/src/validation/graphql.rs index 346cfb9d..a8319a61 100644 --- a/gateway-rs/src/validation/graphql.rs +++ b/gateway-rs/src/validation/graphql.rs @@ -1,5 +1,15 @@ //! GraphQL validation port: depth, complexity field cost, and introspection control. +use std::sync::LazyLock; + +static DOUBLE_QUOTED: LazyLock = + LazyLock::new(|| regex::Regex::new(r#""[^"]*""#).expect("string expression is valid")); +static SINGLE_QUOTED: LazyLock = + LazyLock::new(|| regex::Regex::new(r"'[^']*'").expect("string expression is valid")); +static GRAPHQL_WORD: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"\b[a-zA-Z_][a-zA-Z0-9_]*\b").expect("field expression is valid") +}); + #[derive(Debug, Clone)] pub struct GraphqlValidationConfig { pub max_depth: usize, @@ -42,29 +52,16 @@ pub fn validate_graphql_query(query: &str, config: &GraphqlValidationConfig) -> } pub fn calculate_graphql_depth(query: &str) -> usize { + let without_comments = query + .lines() + .map(|line| line.split_once('#').map_or(line, |(prefix, _)| prefix)) + .collect::>() + .join("\n"); + let without_strings = DOUBLE_QUOTED.replace_all(&without_comments, "\"\""); + let without_strings = SINGLE_QUOTED.replace_all(&without_strings, "''"); let mut depth = 0usize; let mut max_depth = 0usize; - let mut in_string = false; - let mut in_comment = false; - - for ch in query.chars() { - if in_comment { - if ch == '\n' { - in_comment = false; - } - continue; - } - if ch == '"' { - in_string = !in_string; - continue; - } - if in_string { - continue; - } - if ch == '#' { - in_comment = true; - continue; - } + for ch in without_strings.chars() { if ch == '{' { depth += 1; if depth > max_depth { @@ -79,21 +76,19 @@ pub fn calculate_graphql_depth(query: &str) -> usize { } pub fn calculate_graphql_cost(query: &str) -> usize { - let mut cost = 0usize; - - for word in query.split_whitespace() { - let clean_word = word - .trim_matches(|c| c == '{' || c == '}' || c == '(' || c == ')' || c == ':' || c == ','); - if clean_word.starts_with('#') { - continue; - } - if !clean_word.is_empty() && !clean_word.starts_with('"') && !is_graphql_keyword(clean_word) - { - cost += 1; - } + if query.is_empty() { + return 0; } - - cost.max(1) + let without_comments = query + .lines() + .map(|line| line.split_once('#').map_or(line, |(prefix, _)| prefix)) + .collect::>() + .join("\n"); + let without_strings = DOUBLE_QUOTED.replace_all(&without_comments, ""); + GRAPHQL_WORD + .find_iter(&without_strings) + .filter(|word| !is_graphql_keyword(&word.as_str().to_ascii_lowercase())) + .count() } fn is_graphql_keyword(word: &str) -> bool { @@ -119,5 +114,9 @@ mod tests { let introspection = "query { __schema { types { name } } }"; assert!(validate_graphql_query(introspection, &config).is_err()); + + assert_eq!(calculate_graphql_depth("{ field(arg: '{') { child } }"), 2); + assert_eq!(calculate_graphql_cost("query { user { id name } }"), 3); + assert_eq!(calculate_graphql_cost(""), 0); } } diff --git a/gateway-rs/src/validation/json.rs b/gateway-rs/src/validation/json.rs index 43c50adc..66748ddc 100644 --- a/gateway-rs/src/validation/json.rs +++ b/gateway-rs/src/validation/json.rs @@ -50,6 +50,7 @@ pub fn validate_json_with_registry( .unwrap_or(schema) .as_object() .ok_or_else(|| "Invalid endpoint validation schema".to_owned())?; + validate_schema_paths(mapping, "")?; for (path, rules) in mapping { let found = nested_value(value, path); validate_value(found, rules, path, registry)?; @@ -69,7 +70,7 @@ fn validate_value( .unwrap_or(false); let Some(value) = value.filter(|value| !value.is_null()) else { return if required { - Err(format!("Required field {path} is missing")) + Err("Field is required".to_owned()) } else { Ok(()) }; @@ -80,34 +81,43 @@ fn validate_value( .unwrap_or_default(); let valid_type = match expected { "string" => value.is_string(), - "number" => value.is_number(), + // bool is a subclass of int in Python, so the pinned number validator + // accepts JSON booleans and applies numeric bounds to 0/1. + "number" => value.is_number() || value.is_boolean(), "boolean" => value.is_boolean(), "array" => value.is_array(), "object" => value.is_object(), _ => true, }; if !valid_type { - return Err(format!("Expected {expected} at {path}")); + return Err(format!( + "Expected {expected}, got {}", + python_type_name(value) + )); } - if let Some(text) = value.as_str() { + if let Some(text) = value.as_str().filter(|_| expected == "string") { let length = text.chars().count() as f64; - enforce_range(length, rules, path, "String length")?; + enforce_range(length, rules, "String length", "", "")?; if let Some(pattern) = rules.get("pattern").and_then(Value::as_str) { - let regex = Regex::new(pattern) - .map_err(|_| format!("Invalid validation pattern for {path}"))?; - if !regex.is_match(text) { - return Err(format!("String does not match pattern {pattern} at {path}")); + let regex = cached_pattern(pattern) + .ok_or_else(|| format!("Invalid validation pattern for {path}"))?; + if regex.find(text).is_none_or(|found| found.start() != 0) { + return Err(format!("String does not match pattern {pattern}")); } } if let Some(format) = rules.get("format").and_then(Value::as_str) { validate_format(text, format, path)?; } } - if let Some(number) = value.as_f64() { - enforce_range(number, rules, path, "Value")?; + if let Some(number) = value + .as_f64() + .or_else(|| value.as_bool().map(|value| if value { 1.0 } else { 0.0 })) + .filter(|_| expected == "number") + { + enforce_range(number, rules, "Value", "", "")?; } - if let Some(items) = value.as_array() { - enforce_range(items.len() as f64, rules, path, "Array length")?; + if let Some(items) = value.as_array().filter(|_| expected == "array") { + enforce_range(items.len() as f64, rules, "Array", " items", " items")?; if let Some(item_rules) = rules.get("array_items") { for (index, item) in items.iter().enumerate() { validate_value( @@ -120,10 +130,18 @@ fn validate_value( } } if let (Some(object), Some(nested)) = ( - value.as_object(), + value.as_object().filter(|_| expected == "object"), rules.get("nested_schema").and_then(Value::as_object), ) { for (field, nested_rules) in nested { + if nested_rules + .get("required") + .and_then(Value::as_bool) + .unwrap_or(false) + && !object.contains_key(field) + { + return Err(format!("Required field {field} is missing")); + } validate_value( object.get(field), nested_rules, @@ -132,71 +150,348 @@ fn validate_value( )?; } } - if let Some(allowed) = rules.get("enum").and_then(Value::as_array) { - if !allowed.contains(value) { - return Err(format!("Value at {path} must be one of {allowed:?}")); + if let Some(allowed) = rules + .get("enum") + .and_then(Value::as_array) + .filter(|allowed| !allowed.is_empty()) + { + if !allowed.iter().any(|candidate| python_eq(candidate, value)) { + return Err(format!( + "Value must be one of {}", + python_list_repr(allowed) + )); } } if let Some(name) = rules.get("custom_validator").and_then(Value::as_str) { registry .validate(name, value, rules) - .map_err(|message| format!("{message} at {path}"))?; + .map_err(|message| message.to_owned())?; } Ok(()) } -fn enforce_range(value: f64, rules: &Value, path: &str, description: &str) -> Result<(), String> { - if rules - .get("min") - .and_then(Value::as_f64) - .is_some_and(|minimum| value < minimum) +fn enforce_range( + value: f64, + rules: &Value, + description: &str, + minimum_suffix: &str, + maximum_suffix: &str, +) -> Result<(), String> { + if let Some(minimum) = rules.get("min").and_then(Value::as_f64) + && value < minimum { - return Err(format!("{description} is below minimum at {path}")); + return Err(format!( + "{description} must be at least {}{minimum_suffix}", + python_number(minimum) + )); } - if rules - .get("max") - .and_then(Value::as_f64) - .is_some_and(|maximum| value > maximum) + if let Some(maximum) = rules.get("max").and_then(Value::as_f64) + && value > maximum { - return Err(format!("{description} exceeds maximum at {path}")); + return Err(format!( + "{description} must be at most {}{maximum_suffix}", + python_number(maximum) + )); } Ok(()) } +/// Endpoint validation schemas supply their own patterns; compile each once. +/// The cache is bounded so arbitrary schema churn cannot grow it without limit. +pub(crate) fn cached_pattern(pattern: &str) -> Option { + static CACHE: std::sync::OnceLock>> = + std::sync::OnceLock::new(); + let mut cache = CACHE.get_or_init(Default::default).lock().ok()?; + if let Some(regex) = cache.get(pattern) { + return Some(regex.clone()); + } + let regex = Regex::new(pattern).ok()?; + if cache.len() >= 1024 { + cache.clear(); + } + cache.insert(pattern.to_owned(), regex.clone()); + Some(regex) +} + fn validate_format(value: &str, format: &str, path: &str) -> Result<(), String> { + // Python's `$` also matches before one trailing newline. + let pattern_value = value.strip_suffix('\n').unwrap_or(value); let valid = match format { - "email" => value - .split_once('@') - .is_some_and(|(local, domain)| !local.is_empty() && domain.contains('.')), + "email" => { + static EMAIL: std::sync::LazyLock = std::sync::LazyLock::new(|| { + Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") + .expect("email expression is valid") + }); + EMAIL.is_match(pattern_value) + } "url" => { - (value.starts_with("http://") || value.starts_with("https://")) - && value - .split_once("://") - .is_some_and(|(_, host)| host.contains('.')) + static URL: std::sync::LazyLock = std::sync::LazyLock::new(|| { + Regex::new( + r"^https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_+.~#?&/=]*$", + ) + .expect("url expression is valid") + }); + URL.is_match(pattern_value) } "date" => valid_date(value), - "datetime" => value.contains('T') && value.len() >= 16, - "uuid" => uuid::Uuid::parse_str(value).is_ok(), + "datetime" => valid_datetime(value), + "uuid" => valid_uuid(value), _ => true, }; if valid { Ok(()) } else { - Err(format!("Invalid {format} format at {path}")) + let message = match format { + "date" => "Invalid date format (YYYY-MM-DD)", + "datetime" => "Invalid datetime format (ISO 8601)", + "uuid" => "Invalid UUID format", + "email" => "Invalid email format", + "url" => "Invalid URL format", + _ => return Ok(()), + }; + let _ = path; + Err(message.to_owned()) } } +/// `datetime.strptime(value, '%Y-%m-%d')`: unpadded month/day are accepted. fn valid_date(value: &str) -> bool { - let mut parts = value.split('-'); - matches!( - ( - parts.next().and_then(|part| part.parse::().ok()), - parts.next().and_then(|part| part.parse::().ok()), - parts.next().and_then(|part| part.parse::().ok()), - parts.next(), + static DATE: std::sync::LazyLock = std::sync::LazyLock::new(|| { + Regex::new(r"^([0-9]{4})-(1[0-2]|0[1-9]|[1-9])-(3[0-1]|[1-2][0-9]|0[1-9]|[1-9]| [1-9])$") + .expect("date expression is valid") + }); + let Some(parts) = DATE.captures(value) else { + return false; + }; + let year: i32 = parts[1].parse().unwrap_or(0); + let month: u8 = parts[2].parse().unwrap_or(0); + let day: u8 = parts[3].trim().parse().unwrap_or(0); + year >= 1 + && time::Month::try_from(month) + .ok() + .and_then(|month| time::Date::from_calendar_date(year, month, day).ok()) + .is_some() +} + +/// `uuid.UUID(value)`: strips `urn:`/`uuid:`, braces and hyphens, then needs 32 hex digits. +fn valid_uuid(value: &str) -> bool { + let hex = value + .replace("urn:", "") + .replace("uuid:", "") + .trim_matches(|c| c == '{' || c == '}') + .replace('-', ""); + hex.len() == 32 && hex.bytes().all(|byte| byte.is_ascii_hexdigit()) +} + +/// `datetime.fromisoformat(value.replace('Z', '+00:00'))` (Python 3.11+ grammar): +/// extended or basic dates, ISO week dates, any single-character separator, optional +/// `HH[:MM[:SS[.f]]]` time, and an optional `±HH[:MM[:SS[.f]]]` offset. +fn valid_datetime(value: &str) -> bool { + static DATE: std::sync::LazyLock = std::sync::LazyLock::new(|| { + Regex::new( + r"^(?:(\d{4})-(\d{2})-(\d{2})|(\d{4})(\d{2})(\d{2})|(\d{4})-W(\d{2})(?:-(\d))?|(\d{4})W(\d{2})(\d)?)", + ) + .expect("static date pattern") + }); + static TIME: std::sync::LazyLock = std::sync::LazyLock::new(|| { + Regex::new( + r"^(\d{2})(?:(?::(\d{2})(?::(\d{2})(?:[.,]\d+)?)?)|(?:(\d{2})(?:(\d{2})(?:[.,]\d+)?)?))?(?:[+-](\d{2})(?::?(\d{2})(?::?(\d{2})(?:[.,]\d+)?)?)?)?$", + ) + .expect("static time pattern") + }); + let value = value.replace('Z', "+00:00"); + let Some(captures) = DATE.captures(&value) else { + return false; + }; + let number = |index: usize| { + captures + .get(index) + .map(|m| m.as_str().parse::().unwrap()) + }; + let calendar = if number(1).is_some() { + (number(1), number(2), number(3)) + } else { + (number(4), number(5), number(6)) + }; + let date_ok = if let (Some(y), Some(m), Some(d)) = calendar { + u8::try_from(m) + .ok() + .and_then(|m| time::Month::try_from(m).ok()) + .zip(u8::try_from(d).ok()) + .is_some_and(|(m, d)| time::Date::from_calendar_date(y, m, d).is_ok()) + } else { + let (year, week, day) = if number(7).is_some() { + (number(7), number(8), number(9)) + } else { + (number(10), number(11), number(12)) + }; + match (year, week, u8::try_from(day.unwrap_or(1)).ok()) { + (Some(y), Some(w), Some(d)) => { + let weekday = match d { + 1 => Some(time::Weekday::Monday), + 2 => Some(time::Weekday::Tuesday), + 3 => Some(time::Weekday::Wednesday), + 4 => Some(time::Weekday::Thursday), + 5 => Some(time::Weekday::Friday), + 6 => Some(time::Weekday::Saturday), + 7 => Some(time::Weekday::Sunday), + _ => None, + }; + u8::try_from(w) + .ok() + .zip(weekday) + .is_some_and(|(w, wd)| time::Date::from_iso_week_date(y, w, wd).is_ok()) + } + _ => false, + } + }; + if !date_ok { + return false; + } + let rest = &value[captures.get(0).unwrap().end()..]; + let mut chars = rest.chars(); + if chars.next().is_none() { + return true; + } + let time_part = chars.as_str(); + let Some(time) = TIME.captures(time_part) else { + return false; + }; + let field = |index: usize| time.get(index).map(|m| m.as_str().parse::().unwrap()); + let hour = field(1).unwrap(); + let minute = field(2).or(field(4)).unwrap_or(0); + let second = field(3).or(field(5)).unwrap_or(0); + hour < 24 + && minute < 60 + && second < 60 + && field(6).is_none_or(|h| h < 24) + && field(7).is_none_or(|m| m < 60) + && field(8).is_none_or(|s| s < 60) +} + +fn python_type_name(value: &Value) -> &'static str { + match value { + Value::Null => "NoneType", + Value::Bool(_) => "bool", + Value::Number(number) if number.is_i64() || number.is_u64() => "int", + Value::Number(_) => "float", + Value::String(_) => "str", + Value::Array(_) => "list", + Value::Object(_) => "dict", + } +} + +fn python_number(value: f64) -> String { + if value.fract() == 0.0 { + format!("{}", value as i64) + } else { + value.to_string() + } +} + +fn python_list_repr(values: &[Value]) -> String { + let values = values + .iter() + .map(python_repr) + .collect::>() + .join(", "); + format!("[{values}]") +} + +fn python_repr(value: &Value) -> String { + match value { + Value::String(text) => { + let quote = if text.contains('\'') && !text.contains('"') { + '"' + } else { + '\'' + }; + let mut out = String::from(quote); + for character in text.chars() { + match character { + '\\' => out.push_str("\\\\"), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + c if c == quote => { + out.push('\\'); + out.push(c); + } + c => out.push(c), + } + } + out.push(quote); + out + } + Value::Bool(true) => "True".to_owned(), + Value::Bool(false) => "False".to_owned(), + Value::Null => "None".to_owned(), + Value::Array(items) => python_list_repr(items), + Value::Object(map) => format!( + "{{{}}}", + map.iter() + .map(|(key, value)| format!( + "{}: {}", + python_repr(&Value::String(key.clone())), + python_repr(value) + )) + .collect::>() + .join(", ") ), - (Some(year), Some(1..=12), Some(1..=31), None) if year > 0 - ) + number => number.to_string(), + } +} + +/// Python `==` semantics for JSON-shaped values: `1 == 1.0 == True`. +fn python_eq(left: &Value, right: &Value) -> bool { + let numeric = |value: &Value| match value { + Value::Bool(flag) => Some(if *flag { 1.0 } else { 0.0 }), + Value::Number(number) => number.as_f64(), + _ => None, + }; + if let (Some(a), Some(b)) = (numeric(left), numeric(right)) { + return a == b; + } + match (left, right) { + (Value::Array(a), Value::Array(b)) => { + a.len() == b.len() && a.iter().zip(b).all(|(x, y)| python_eq(x, y)) + } + (Value::Object(a), Value::Object(b)) => { + a.len() == b.len() + && a.iter() + .all(|(key, value)| b.get(key).is_some_and(|other| python_eq(value, other))) + } + _ => left == right, + } +} + +fn validate_schema_paths( + schema: &serde_json::Map, + parent: &str, +) -> Result<(), String> { + for (path, rules) in schema { + let full_path = if parent.is_empty() { + path.clone() + } else { + format!("{parent}.{path}") + }; + if full_path.split('.').any(|part| { + part.is_empty() + || part.split_once('[').is_some_and(|(field, index)| { + field.is_empty() && !is_ascii_digits(index.trim_end_matches(']')) + }) + }) { + return Err(format!("Invalid field path: {full_path}")); + } + if let Some(nested) = rules.get("nested_schema").and_then(Value::as_object) { + validate_schema_paths(nested, &full_path)?; + } + } + Ok(()) +} + +fn is_ascii_digits(text: &str) -> bool { + !text.is_empty() && text.bytes().all(|byte| byte.is_ascii_digit()) } fn nested_value<'a>(root: &'a Value, path: &str) -> Option<&'a Value> { @@ -207,15 +502,22 @@ fn nested_value<'a>(root: &'a Value, path: &str) -> Option<&'a Value> { current = current.get(field)?; } if let Some(index) = index { - current = current.as_array()?.get(index)?; + let items = current.as_array()?; + // Python indexes from the end for negative values. + let index = if index < 0 { + items.len().checked_sub(index.unsigned_abs() as usize)? + } else { + index as usize + }; + current = items.get(index)?; } } Some(current) } -fn parse_segment(segment: &str) -> Option<(&str, Option)> { +fn parse_segment(segment: &str) -> Option<(&str, Option)> { if let Some((field, raw_index)) = segment.split_once('[') { - let index = raw_index.strip_suffix(']')?.parse().ok()?; + let index = raw_index.trim_end_matches(']').parse().ok()?; Some((field, Some(index))) } else if segment.is_empty() { None @@ -267,7 +569,7 @@ mod tests { assert!(validate_json_with_registry(&json!({"code": "ABC"}), &schema, ®istry).is_ok()); assert_eq!( validate_json_with_registry(&json!({"code": "Abc"}), &schema, ®istry).unwrap_err(), - "Not upper at code" + "Not upper" ); let unknown = json!({"code": {"custom_validator": "not_compiled"}}); @@ -362,4 +664,166 @@ mod tests { }}}); assert!(validate_json(&json!({"user": {"name": "ok"}}), &invalid_path).is_err()); } + + #[test] + fn preserves_python_validation_messages_and_coercion_edges() { + assert_eq!( + validate_json( + &json!({}), + &json!({"name": {"required": true, "type": "string"}}) + ) + .unwrap_err(), + "Field is required" + ); + assert_eq!( + validate_json(&json!({"name": 1}), &json!({"name": {"type": "string"}})).unwrap_err(), + "Expected string, got int" + ); + assert!( + validate_json( + &json!({"enabled": true}), + &json!({"enabled": {"type": "number", "min": 1}}) + ) + .is_ok() + ); + assert_eq!( + validate_json( + &json!({"code": "xxABC"}), + &json!({"code": {"type": "string", "pattern": "ABC"}}) + ) + .unwrap_err(), + "String does not match pattern ABC" + ); + assert_eq!( + validate_json( + &json!({"when": "2025-02-30"}), + &json!({"when": {"type": "string", "format": "date"}}) + ) + .unwrap_err(), + "Invalid date format (YYYY-MM-DD)" + ); + assert_eq!( + validate_json( + &json!({"status": "CLOSED"}), + &json!({"status": {"enum": ["NEW", "OPEN"]}}) + ) + .unwrap_err(), + "Value must be one of ['NEW', 'OPEN']" + ); + } + + #[test] + fn python_edge_cases_for_paths_types_enums_and_formats() { + let ok = |doc: Value, schema: Value| validate_json(&doc, &schema); + // Invalid schema paths: empty index, multiple closing brackets are fine. + for (path, valid) in [ + ("[", false), + ("[]", false), + ("[0]", true), + ("[0]]", true), + ("a[x]", true), + ("a..b", false), + ] { + let schema = json!({ path: {"type": "string"} }); + assert_eq!( + validate_schema_paths(schema.as_object().unwrap(), "").is_ok(), + valid, + "{path}" + ); + } + // Present-but-null nested field: "Field is required", not "is missing". + let nested = json!({"o": {"type": "object", "nested_schema": {"k": {"type": "string", "required": true}}}}); + assert_eq!( + ok(json!({"o": {"k": null}}), nested.clone()).unwrap_err(), + "Field is required" + ); + assert_eq!( + ok(json!({"o": {}}), nested).unwrap_err(), + "Required field k is missing" + ); + // Negative indexes read from the end. + let idx = json!({"a[-1]": {"type": "number", "min": 5}}); + assert!(ok(json!({"a": [1, 9]}), idx.clone()).is_ok()); + assert_eq!( + ok(json!({"a": [9, 1]}), idx).unwrap_err(), + "Value must be at least 5" + ); + // Empty enum is ignored; 1 == 1.0 == True; repr of nested values. + assert!( + ok( + json!({"x": "q"}), + json!({"x": {"type": "string", "enum": []}}) + ) + .is_ok() + ); + assert!( + ok( + json!({"x": 1.0}), + json!({"x": {"type": "number", "enum": [1]}}) + ) + .is_ok() + ); + assert_eq!( + ok( + json!({"x": "z"}), + json!({"x": {"type": "string", "enum": ["a", "it's", 2, true, null]}}) + ) + .unwrap_err(), + "Value must be one of ['a', \"it's\", 2, True, None]" + ); + // Constraints only apply for the declared type. + assert!( + ok( + json!({"x": "ab"}), + json!({"x": {"type": "other", "min": 5}}) + ) + .is_ok() + ); + // strptime-style dates, permissive UUID normalisation, trailing newline in `$` formats. + for (format, value, valid) in [ + ("date", "2024-1-5", true), + ("date", "2024-02-30", false), + ("date", "2024-02-29", true), + ("date", "2023-02-29", false), + ("date", "2024-01-05T00:00", false), + ("date", "0000-01-01", false), + ("uuid", "12345678123456781234567812345678", true), + ("uuid", "{12345678-1234-5678-1234-567812345678}", true), + ( + "uuid", + "urn:uuid:12345678-1234-5678-1234-567812345678", + true, + ), + ("uuid", "1234-5678", false), + ("uuid", "1234567-8123-4567-8123-4567812345678", true), + ("datetime", "2024-1-5", false), + ("datetime", "2024-01-05", true), + ("datetime", "2024-01-05T10", true), + ("datetime", "2024-01-05 10:30:15.123456789", true), + ("datetime", "20240105T103015", true), + ("datetime", "2024-W01-1T10:00", true), + ("datetime", "2024W011", true), + ("datetime", "2024-01-05T10:00:00Z", true), + ("datetime", "2024-01-05T10:00:00+0530", true), + ("datetime", "2024-01-05T10:00:00,5", true), + ("datetime", "2024-01-05*10:00", true), + ("datetime", "2024-13-01", false), + ("datetime", "2024-02-30", false), + ("datetime", "2024-01-05T24:00", false), + ("datetime", "2024-01-05T10:60", false), + ("datetime", "2024-01-05T", false), + ("datetime", "2024-01-05T10:00+24:00", false), + ("datetime", "2024-01-05T10:0000", false), + ("datetime", "10:00", false), + ("email", "a@b.com\n", true), + ("email", "a@b.com\n\n", false), + ] { + let schema = json!({"v": {"type": "string", "format": format}}); + assert_eq!( + ok(json!({"v": value}), schema).is_ok(), + valid, + "{format} {value:?}" + ); + } + } } diff --git a/gateway-rs/src/validation/mod.rs b/gateway-rs/src/validation/mod.rs index 1fff0cd6..40b65855 100644 --- a/gateway-rs/src/validation/mod.rs +++ b/gateway-rs/src/validation/mod.rs @@ -1,4 +1,5 @@ pub mod graphql; pub mod json; pub mod protobuf; +pub mod sanitize; pub mod xml; diff --git a/gateway-rs/src/validation/sanitize.rs b/gateway-rs/src/validation/sanitize.rs new file mode 100644 index 00000000..f5373fbd --- /dev/null +++ b/gateway-rs/src/validation/sanitize.rs @@ -0,0 +1,85 @@ +use std::sync::LazyLock; + +static HTML_TAG: LazyLock = + LazyLock::new(|| regex::Regex::new(r"<[^>]+>").expect("HTML tag expression is valid")); +static UNSAFE_USERNAME: LazyLock = LazyLock::new(|| { + regex::Regex::new(r"[^a-zA-Z0-9_\-.@]").expect("username expression is valid") +}); +static UNSAFE_API_NAME: LazyLock = + LazyLock::new(|| regex::Regex::new(r"[^a-zA-Z0-9_-]").expect("API-name expression is valid")); + +fn html_escape(text: &str) -> String { + text.replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) + .replace('\'', "'") +} + +pub fn sanitize_html(text: &str, allow_tags: bool) -> String { + if allow_tags { + html_escape(text) + } else { + html_escape(&HTML_TAG.replace_all(text, "")) + } +} + +pub fn sanitize_input(text: &str, max_length: Option) -> String { + let sanitized = sanitize_html(text, false).replace('\0', ""); + let normalized = sanitized.split_whitespace().collect::>().join(" "); + match max_length.filter(|maximum| normalized.chars().count() > *maximum) { + Some(maximum) => normalized.chars().take(maximum).collect(), + None => normalized, + } +} + +pub fn sanitize_url(url: &str) -> String { + let url = crate::python_scalar::strip(url); + let lower = url.to_lowercase(); + if ["javascript:", "data:", "vbscript:", "file:", "about:"] + .iter() + .any(|scheme| lower.starts_with(scheme)) + { + String::new() + } else { + url.to_owned() + } +} + +pub fn strip_control_characters(text: &str) -> String { + text.chars() + .filter(|character| *character as u32 >= 32 || matches!(character, '\n' | '\r' | '\t')) + .collect() +} + +pub fn sanitize_username(username: &str) -> String { + let sanitized = sanitize_html(username, false); + UNSAFE_USERNAME.replace_all(&sanitized, "").into_owned() +} + +pub fn sanitize_api_name(name: &str) -> String { + let sanitized = sanitize_html(name, false); + UNSAFE_API_NAME.replace_all(&sanitized, "").into_owned() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sanitizers_match_python_examples_and_character_limits() { + assert_eq!(sanitize_html("A&B", false), "A&B"); + assert_eq!( + sanitize_html("A&B", true), + "<b>A&B</b>" + ); + assert_eq!( + sanitize_input(" hello\0 world ", Some(8)), + "hello wo" + ); + assert_eq!(sanitize_url(" JAVASCRIPT:alert(1) "), ""); + assert_eq!(strip_control_characters("a\u{1}b\nc"), "ab\nc"); + assert_eq!(sanitize_username("a!._-@"), "a._-@"); + assert_eq!(sanitize_api_name("api name/v1"), "apinamev1"); + } +} diff --git a/gateway-rs/src/validation/xml.rs b/gateway-rs/src/validation/xml.rs index 8ee871c3..d344dba6 100644 --- a/gateway-rs/src/validation/xml.rs +++ b/gateway-rs/src/validation/xml.rs @@ -26,7 +26,7 @@ pub fn soap_body_object(xml: &str) -> Result { fn parse(xml: &str) -> Result { let mut reader = Reader::from_str(xml); - reader.config_mut().trim_text(true); + reader.config_mut().trim_text(false); let mut stack = Vec::::new(); let mut root = None; loop { @@ -52,11 +52,39 @@ fn parse(xml: &str) -> Result { if let Some(node) = stack.last_mut() { let text = event .decode() + .map_err(|_| "Invalid SOAP envelope".to_owned())?; + node.text.get_or_insert_with(String::new).push_str(&text); + } + } + Ok(Event::CData(event)) => { + if let Some(node) = stack.last_mut() { + let text = event + .decode() + .map_err(|_| "Invalid SOAP envelope".to_owned())?; + node.text.get_or_insert_with(String::new).push_str(&text); + } + } + Ok(Event::GeneralRef(event)) => { + if let Some(node) = stack.last_mut() { + let resolved = match event + .resolve_char_ref() .map_err(|_| "Invalid SOAP envelope".to_owned())? - .into_owned(); - if !text.is_empty() { - node.text = Some(text); - } + { + Some(character) => character, + None => match event + .decode() + .map_err(|_| "Invalid SOAP envelope".to_owned())? + .as_ref() + { + "lt" => '<', + "gt" => '>', + "amp" => '&', + "apos" => '\'', + "quot" => '"', + _ => return Err("Invalid SOAP envelope".to_owned()), + }, + }; + node.text.get_or_insert_with(String::new).push(resolved); } } Ok(Event::End(_)) => { @@ -125,6 +153,15 @@ mod tests { ); } + #[test] + fn preserves_leaf_text_exactly_like_element_tree() { + let xml = r#" x & y A]]> 12"#; + assert_eq!( + soap_body_object(xml).unwrap(), + serde_json::json!({"a": " x & y A ", "b": " ", "c": null, "d": null, "e": "2"}) + ); + } + #[test] fn rejects_dtd_and_missing_bodies_when_validation_is_enabled() { assert!(soap_body_object("").is_err()); diff --git a/gateway-rs/tests/bootstrap_admin_parity.rs b/gateway-rs/tests/bootstrap_admin_parity.rs index c6b322b0..1f761152 100644 --- a/gateway-rs/tests/bootstrap_admin_parity.rs +++ b/gateway-rs/tests/bootstrap_admin_parity.rs @@ -110,3 +110,53 @@ async fn bootstrap_admin_seed_matches_python_and_preserves_existing_credentials( .unwrap() ); } + +#[tokio::test] +async fn bootstrap_repairs_missing_admin_fields_like_python_mongo_mode() { + if std::env::var_os("DOORMAN_BOOTSTRAP_REPAIR_TEST_CHILD").is_none() { + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .args([ + "--exact", + "bootstrap_repairs_missing_admin_fields_like_python_mongo_mode", + "--nocapture", + ]) + .env("DOORMAN_BOOTSTRAP_REPAIR_TEST_CHILD", "1") + .env("DOORMAN_ADMIN_EMAIL", "repair@doorman.dev") + .env("DOORMAN_ADMIN_PASSWORD", "repair-password-12chars") + .output() + .unwrap(); + assert!( + output.status.success(), + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + return; + } + let config = Config::for_test("removed-internal-backend".to_owned()); + let storage = SharedStorage::connect(&config.shared_storage) + .await + .unwrap(); + storage + .insert_one( + "users", + json!({"username": "admin", "ui_access": false, "email": ""}), + ) + .await + .unwrap(); + storage.initialize_core().await.unwrap(); + let admin = storage + .find_one("users", &json!({"username": "admin"})) + .await + .unwrap() + .unwrap(); + assert_eq!(admin["ui_access"], true); + assert_eq!(admin["email"], "repair@doorman.dev"); + assert!( + bcrypt::verify( + "repair-password-12chars", + admin["password"].as_str().unwrap() + ) + .unwrap() + ); +} diff --git a/gateway-rs/tests/chaos_platform_fail_closed.rs b/gateway-rs/tests/chaos_platform_fail_closed.rs new file mode 100644 index 00000000..fb0fc3f8 --- /dev/null +++ b/gateway-rs/tests/chaos_platform_fail_closed.rs @@ -0,0 +1,87 @@ +// Own test binary: the chaos outage flags are process-global, so toggling them +// here must not race the platform suites that share a process. +use std::sync::{Arc, atomic::Ordering}; + +use axum::body::{Body, to_bytes}; +use doorman_gateway::{ + AppState, Config, build_router, middleware::chaos::CHAOS_MONGO_OUTAGE, + storage::runtime::SharedStorage, +}; +use http::{Request, StatusCode, header}; +use serde_json::{Value, json}; +use tower::ServiceExt; + +const PASSWORD: &str = "fail-closed-password-123"; + +async fn send(app: &axum::Router, request: Request) -> (StatusCode, Value) { + let response = app.clone().oneshot(request).await.unwrap(); + let status = response.status(); + let body = to_bytes(response.into_body(), 1 << 20).await.unwrap(); + (status, serde_json::from_slice(&body).unwrap_or(Value::Null)) +} + +fn login_request() -> Request { + Request::builder() + .method("POST") + .uri("/platform/authorization") + .header(header::CONTENT_TYPE, "application/json") + .body(Body::from( + json!({"email": "admin@doorman.dev", "password": PASSWORD}).to_string(), + )) + .unwrap() +} + +fn me_request(token: &str) -> Request { + Request::builder() + .uri("/platform/user/me") + .header(header::AUTHORIZATION, format!("Bearer {token}")) + .body(Body::empty()) + .unwrap() +} + +/// Approved security divergence: while storage is unavailable the Rust +/// platform cannot evaluate the persisted IP/security policy, so every +/// platform request -- including login and cached-user reads that Python's +/// in-memory cache would still serve -- fails closed with 503 SEC012, and +/// recovers as soon as storage returns. +#[tokio::test] +async fn storage_outage_fails_platform_requests_closed_and_recovers() { + let config = Config::for_test("removed-internal-backend".to_owned()); + let storage = SharedStorage::connect(&config.shared_storage) + .await + .unwrap(); + storage + .insert_one("roles", json!({"role_name": "admin", "manage_users": true})) + .await + .unwrap(); + storage + .insert_one( + "users", + json!({ + "username": "admin", "email": "admin@doorman.dev", + "password": bcrypt::hash(PASSWORD, 4).unwrap(), + "role": "admin", "groups": ["ALL", "admin"], "active": true, "ui_access": true + }), + ) + .await + .unwrap(); + let mut state = AppState::new(config).unwrap(); + state.storage = Some(Arc::new(storage)); + let app = build_router(state); + + let (status, body) = send(&app, login_request()).await; + assert_eq!(status, StatusCode::OK, "{body}"); + let token = body["access_token"].as_str().unwrap().to_owned(); + + CHAOS_MONGO_OUTAGE.store(true, Ordering::Relaxed); + let during_me = send(&app, me_request(&token)).await; + let during_login = send(&app, login_request()).await; + CHAOS_MONGO_OUTAGE.store(false, Ordering::Relaxed); + + for (status, body) in [during_me, during_login] { + assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE, "{body}"); + assert_eq!(body["error_code"], "SEC012"); + } + let (status, body) = send(&app, me_request(&token)).await; + assert_eq!(status, StatusCode::OK, "{body}"); +} diff --git a/gateway-rs/tests/chaos_storage_parity.rs b/gateway-rs/tests/chaos_storage_parity.rs new file mode 100644 index 00000000..24e0a952 --- /dev/null +++ b/gateway-rs/tests/chaos_storage_parity.rs @@ -0,0 +1,47 @@ +use doorman_gateway::{ + Config, + middleware::chaos::{CHAOS_ERROR_BUDGET_BURN, CHAOS_MONGO_OUTAGE, CHAOS_REDIS_OUTAGE}, + storage::runtime::SharedStorage, +}; +use serde_json::json; +use std::sync::atomic::Ordering; + +#[tokio::test] +async fn chaos_outage_flags_fail_storage_operations_and_burn_error_budget_like_python() { + let config = Config::for_test("removed-internal-backend".to_owned()); + let storage = SharedStorage::connect(&config.shared_storage) + .await + .unwrap(); + storage + .insert_one("apis", json!({"api_name": "a"})) + .await + .unwrap(); + let burn_before = CHAOS_ERROR_BUDGET_BURN.load(Ordering::Relaxed); + + CHAOS_MONGO_OUTAGE.store(true, Ordering::Relaxed); + let err = storage.find_one("apis", &json!({})).await.unwrap_err(); + assert!(err.to_string().contains("chaos: simulated mongo outage")); + assert!(storage.insert_one("apis", json!({})).await.is_err()); + assert!(storage.delete_one("apis", &json!({})).await.is_err()); + CHAOS_MONGO_OUTAGE.store(false, Ordering::Relaxed); + assert!( + storage + .find_one("apis", &json!({})) + .await + .unwrap() + .is_some() + ); + + CHAOS_REDIS_OUTAGE.store(true, Ordering::Relaxed); + let err = storage.set_ephemeral("k", json!(1), 60).await.unwrap_err(); + assert!(err.to_string().contains("chaos: simulated redis outage")); + assert!(storage.get_ephemeral("k").await.is_err()); + CHAOS_REDIS_OUTAGE.store(false, Ordering::Relaxed); + storage.set_ephemeral("k", json!(1), 60).await.unwrap(); + assert_eq!(storage.get_ephemeral("k").await.unwrap(), Some(json!(1))); + + assert_eq!( + CHAOS_ERROR_BUDGET_BURN.load(Ordering::Relaxed) - burn_before, + 5 + ); +} diff --git a/gateway-rs/tests/crud_rest_parity.rs b/gateway-rs/tests/crud_rest_parity.rs new file mode 100644 index 00000000..2b81e5fc --- /dev/null +++ b/gateway-rs/tests/crud_rest_parity.rs @@ -0,0 +1,269 @@ +use axum::body::{Body, to_bytes}; +use doorman_gateway::{AppState, Config, build_router, storage::runtime::SharedStorage}; +use http::{Method, Request, StatusCode}; +use serde_json::{Value, json}; +use std::sync::Arc; +use tower::ServiceExt; + +async fn crud_app(schema: Value) -> axum::Router { + let config = Config::for_test("removed-internal-backend".to_owned()); + let storage = SharedStorage::connect(&config.shared_storage) + .await + .unwrap(); + storage + .insert_one( + "apis", + json!({ + "api_id": "crud-1", "api_name": "items", "api_version": "v1", + "api_type": "REST", "api_public": true, "api_auth_required": false, + "api_is_crud": true, "api_crud_collection": "crud_data_items", + "api_crud_schema": schema, "active": true, "api_servers": [] + }), + ) + .await + .unwrap(); + for method in ["GET", "POST", "PUT", "PATCH"] { + let uri = if method == "GET" || method == "POST" { + "/items" + } else { + "/items/{id}" + }; + storage + .insert_one( + "endpoints", + json!({ + "api_id": "crud-1", "api_name": "items", "api_version": "v1", + "endpoint_method": method, "endpoint_uri": uri, + "endpoint_id": format!("e-{method}") + }), + ) + .await + .unwrap(); + } + let mut state = AppState::new(config).unwrap(); + state.storage = Some(Arc::new(storage)); + build_router(state) +} + +async fn call(app: &axum::Router, method: Method, uri: &str, body: &str) -> (StatusCode, Value) { + let response = app + .clone() + .oneshot( + Request::builder() + .method(method) + .uri(uri) + .header("content-type", "application/json") + .body(Body::from(body.to_owned())) + .unwrap(), + ) + .await + .unwrap(); + let status = response.status(); + let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); + ( + status, + serde_json::from_slice(&bytes).unwrap_or(Value::Null), + ) +} + +#[tokio::test] +async fn crud_validation_failure_body_omits_error_list_like_python() { + let app = crud_app(json!({"name": {"type": "string", "required": true}})).await; + let (status, body) = call(&app, Method::POST, "/api/rest/items/v1/items", "{}").await; + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!( + body, + json!({"error_code": "CRUD400", "error_message": "Validation failed"}) + ); +} + +#[tokio::test] +async fn crud_non_object_bodies_raise_python_internal_errors() { + let app = crud_app(json!({})).await; + for (body, message) in [ + ("[1]", "list indices must be integers or slices, not str"), + ("\"abc\"", "'str' object does not support item assignment"), + ("5", "argument of type 'int' is not iterable"), + ("1.5", "argument of type 'float' is not iterable"), + ("true", "argument of type 'bool' is not iterable"), + ("null", "argument of type 'NoneType' is not iterable"), + ] { + let (status, json_body) = call(&app, Method::POST, "/api/rest/items/v1/items", body).await; + assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR, "{body}"); + assert_eq!(json_body["error_code"], "CRUD999", "{body}"); + assert_eq!( + json_body["error_message"], + format!("Internal CRUD error: {message}"), + "{body}" + ); + } +} + +#[tokio::test] +async fn crud_non_object_update_bodies_follow_python_order() { + let app = crud_app(json!({})).await; + let (status, created) = call( + &app, + Method::POST, + "/api/rest/items/v1/items", + r#"{"_id":"a","n":1}"#, + ) + .await; + assert_eq!(status, StatusCode::CREATED); + assert_eq!(created["_id"], "a"); + // Missing resource is reported before the body is used. + let (status, body) = call(&app, Method::PUT, "/api/rest/items/v1/items/zzz", "[1]").await; + assert_eq!( + (status, body["error_code"].as_str()), + (StatusCode::NOT_FOUND, Some("CRUD404")) + ); + // Truthy non-object body: `.items()` AttributeError. + let (status, body) = call(&app, Method::PUT, "/api/rest/items/v1/items/a", "[1]").await; + assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!( + body["error_message"], + "Internal CRUD error: 'list' object has no attribute 'items'" + ); + // Falsy body is a no-op update returning the stored document. + let (status, body) = call(&app, Method::PATCH, "/api/rest/items/v1/items/a", "null").await; + assert_eq!(status, StatusCode::OK); + assert_eq!(body["n"], 1); +} + +#[tokio::test] +async fn crud_non_object_update_with_schema_raises_get_attribute_error() { + let app = crud_app(json!({"name": {"type": "string"}})).await; + let (status, body) = call(&app, Method::PUT, "/api/rest/items/v1/items/a", "[1]").await; + assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!( + body["error_message"], + "Internal CRUD error: 'list' object has no attribute 'get'" + ); +} + +async fn soap_app() -> axum::Router { + let config = Config::for_test("removed-internal-backend".to_owned()); + let storage = SharedStorage::connect(&config.shared_storage) + .await + .unwrap(); + storage + .insert_one( + "apis", + json!({ + "api_id": "crud-s", "api_name": "sitems", "api_version": "v1", + "api_type": "SOAP", "api_public": true, "api_auth_required": false, + "api_is_crud": true, "api_crud_collection": "crud_data_sitems", + "api_crud_schema": {"name": {"type": "string", "required": true}}, + "active": true, "api_servers": [] + }), + ) + .await + .unwrap(); + for method in ["GET", "POST"] { + storage + .insert_one( + "endpoints", + json!({ + "api_id": "crud-s", "api_name": "sitems", "api_version": "v1", + "endpoint_method": method, "endpoint_uri": "/soap", + "endpoint_id": format!("s-{method}") + }), + ) + .await + .unwrap(); + } + let mut state = AppState::new(config).unwrap(); + state.storage = Some(Arc::new(storage)); + build_router(state) +} + +async fn soap_call( + app: &axum::Router, + method: Method, + uri: &str, + body: &str, +) -> (StatusCode, String, String) { + let response = app + .clone() + .oneshot( + Request::builder() + .method(method) + .uri(uri) + .header("content-type", "text/xml") + .body(Body::from(body.to_owned())) + .unwrap(), + ) + .await + .unwrap(); + let status = response.status(); + let content_type = response + .headers() + .get("content-type") + .and_then(|value| value.to_str().ok()) + .unwrap_or_default() + .to_owned(); + let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); + ( + status, + content_type, + String::from_utf8(bytes.to_vec()).unwrap(), + ) +} + +fn envelope(operation: &str) -> String { + format!( + "{operation}" + ) +} + +#[tokio::test] +async fn crud_soap_matches_python_envelope_wsdl_and_fault() { + let app = soap_app().await; + let (status, content_type, body) = + soap_call(&app, Method::GET, "/api/soap/sitems/v1/soap?wsdl", "").await; + assert_eq!( + (status, content_type.as_str()), + (StatusCode::OK, "text/xml") + ); + assert!(body.contains("name=\"sitemsService\"") && !body.contains("getItem")); + + let create = envelope( + "{"name": "Ada", "_id": "x1"}", + ); + let (status, content_type, body) = + soap_call(&app, Method::POST, "/api/soap/sitems/v1/soap", &create).await; + assert_eq!( + (status, content_type.as_str()), + (StatusCode::OK, "text/xml") + ); + assert_eq!( + body, + "\n\n \n \n {\"name\": \"Ada\", \"_id\": \"x1\"}\n \n \n" + ); + + let list = envelope(""); + let (_, _, body) = soap_call(&app, Method::POST, "/api/soap/sitems/v1/soap", &list).await; + assert!( + body.contains("[{\"name\": \"Ada\", \"_id\": \"x1\"}]"), + "{body}" + ); + + let fault = "An unknown error occurred in SOAP response"; + for operation in [ + "x1", + "x1", + "{}", + "not json", + ] { + let (status, content_type, body) = soap_call( + &app, + Method::POST, + "/api/soap/sitems/v1/soap", + &envelope(operation), + ) + .await; + assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR, "{operation}"); + assert_eq!(content_type, "text/xml", "{operation}"); + assert_eq!(body, fault, "{operation}"); + } +} diff --git a/gateway-rs/tests/external_storage.rs b/gateway-rs/tests/external_storage.rs index 6cc927bf..82711e73 100644 --- a/gateway-rs/tests/external_storage.rs +++ b/gateway-rs/tests/external_storage.rs @@ -91,8 +91,12 @@ async fn external_storage_shares_mongo_documents_and_redis_state() { .unwrap(); assert!(first.find_one("apis", &id_filter).await.unwrap().is_some()); let marker = format!("external-{nonce}"); + let marker_id = format!("{marker}-id"); first - .insert_one("apis", json!({"api_name": marker, "api_version": "v1"})) + .insert_one( + "apis", + json!({"api_name": marker, "api_version": "v1", "api_id": marker_id}), + ) .await .unwrap(); assert!( @@ -102,9 +106,13 @@ async fn external_storage_shares_mongo_documents_and_redis_state() { .unwrap() .is_some() ); + // The unique api_id index is enforced across connections. assert!( second - .insert_one("apis", json!({"api_name": marker, "api_version": "v1"})) + .insert_one( + "apis", + json!({"api_name": marker, "api_version": "v2", "api_id": marker_id}), + ) .await .is_err() ); @@ -659,8 +667,14 @@ message HelloReply { string message = 1; } .method(Method::POST) .uri(format!("/platform/proto/{api_name}/{api_version}")) .header(header::COOKIE, &cookie) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(proto)) + // The upload is a multipart `file` part (FastAPI UploadFile). + .header( + header::CONTENT_TYPE, + "multipart/form-data; boundary=external-proto-boundary", + ) + .body(Body::from(format!( + "--external-proto-boundary\r\nContent-Disposition: form-data; name=\"file\"; filename=\"service.proto\"\r\nContent-Type: application/octet-stream\r\n\r\n{proto}\r\n--external-proto-boundary--\r\n" + ))) .unwrap(), ) .await diff --git a/gateway-rs/tests/fixtures/crud_customer_api.proto b/gateway-rs/tests/fixtures/crud_customer_api.proto new file mode 100644 index 00000000..06e229a8 --- /dev/null +++ b/gateway-rs/tests/fixtures/crud_customer_api.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package customer_api; + +service Customer_apiService { + rpc CreateItem (CreateItemRequest) returns (CreateItemResponse); + rpc ListItems (ListItemsRequest) returns (ListItemsResponse); +} + +message CreateItemRequest { + string name = 1; + int32 age = 2; + double score = 3; + bool ok = 4; + repeated string tags = 5; + string meta = 6; +} + +message CreateItemResponse { + string result = 1; // JSON string of created object +} + +message ListItemsRequest {} + +message ListItemsResponse { + string items = 1; // JSON string of list +} diff --git a/gateway-rs/tests/fixtures/crud_customer_api.wsdl b/gateway-rs/tests/fixtures/crud_customer_api.wsdl new file mode 100644 index 00000000..8c5416d3 --- /dev/null +++ b/gateway-rs/tests/fixtures/crud_customer_api.wsdl @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gateway-rs/tests/live_tests_parity.rs b/gateway-rs/tests/live_tests_parity.rs index 68cf43c2..3ca389f0 100644 --- a/gateway-rs/tests/live_tests_parity.rs +++ b/gateway-rs/tests/live_tests_parity.rs @@ -28,6 +28,27 @@ async fn test_app_state() -> AppState { test_app_state_with(|_| {}).await } +/// Re-runs `name` in a child process with ENFORCE_ADMIN_SUBSCRIPTION=true (as the Python +/// live tests do) so the process-global variable cannot leak into concurrent tests. +fn reexec_with_enforced_admin_subscription(name: &str) -> bool { + if std::env::var_os("DOORMAN_ENFORCE_ADMIN_SUB_CHILD").is_some() { + return false; + } + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .args(["--exact", name, "--nocapture"]) + .env("DOORMAN_ENFORCE_ADMIN_SUB_CHILD", "1") + .env("ENFORCE_ADMIN_SUBSCRIPTION", "true") + .output() + .unwrap(); + assert!( + output.status.success(), + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + true +} + #[tokio::test] async fn hot_reload_changes_retry_and_timeout_on_real_http_requests() { let attempts = Arc::new(AtomicUsize::new(0)); @@ -227,6 +248,15 @@ async fn test_app_state_with(configure: impl FnOnce(&mut Config)) -> AppState { state } +const PROTO_MULTIPART_CONTENT_TYPE: &str = "multipart/form-data; boundary=live-proto-boundary"; + +/// The pinned proto upload takes a multipart `file` part (FastAPI UploadFile). +fn proto_multipart(proto: &str) -> String { + format!( + "--live-proto-boundary\r\nContent-Disposition: form-data; name=\"file\"; filename=\"service.proto\"\r\nContent-Type: application/octet-stream\r\n\r\n{proto}\r\n--live-proto-boundary--\r\n" + ) +} + async fn login_admin(app: &axum::Router) -> String { let response = app .clone() @@ -978,7 +1008,6 @@ async fn configure_bandwidth_gateway( json!({ "bandwidth_limit_bytes": limit, "bandwidth_limit_window": window, - "bandwidth_limit_enabled": true, }), ) .await; @@ -1172,7 +1201,7 @@ async fn live_test_99_cors_credentialed_wildcard_is_rejected_as_approved_securit let (status, body) = authed_json_response(&app, &token, Method::POST, "/platform/api", api).await; assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY); - assert_eq!(body["detail"][0]["type"], "value_error.cors_origins"); + assert_eq!(body["error_code"], "VAL001"); } #[tokio::test] @@ -1388,11 +1417,18 @@ async fn monitor_metrics_increment_status_series_and_top_apis_parity() { assert!(metrics["total_requests"].as_u64().unwrap_or(0) >= 3); assert!(metrics["status_counts"]["200"].as_u64().unwrap_or(0) >= 3); assert!(metrics["series"].is_array()); + // Python's MetricsStore.snapshot serializes top_apis as a list of + // [name, count] tuples (sorted(dict.items())), not objects. assert!(metrics["top_apis"].as_array().unwrap().iter().any(|entry| { - entry["name"] - .as_str() + entry + .as_array() + .and_then(|pair| pair.first()) + .and_then(|name| name.as_str()) .is_some_and(|name| name.starts_with("rest:")) })); + // Python's unique_users is len(agg_user_counts) reconstructed from the + // selected range's buckets, not a process-global all-time distinct count. + assert!(metrics["unique_users"].as_u64().unwrap_or(0) >= 1); upstream.abort(); } @@ -2225,6 +2261,10 @@ async fn request_id_is_forwarded_to_authenticated_rest_upstream_and_response() { #[tokio::test] async fn live_test_21_subscription_list_unsubscribe_parity() { + if reexec_with_enforced_admin_subscription("live_test_21_subscription_list_unsubscribe_parity") + { + return; + } let app = build_router(test_app_state().await); let token = login_admin(&app).await; let api_name = "subs-test-21"; @@ -3157,10 +3197,12 @@ async fn live_test_41_soap_and_85_endpoint_validation_parity() { "endpoint_id": endpoint_id, "validation_enabled": true, "validation_schema": { - "intA": { - "required": true, - "type": "string", - "min": 2 + "validation_schema": { + "intA": { + "required": true, + "type": "string", + "min": 2 + } } } }) @@ -3221,7 +3263,7 @@ async fn live_test_90_security_tools_and_config_export_import_parity() { .header(header::AUTHORIZATION, format!("Bearer {token}")) .header(header::CONTENT_TYPE, "application/json") .body(Body::from( - json!({"origin": "http://localhost:3000"}).to_string(), + json!({"origin": "http://localhost:3000", "method": "GET"}).to_string(), )) .unwrap(), ) @@ -3934,8 +3976,8 @@ message DeleteReply { bool ok = 1; } .method(Method::POST) .uri(format!("/platform/proto/{api_name}/{api_version}")) .header(header::AUTHORIZATION, format!("Bearer {token}")) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(proto)) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(proto))) .unwrap(), ) .await @@ -4086,8 +4128,8 @@ message HelloReply { string message = 1; } .method(Method::POST) .uri(format!("/platform/proto/{api_name}/{api_version}")) .header(header::AUTHORIZATION, format!("Bearer {token}")) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(proto)) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(proto))) .unwrap(), ) .await @@ -4168,6 +4210,9 @@ message HelloReply { string message = 1; } #[tokio::test] async fn grpc_gateway_maps_upstream_statuses_parity() { + if reexec_with_enforced_admin_subscription("grpc_gateway_maps_upstream_statuses_parity") { + return; + } let (upstream_url, upstream) = start_grpc_status_upstream().await; let app = build_router(test_app_state().await); let token = login_admin(&app).await; @@ -4209,8 +4254,8 @@ message HelloReply { string message = 1; } .method(Method::POST) .uri(format!("/platform/proto/{api_name}/{api_version}")) .header(header::AUTHORIZATION, format!("Bearer {token}")) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(proto)) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(proto))) .unwrap(), ) .await @@ -4400,8 +4445,8 @@ message WatchReply { string message = 1; } .method(Method::POST) .uri(format!("/platform/proto/{api_name}/{api_version}")) .header(header::AUTHORIZATION, format!("Bearer {token}")) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(proto)) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(proto))) .unwrap(), ) .await @@ -4504,8 +4549,8 @@ message SumReply { int32 sum = 1; } .method(Method::POST) .uri(format!("/platform/proto/{api_name}/{api_version}")) .header(header::AUTHORIZATION, format!("Bearer {token}")) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(proto)) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(proto))) .unwrap(), ) .await @@ -4678,8 +4723,8 @@ service Greeter {} .method(Method::POST) .uri(format!("/platform/proto/{api_name}/{api_version}")) .header(header::AUTHORIZATION, format!("Bearer {token}")) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(proto)) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(proto))) .unwrap(), ) .await @@ -4760,8 +4805,8 @@ message HelloReply { string message = 1; } .method(Method::POST) .uri(format!("/platform/proto/{api_name}/{api_version}")) .header(header::AUTHORIZATION, format!("Bearer {token}")) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(proto)) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(proto))) .unwrap(), ) .await @@ -4859,8 +4904,8 @@ message SumReply { int32 sum = 1; } .method(Method::POST) .uri(format!("/platform/proto/{api_name}/{api_version}")) .header(header::AUTHORIZATION, format!("Bearer {token}")) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(proto)) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(proto))) .unwrap(), ) .await @@ -5069,7 +5114,7 @@ async fn live_test_40_soap_gateway_basic_flow_parity() { response.headers()[header::CONTENT_TYPE] .to_str() .unwrap() - .starts_with("text/xml") + .starts_with("application/xml") ); let body = String::from_utf8( to_bytes(response.into_body(), usize::MAX) diff --git a/gateway-rs/tests/memory_routes_parity.rs b/gateway-rs/tests/memory_routes_parity.rs index a26f49d1..d1ee83bf 100644 --- a/gateway-rs/tests/memory_routes_parity.rs +++ b/gateway-rs/tests/memory_routes_parity.rs @@ -170,6 +170,17 @@ async fn python_memory_route_contracts_in_isolated_processes() { } let manager = login(&app, "manager").await; + let (status, body) = request( + &app, + Method::POST, + "/platform/memory/dump", + Some(&manager), + json!({"path": []}), + ) + .await; + assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY, "{body}"); + assert_eq!(body["error_code"], "VAL001"); + let (status, body) = request( &app, Method::POST, diff --git a/gateway-rs/tests/platform_native.rs b/gateway-rs/tests/platform_native.rs index 2eafa27a..214bdd98 100644 --- a/gateway-rs/tests/platform_native.rs +++ b/gateway-rs/tests/platform_native.rs @@ -71,6 +71,16 @@ impl Drop for EnvVarRestore { } } +const PROTO_MULTIPART_CONTENT_TYPE: &str = "multipart/form-data; boundary=native-proto-boundary"; + +/// The pinned proto upload takes a multipart `file` part (FastAPI UploadFile). +fn proto_multipart(content: &[u8]) -> Vec { + let mut body = b"--native-proto-boundary\r\nContent-Disposition: form-data; name=\"file\"; filename=\"service.proto\"\r\nContent-Type: application/octet-stream\r\n\r\n".to_vec(); + body.extend_from_slice(content); + body.extend_from_slice(b"\r\n--native-proto-boundary--\r\n"); + body +} + #[tokio::test] async fn restored_python_and_mongo_password_bytes_support_login() { use base64::{Engine, engine::general_purpose::STANDARD}; @@ -306,7 +316,11 @@ async fn discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract let calls = openapi_counter.clone(); async move { calls.fetch_add(1, Ordering::SeqCst); - Json(json!({"openapi": "3.0.0", "info": {"title": "Test API", "version": "1.0.0"}})) + Json(json!({ + "openapi": "3.0.0", + "info": {"title": "Test API", "version": "1.0.0"}, + "paths": {"/widgets": {"get": {"summary": "List widgets"}}} + })) } }), ) @@ -380,6 +394,45 @@ async fn discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract } assert_eq!(openapi_calls.load(Ordering::SeqCst), 1); + let refreshed = platform_request( + &app, + Method::POST, + "/platform/api/cached-openapi/v1/openapi/refresh", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(refreshed.status(), StatusCode::OK); + assert_eq!( + response_json(refreshed).await, + json!({ + "message": "OpenAPI spec refreshed successfully", + "endpoints_found": 1 + }) + ); + assert_eq!(openapi_calls.load(Ordering::SeqCst), 2); + + let imported = platform_request( + &app, + Method::POST, + "/platform/api/cached-openapi/v1/openapi/import", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(imported.status(), StatusCode::OK); + assert_eq!( + response_json(imported).await, + json!({ + "message": "OpenAPI import completed", + "endpoints_found": 1, + "endpoints_imported": 1, + "endpoints_skipped": 0 + }) + ); + for (expected_cached, _) in [(false, 0), (true, 1)] { let response = platform_request( &app, @@ -1015,6 +1068,45 @@ async fn user_create_requires_python_model_role_before_service_logic() { ); } +#[tokio::test] +async fn update_password_model_validates_before_auth_and_matches_pydantic_bounds() { + let app = build_router(memory_state(false).await); + for body in [ + json!({}), + json!({"new_password": []}), + json!({"new_password": "short"}), + json!({"new_password": "x".repeat(37)}), + ] { + let response = platform_request( + &app, + Method::PUT, + "/platform/user/someone/update-password", + None, + None, + Some(body), + ) + .await; + assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); + assert_eq!(response_json(response).await["error_code"], "VAL001"); + } + + for body in [ + json!({"new_password": "é".repeat(36)}), + json!({"new_password": 123456, "unknown": "ignored"}), + ] { + let response = platform_request( + &app, + Method::PUT, + "/platform/user/someone/update-password", + None, + None, + Some(body), + ) + .await; + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + } +} + #[tokio::test] async fn user_create_matches_pydantic_bounds_and_scalar_coercion() { let app = build_router(memory_state(false).await); @@ -1025,6 +1117,8 @@ async fn user_create_matches_pydantic_bounds_and_scalar_coercion() { json!({"username": "shortpassword", "email": "shortpassword@example.com", "password": "TooShort1!", "role": "user"}), json!({"username": "negative-rate", "email": "negative-rate@example.com", "password": "A_secure_password_123!", "role": "user", "rate_limit_duration": -1}), json!({"username": "scalar-groups", "email": "scalar-groups@example.com", "password": "A_secure_password_123!", "role": "user", "groups": "team"}), + json!({"username": "null-groups", "email": "null-groups@example.com", "password": "A_secure_password_123!", "role": "user", "groups": null}), + json!({"username": "unicode-too-long", "email": "unicode-too-long@example.com", "password": "A_secure_password_123!", "role": "é".repeat(51)}), ] { let response = platform_request( &app, @@ -1070,7 +1164,26 @@ async fn user_create_matches_pydantic_bounds_and_scalar_coercion() { assert_eq!(created["rate_limit_duration"], 7); assert_eq!(created["active"], false); assert_eq!(created["bandwidth_limit_window"], "day"); + assert_eq!(created["ui_access"], false); + assert!(created["throttle_duration"].is_null()); + assert!(created["custom_attributes"].is_null()); assert!(created.get("unexpected").is_none()); + + let unicode = platform_request( + &app, + Method::POST, + "/platform/user", + Some(&cookie), + None, + Some(json!({ + "username": "é".repeat(50), + "email": "unicode@example.com", + "password": "A_secure_password_123!", + "role": "é".repeat(50), + })), + ) + .await; + assert_eq!(unicode.status(), StatusCode::CREATED); } #[tokio::test] @@ -1390,6 +1503,35 @@ async fn role_and_group_models_match_pydantic_defaults_coercion_and_empty_update response_json(empty_group_update).await["error_code"], "GRP006" ); + for (path, field) in [ + ("/platform/role", "role_name"), + ("/platform/group", "group_name"), + ] { + let accepted = platform_request( + &app, + Method::POST, + path, + Some(&cookie), + None, + Some(json!({field: "é".repeat(50)})), + ) + .await; + assert_eq!(accepted.status(), StatusCode::CREATED, "{path}"); + let rejected = platform_request( + &app, + Method::POST, + path, + Some(&cookie), + None, + Some(json!({field: "é".repeat(51)})), + ) + .await; + assert_eq!( + rejected.status(), + StatusCode::UNPROCESSABLE_ENTITY, + "{path}" + ); + } } #[tokio::test] @@ -1497,6 +1639,26 @@ async fn routing_models_match_pydantic_generation_coercion_and_update_exclusion( .await; assert_eq!(missing_update.status(), StatusCode::BAD_REQUEST); assert_eq!(response_json(missing_update).await["error_code"], "RTG004"); + let unicode_boundary = platform_request( + &app, + Method::POST, + "/platform/routing", + Some(&cookie), + None, + Some(json!({"routing_name": "é".repeat(50), "routing_servers": ["http://upstream"]})), + ) + .await; + assert_eq!(unicode_boundary.status(), StatusCode::CREATED); + let unicode_too_long = platform_request( + &app, + Method::POST, + "/platform/routing", + Some(&cookie), + None, + Some(json!({"routing_name": "é".repeat(51), "routing_servers": ["http://upstream"]})), + ) + .await; + assert_eq!(unicode_too_long.status(), StatusCode::UNPROCESSABLE_ENTITY); } #[tokio::test] @@ -1521,7 +1683,7 @@ async fn endpoint_models_match_pydantic_coercion_bounds_and_null_elision() { Some(json!({ "api_name": "endpoint-model", "api_version": "v1", "endpoint_method": "GET", "endpoint_uri": "/items", "endpoint_description": 123, "endpoint_servers": [1, false], - "ignored": "field" + "api_id": "", "endpoint_id": "", "ignored": "field" })), ) .await; @@ -1538,6 +1700,32 @@ async fn endpoint_models_match_pydantic_coercion_bounds_and_null_elision() { assert_eq!(endpoint["client_uri"], Value::Null); assert_eq!(endpoint["api_id"], "source-api-id"); assert!(endpoint.get("ignored").is_none()); + let unicode_boundary = platform_request( + &app, + Method::POST, + "/platform/endpoint", + Some(&cookie), + None, + Some(json!({ + "api_name": "endpoint-model", "api_version": "v1", "endpoint_method": "GET", + "endpoint_uri": "/unicode", "endpoint_description": "é".repeat(255) + })), + ) + .await; + assert_eq!(unicode_boundary.status(), StatusCode::CREATED); + let unicode_too_long = platform_request( + &app, + Method::POST, + "/platform/endpoint", + Some(&cookie), + None, + Some(json!({ + "api_name": "endpoint-model", "api_version": "v1", "endpoint_method": "GET", + "endpoint_uri": "/unicode-too-long", "endpoint_description": "é".repeat(256) + })), + ) + .await; + assert_eq!(unicode_too_long.status(), StatusCode::UNPROCESSABLE_ENTITY); let duplicate = platform_request( &app, Method::POST, @@ -1743,6 +1931,30 @@ async fn subscription_model_coerces_python_scalars_and_ignores_unknown_fields() ) .await; assert_eq!(invalid.status(), StatusCode::UNPROCESSABLE_ENTITY); + let unicode_boundary = platform_request( + &app, + Method::POST, + "/platform/subscription/subscribe", + Some(&cookie), + None, + Some(json!({ + "username": "admin", "api_name": "é".repeat(50), "api_version": "v1" + })), + ) + .await; + assert_eq!(unicode_boundary.status(), StatusCode::NOT_FOUND); + let unicode_too_long = platform_request( + &app, + Method::POST, + "/platform/subscription/subscribe", + Some(&cookie), + None, + Some(json!({ + "username": "admin", "api_name": "é".repeat(51), "api_version": "v1" + })), + ) + .await; + assert_eq!(unicode_too_long.status(), StatusCode::UNPROCESSABLE_ENTITY); } #[tokio::test] @@ -1858,7 +2070,32 @@ async fn authorization_refresh_and_invalidate_match_python() { Some(json!({})), ) .await; + let invalidate_cookies = invalidate + .headers() + .get_all(header::SET_COOKIE) + .iter() + .map(|value| value.to_str().unwrap().to_owned()) + .collect::>(); + // Python's authorization_invalidate clears both the primary and dev cookie + // names with Max-Age=0. + assert!( + invalidate_cookies.iter().any( + |cookie| cookie.starts_with("access_token_cookie=") && cookie.contains("Max-Age=0") + ), + "{invalidate_cookies:?}" + ); + assert!( + invalidate_cookies + .iter() + .any(|cookie| cookie.starts_with("access_token_cookie_dev=") + && cookie.contains("Max-Age=0")), + "{invalidate_cookies:?}" + ); assert_eq!(invalidate.status(), StatusCode::OK); + assert_eq!( + response_json(invalidate).await, + json!({"message": "Your token has been invalidated"}) + ); let rejected = platform_request( &app, @@ -2028,8 +2265,7 @@ async fn authorization_admin_lifecycle_and_revoke_match_python() { .await; assert_eq!(status.status(), StatusCode::OK); let status = response_json(status).await; - assert_eq!(status["active"], true); - assert_eq!(status["revoked"], false); + assert_eq!(status, json!({"active": true, "revoked": false})); let revoke_path = format!("/platform/authorization/admin/revoke/{username}"); let revoke = platform_request( @@ -2042,6 +2278,10 @@ async fn authorization_admin_lifecycle_and_revoke_match_python() { ) .await; assert_eq!(revoke.status(), StatusCode::OK); + assert_eq!( + response_json(revoke).await, + json!({"message": format!("All tokens revoked for {username}")}) + ); let rejected = platform_request( &app, Method::GET, @@ -2064,6 +2304,10 @@ async fn authorization_admin_lifecycle_and_revoke_match_python() { ) .await; assert_eq!(disable.status(), StatusCode::OK); + assert_eq!( + response_json(disable).await, + json!({"message": format!("User {username} disabled and tokens revoked")}) + ); let status = platform_request( &app, Method::GET, @@ -2088,6 +2332,10 @@ async fn authorization_admin_lifecycle_and_revoke_match_python() { ) .await; assert_eq!(enable.status(), StatusCode::OK); + assert_eq!( + response_json(enable).await, + json!({"message": format!("User {username} enabled")}) + ); let unrevoke_path = format!("/platform/authorization/admin/unrevoke/{username}"); let unrevoke = platform_request( &app, @@ -2099,6 +2347,10 @@ async fn authorization_admin_lifecycle_and_revoke_match_python() { ) .await; assert_eq!(unrevoke.status(), StatusCode::OK); + assert_eq!( + response_json(unrevoke).await, + json!({"message": format!("Token revocation cleared for {username}")}) + ); let status = platform_request( &app, Method::GET, @@ -2111,6 +2363,49 @@ async fn authorization_admin_lifecycle_and_revoke_match_python() { let status = response_json(status).await; assert_eq!(status["active"], true); assert_eq!(status["revoked"], false); + + for (method, action, expected) in [ + ( + Method::POST, + "revoke", + "All tokens revoked for missing-user", + ), + ( + Method::POST, + "unrevoke", + "Token revocation cleared for missing-user", + ), + ( + Method::POST, + "disable", + "User missing-user disabled and tokens revoked", + ), + (Method::POST, "enable", "User missing-user enabled"), + ] { + let response = platform_request( + &app, + method, + &format!("/platform/authorization/admin/{action}/missing-user"), + Some(&admin_cookie), + None, + Some(json!({})), + ) + .await; + assert_eq!(response.status(), StatusCode::OK, "{action}"); + assert_eq!(response_json(response).await, json!({"message": expected})); + } + + let missing_status = platform_request( + &app, + Method::GET, + "/platform/authorization/admin/status/missing-user", + Some(&admin_cookie), + None, + None, + ) + .await; + assert_eq!(missing_status.status(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(response_json(missing_status).await["error_code"], "GTW999"); } #[derive(Clone, Default)] @@ -2447,7 +2742,7 @@ async fn user_managers_cannot_assign_or_escalate_to_admin() { for (method, path, payload) in [ ( "POST", - "/platform/users", + "/platform/user", json!({ "username": "new-admin", "email": "new-admin@example.com", @@ -2455,7 +2750,7 @@ async fn user_managers_cannot_assign_or_escalate_to_admin() { "role": "admin" }), ), - ("PUT", "/platform/users/manager", json!({"role": "admin"})), + ("PUT", "/platform/user/manager", json!({"role": "admin"})), ] { let response = app .clone() @@ -2810,6 +3105,50 @@ async fn https_mode_requires_matching_csrf_and_preserves_request_id() { assert_eq!(accepted_without_csrf.status(), StatusCode::OK); } +/// Approved security divergence: under HTTPS_ONLY Python skips CSRF unless the +/// effective connection is HTTPS (scheme or X-Forwarded-Proto) and exempts every +/// `/platform/authorization*` path. Rust enforces the double-submit check for +/// every authenticated platform request regardless of the transport the proxy +/// reports, exempting only login, registration, and the admin bootstrap path. +#[tokio::test] +async fn https_only_csrf_applies_to_plain_http_and_authorization_subpaths() { + let app = build_router(memory_state(true).await); + let (cookie, csrf) = login(&app).await; + + let plain_http = platform_request( + &app, + Method::GET, + "/platform/user/me", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(plain_http.status(), StatusCode::UNAUTHORIZED); + + let refresh_without_csrf = platform_request( + &app, + Method::POST, + "/platform/authorization/refresh", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(refresh_without_csrf.status(), StatusCode::UNAUTHORIZED); + + let refresh_with_csrf = platform_request( + &app, + Method::POST, + "/platform/authorization/refresh", + Some(&cookie), + Some(&csrf), + None, + ) + .await; + assert_eq!(refresh_with_csrf.status(), StatusCode::OK); +} + // Cookie options are read per request from the environment in both the pinned // Python routes and Rust compatibility handler. Run each variant in a child so // Rust's parallel test workers never observe another case's cookie policy. @@ -2929,7 +3268,8 @@ async fn tampered_jwt_is_rejected_with_python_compatible_unauthorized_status() { .await; assert_eq!(response.status(), StatusCode::UNAUTHORIZED); let body = response_json(response).await; - assert_eq!(body["error_code"], "AUTH003"); + // Verified against the pinned server: /platform/user/me wraps auth failures as GTW998. + assert_eq!(body["error_code"], "GTW998"); assert_eq!(body["error_message"], "Unauthorized"); } @@ -3274,7 +3614,8 @@ async fn soap_text_xml_valid_request_passes_endpoint_validation_and_proxies() { .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); - assert_eq!(response.headers()[header::CONTENT_TYPE], "text/xml"); + // The pinned SOAP gateway always answers application/xml. + assert_eq!(response.headers()[header::CONTENT_TYPE], "application/xml"); assert_eq!(to_bytes(response.into_body(), 1024).await.unwrap(), ""); upstream.abort(); } @@ -3440,7 +3781,7 @@ async fn strict_wildcard_cors_allows_localhost_like_python() { } #[tokio::test] -async fn proto_upload_rejects_traversal_filename() { +async fn proto_upload_ignores_the_client_filename_like_python() { let app = build_router(memory_state(false).await); let (cookie, _) = login(&app).await; let boundary = "doorman-proto-boundary"; @@ -3462,13 +3803,18 @@ async fn proto_upload_rejects_traversal_filename() { ) .await .unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - assert_eq!(response_json(response).await["error_code"], "REQ002"); + assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response_json(response).await["message"], + "Proto file uploaded and gRPC code generated successfully" + ); } #[tokio::test] async fn proto_upload_extension_acceptance_matches_python_contract() { - let app = build_router(memory_state(false).await); + let state = memory_state(false).await; + let storage = state.storage.clone().unwrap(); + let app = build_router(state); let (cookie, _) = login(&app).await; let boundary = "doorman-proto-extension-boundary"; let multipart = |filename: &str, content_type: &str, content: &str| { @@ -3527,6 +3873,15 @@ async fn proto_upload_extension_acceptance_matches_python_contract() { .to_ascii_lowercase() .starts_with("proto file uploaded") ); + let pending = storage + .find_one( + "grpc_proto_uploads", + &json!({"api_name": "sample", "api_version": "v1"}), + ) + .await + .unwrap() + .unwrap(); + assert_eq!(pending["api_grpc_package"], "sample_v1"); let updated = app .clone() @@ -3549,6 +3904,10 @@ async fn proto_upload_extension_acceptance_matches_python_contract() { .await .unwrap(); assert_eq!(updated.status(), StatusCode::OK); + assert_eq!( + response_json(updated).await, + json!({"message": "Proto file updated successfully"}) + ); let fetched = app .clone() .oneshot( @@ -3561,12 +3920,9 @@ async fn proto_upload_extension_acceptance_matches_python_contract() { .await .unwrap(); assert_eq!(fetched.status(), StatusCode::OK); - assert!( - response_json(fetched).await["content"] - .as_str() - .unwrap() - .contains("Pong") - ); + let fetched = response_json(fetched).await; + assert!(fetched.get("message").is_none()); + assert!(fetched["content"].as_str().unwrap().contains("Pong")); let deleted = app .clone() .oneshot( @@ -3580,6 +3936,10 @@ async fn proto_upload_extension_acceptance_matches_python_contract() { .await .unwrap(); assert_eq!(deleted.status(), StatusCode::OK); + assert_eq!( + response_json(deleted).await, + json!({"message": "Proto file and generated files deleted successfully"}) + ); for path in [ "/platform/proto/sample/v1", "/platform/proto/doesnotexist/v9", @@ -3596,25 +3956,77 @@ async fn proto_upload_extension_acceptance_matches_python_contract() { .await .unwrap(); assert_eq!(missing.status(), StatusCode::NOT_FOUND, "{path}"); + assert_eq!( + response_json(missing).await["error_code"], + "API002", + "{path}" + ); } } #[tokio::test] -async fn api_creation_attaches_proto_uploaded_before_the_api() { +async fn proto_content_validation_errors_match_python() { let app = build_router(memory_state(false).await); let (cookie, _) = login(&app).await; - let source = "syntax = \"proto3\"; message Hello { string name = 1; }"; - let uploaded = app - .clone() - .oneshot( - Request::builder() - .method(Method::POST) - .uri("/platform/proto/proto-before-api/v1") - .header(header::COOKIE, &cookie) - .header(header::CONTENT_TYPE, "text/plain") - .body(Body::from(source)) - .unwrap(), - ) + let cases = [ + ( + "missing", + b"edition = 2023;".to_vec(), + "Invalid proto file: Invalid proto file: missing proto syntax (syntax/message/service)", + ), + ( + "binary", + b"syntax = \"proto3\";\0".to_vec(), + "Invalid proto file: Invalid proto file: binary content detected", + ), + ( + "utf8", + vec![b's', b'y', b'n', b't', b'a', b'x', 0xff], + "Invalid proto file: Invalid proto file: not valid UTF-8", + ), + ( + "suspicious", + b"syntax = \"proto3\"; curl example.test".to_vec(), + "Invalid proto file: Invalid proto file: suspicious content detected", + ), + ]; + for (name, content, expected) in cases { + let response = app + .clone() + .oneshot( + Request::builder() + .method(Method::POST) + .uri(format!("/platform/proto/{name}/v1")) + .header(header::COOKIE, &cookie) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(&content))) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST, "{name}"); + let body = response_json(response).await; + assert_eq!(body["error_code"], "REQ003", "{name}"); + assert_eq!(body["error_message"], expected, "{name}"); + } +} + +#[tokio::test] +async fn api_creation_attaches_proto_uploaded_before_the_api() { + let app = build_router(memory_state(false).await); + let (cookie, _) = login(&app).await; + let source = "syntax = \"proto3\"; message Hello { string name = 1; }"; + let uploaded = app + .clone() + .oneshot( + Request::builder() + .method(Method::POST) + .uri("/platform/proto/proto-before-api/v1") + .header(header::COOKIE, &cookie) + .header(header::CONTENT_TYPE, PROTO_MULTIPART_CONTENT_TYPE) + .body(Body::from(proto_multipart(source.as_bytes()))) + .unwrap(), + ) .await .unwrap(); assert_eq!(uploaded.status(), StatusCode::OK); @@ -3683,6 +4095,36 @@ async fn descriptor_backfill_compiles_active_grpc_apis_missing_descriptors() { ) .await .unwrap(); + storage + .insert_one( + "apis", + json!({ + "api_name": "source-only", "api_version": "v1", "active": true, + "api_grpc_proto_source": "syntax = \"proto3\"; message SourceOnly {}" + }), + ) + .await + .unwrap(); + storage + .insert_one( + "apis", + json!({ + "api_name": "package-only", "api_version": "v1", "active": true, + "api_grpc_package": "package_only" + }), + ) + .await + .unwrap(); + storage + .insert_one( + "apis", + json!({ + "api_version": "v1", "active": true, + "api_grpc_proto_source": "syntax = \"proto3\"; message MissingName {}" + }), + ) + .await + .unwrap(); let app = build_router(state); let (cookie, _) = login(&app).await; let response = platform_request( @@ -3696,10 +4138,14 @@ async fn descriptor_backfill_compiles_active_grpc_apis_missing_descriptors() { .await; assert_eq!(response.status(), StatusCode::OK); let result = response_json(response).await; - assert_eq!(result["scanned"], 2); - assert_eq!(result["updated"], 1); + assert_eq!(result["scanned"], 5); + assert_eq!(result["updated"], 2); assert_eq!(result["skipped"], 1); - assert_eq!(result["missing"], 0); + assert_eq!(result["missing"], 2); + assert_eq!(result["errors"][0]["api"], "package-only/v1"); + assert_eq!(result["errors"][0]["error"], "Proto source not found"); + assert_eq!(result["errors"][1]["api"], "/v1"); + assert_eq!(result["errors"][1]["error"], "API name/version is missing"); let orders = storage .find_one("apis", &json!({"api_name": "orders", "api_version": "v1"})) .await @@ -3807,7 +4253,11 @@ async fn limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validatio "/platform/endpoint/endpoint/validation", Some(&cookie), None, - Some(json!({"endpoint_id": "missing", "validation_enabled": true})), + Some(json!({ + "endpoint_id": "missing", + "validation_enabled": true, + "validation_schema": {"validation_schema": {}} + })), ) .await; assert_eq!(validation.status(), StatusCode::FORBIDDEN); @@ -3938,10 +4388,10 @@ async fn proto_retrieval_requires_manage_apis_permission() { .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); let body = response_json(response).await; - assert_eq!(body["error_code"], "API008"); + assert_eq!(body["error_code"], "AUTH001"); assert_eq!( body["error_message"], - "You do not have permission to manage proto files" + "User does not have permission to manage APIs" ); } @@ -3996,6 +4446,30 @@ async fn platform_security_headers_csp_hsts_and_request_ids_match_python() { assert!(secure.headers().contains_key("strict-transport-security")); assert_eq!(secure.headers()["x-request-id"], "python-request-id"); assert_eq!(secure.headers()["request_id"], "python-request-id"); + + let docs_app = build_router(memory_state(false).await); + let (cookie, _) = login(&docs_app).await; + let docs = platform_request( + &docs_app, + Method::GET, + "/platform/docs", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(docs.status(), StatusCode::OK); + assert!( + !docs.headers().contains_key("x-frame-options"), + "{:?}", + docs.headers() + ); + assert!( + docs.headers()["content-security-policy"] + .to_str() + .unwrap() + .contains("frame-ancestors *") + ); } #[tokio::test] @@ -4107,7 +4581,8 @@ async fn config_reload_routes_preserve_legacy_values_metadata_and_permissions() ) .await .unwrap(); - assert_eq!(keys.status(), StatusCode::FORBIDDEN); + // The pinned route only requires authentication. + assert_eq!(keys.status(), StatusCode::OK); let keys = app .clone() @@ -4289,7 +4764,7 @@ async fn api_create_and_update_preserve_python_pydantic_and_duplicate_contracts( assert_eq!(invalid.status(), StatusCode::UNPROCESSABLE_ENTITY); let invalid: Value = serde_json::from_slice(&to_bytes(invalid.into_body(), 64 * 1024).await.unwrap()).unwrap(); - assert_eq!(invalid["detail"].as_array().unwrap().len(), 2); + assert_eq!(invalid["error_code"], "VAL001"); let empty_update = app .clone() @@ -4407,8 +4882,10 @@ async fn management_permissions_readiness_tools_and_restart_preserve_contract() let admin_readiness: Value = serde_json::from_slice(&to_bytes(admin_readiness.into_body(), 4096).await.unwrap()) .unwrap(); + assert_eq!(admin_readiness.as_object().unwrap().len(), 8); assert!(admin_readiness.get("mongodb").is_some()); assert!(admin_readiness.get("cache_backend").is_some()); + assert!(admin_readiness.get("tls_certificates_valid").is_some()); for (method, path, code) in [ ("GET", "/platform/security/settings", "SEC001"), @@ -4416,7 +4893,6 @@ async fn management_permissions_readiness_tools_and_restart_preserve_contract() ("GET", "/platform/monitor/report", "MON002"), ("GET", "/platform/analytics/timeseries", "ANALYTICS001"), ("GET", "/platform/analytics/top-apis", "ANALYTICS001"), - ("GET", "/platform/dashboard", "ANALYTICS001"), ("POST", "/platform/tools/rate-limit-simulator", "RATE001"), ("GET", "/platform/openapi.json", "API008"), ("GET", "/platform/docs", "API008"), @@ -4536,6 +5012,77 @@ async fn management_permissions_readiness_tools_and_restart_preserve_contract() assert_eq!(restart["error_code"], "SEC004"); } +#[tokio::test] +async fn tools_permissions_and_required_models_match_python() { + let (gateway_app, gateway_cookie) = + config_permission_app(Some("manage_gateway"), "tools-gateway").await; + let chaos = platform_request( + &gateway_app, + Method::POST, + "/platform/tools/chaos/toggle", + Some(&gateway_cookie), + None, + Some(json!({"backend": "mongo", "enabled": false})), + ) + .await; + assert_eq!(chaos.status(), StatusCode::OK); + + let cors_denied = platform_request( + &gateway_app, + Method::POST, + "/platform/tools/cors/check", + Some(&gateway_cookie), + None, + Some(json!({"origin": "http://localhost:3000", "method": "GET"})), + ) + .await; + assert_eq!(cors_denied.status(), StatusCode::FORBIDDEN); + + let missing = platform_request( + &gateway_app, + Method::POST, + "/platform/tools/chaos/toggle", + Some(&gateway_cookie), + None, + Some(json!({})), + ) + .await; + assert_eq!(missing.status(), StatusCode::UNPROCESSABLE_ENTITY); + let missing: Value = + serde_json::from_slice(&to_bytes(missing.into_body(), 4096).await.unwrap()).unwrap(); + assert_eq!(missing["error_code"], "VAL001"); + + let (security_app, security_cookie) = + config_permission_app(Some("manage_security"), "tools-security").await; + let cors = platform_request( + &security_app, + Method::POST, + "/platform/tools/cors/check", + Some(&security_cookie), + None, + Some(json!({"origin": "http://not-allowed.example", "method": "GET"})), + ) + .await; + assert_eq!(cors.status(), StatusCode::OK); + let cors: Value = + serde_json::from_slice(&to_bytes(cors.into_body(), 16 * 1024).await.unwrap()).unwrap(); + assert_eq!( + cors["preflight"]["response_headers"]["Access-Control-Allow-Origin"], + Value::Null + ); + + let chaos_denied = platform_request( + &security_app, + Method::POST, + "/platform/tools/chaos/toggle", + Some(&security_cookie), + None, + Some(json!({"backend": "redis", "enabled": true})), + ) + .await; + assert_eq!(chaos_denied.status(), StatusCode::FORBIDDEN); +} + #[tokio::test] async fn readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor() { let state = memory_state(false).await; @@ -4565,7 +5112,7 @@ async fn readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor() { None, ) .await; - assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE); + assert_eq!(response.status(), StatusCode::OK); let readiness = response_json(response).await; assert_eq!(readiness["status"], "degraded"); assert_eq!(readiness["missing_grpc_descriptors"], 1); @@ -4576,7 +5123,7 @@ async fn readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor() { } #[tokio::test] -async fn readiness_degrades_when_a_background_persistence_task_is_unhealthy() { +async fn readiness_matches_python_and_ignores_v2_background_task_health() { let state = memory_state(false).await; state .runtime @@ -4594,10 +5141,10 @@ async fn readiness_degrades_when_a_background_persistence_task_is_unhealthy() { None, ) .await; - assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE); + assert_eq!(response.status(), StatusCode::OK); let readiness = response_json(response).await; - assert_eq!(readiness["status"], "degraded"); - assert_eq!(readiness["metrics_persistence_healthy"], false); + assert_eq!(readiness["status"], "ready"); + assert!(readiness.get("metrics_persistence_healthy").is_none()); } #[tokio::test] @@ -4708,6 +5255,37 @@ async fn analytics_routes_preserve_python_v2_response_contracts() { assert!(top["top_apis"][0]["api"].is_string()); assert!(top["total_apis"].is_number()); + for (path, field, kind) in [ + ( + "/platform/analytics/top-users?limit=0", + "limit", + "value_error.number.not_ge", + ), + ( + "/platform/analytics/overview?start_ts=not-an-int", + "start_ts", + "type_error.integer", + ), + ] { + let invalid = app + .clone() + .oneshot( + Request::builder() + .uri(path) + .header(header::COOKIE, &cookie) + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(invalid.status(), StatusCode::UNPROCESSABLE_ENTITY); + let invalid: Value = + serde_json::from_slice(&to_bytes(invalid.into_body(), 16 * 1024).await.unwrap()) + .unwrap(); + // The pinned global handler renders every validation failure as VAL001. + assert_eq!(invalid["error_code"], "VAL001", "{field} {kind}"); + } + let detail = app .oneshot( Request::builder() @@ -4817,8 +5395,18 @@ async fn python_api_disabled_blocks_rest_graphql_grpc_and_soap() { .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN, "{path}"); - let body = response_json(response).await; - assert_eq!(body["error_code"], "GTW012", "{path}"); + let bytes = to_bytes(response.into_body(), 16 * 1024).await.unwrap(); + if path.starts_with("/api/soap/") { + // The pinned SOAP gateway renders its errors as XML. + let text = String::from_utf8(bytes.to_vec()).unwrap(); + assert!( + text.contains("GTW012"), + "{path}: {text}" + ); + } else { + let body: Value = serde_json::from_slice(&bytes).unwrap(); + assert_eq!(body["error_code"], "GTW012", "{path}"); + } } } #[tokio::test] @@ -5503,10 +6091,24 @@ async fn python_endpoint_failure_and_validation_crud_contracts() { None, ) .await; - assert!(matches!( - missing.status(), - StatusCode::BAD_REQUEST | StatusCode::NOT_FOUND - )); + assert_eq!(missing.status(), StatusCode::BAD_REQUEST); + let missing = response_json(missing).await; + assert_eq!(missing["error_code"], "END004"); + assert_eq!( + missing["error_message"], + "Endpoint does not exist for the requested API name, version and URI" + ); + let empty = platform_request( + &app, + Method::GET, + "/platform/endpoint/na/v1", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(empty.status(), StatusCode::BAD_REQUEST); + assert_eq!(response_json(empty).await["error_code"], "END005"); let api = platform_request(&app, Method::POST, "/platform/api", Some(&cookie), None, Some(json!({"api_name": "valapi", "api_version": "v1", "api_description": "validation api", "api_allowed_roles": ["admin"], "api_allowed_groups": ["ALL"], "api_servers": ["http://127.0.0.1:9"], "api_type": "REST", "active": true}))).await; assert!(api.status().is_success()); let endpoint = platform_request(&app, Method::POST, "/platform/endpoint", Some(&cookie), None, Some(json!({"api_name": "valapi", "api_version": "v1", "endpoint_method": "POST", "endpoint_uri": "/payload", "endpoint_description": "payload"}))).await; @@ -5525,8 +6127,51 @@ async fn python_endpoint_failure_and_validation_crud_contracts() { .as_str() .unwrap() .to_owned(); - let schema = json!({"validation_schema": {"id": {"required": true, "type": "string"}}}); - let validation = json!({"endpoint_id": endpoint_id.clone(), "validation_enabled": true, "validation_schema": schema}); + let schema = json!({"validation_schema": {"id": { + "required": 1, + "type": "string", + "min": "2", + "nested_schema": null, + "ignored": "field" + }}}); + let validation = json!({ + "endpoint_id": endpoint_id.clone(), + "validation_enabled": "yes", + "validation_schema": schema, + "ignored": "field" + }); + let missing_endpoint = platform_request( + &app, + Method::POST, + "/platform/endpoint/validation", + Some(&cookie), + None, + Some(json!({ + "endpoint_id": "missing-endpoint", + "validation_enabled": true, + "validation_schema": validation["validation_schema"].clone() + })), + ) + .await; + assert_eq!(missing_endpoint.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response_json(missing_endpoint).await["error_code"], + "END015" + ); + let missing_validation = platform_request( + &app, + Method::GET, + "/platform/endpoint/endpoint/validation/missing-endpoint", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(missing_validation.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response_json(missing_validation).await["error_code"], + "END018" + ); let created = platform_request( &app, Method::POST, @@ -5537,6 +6182,17 @@ async fn python_endpoint_failure_and_validation_crud_contracts() { ) .await; assert!(created.status().is_success()); + let duplicate = platform_request( + &app, + Method::POST, + "/platform/endpoint/validation", + Some(&cookie), + None, + Some(validation.clone()), + ) + .await; + assert_eq!(duplicate.status(), StatusCode::BAD_REQUEST); + assert_eq!(response_json(duplicate).await["error_code"], "END017"); let fetched = platform_request( &app, Method::GET, @@ -5547,6 +6203,15 @@ async fn python_endpoint_failure_and_validation_crud_contracts() { ) .await; assert_eq!(fetched.status(), StatusCode::OK); + let fetched = response_json(fetched).await; + assert_eq!(fetched["validation_enabled"], true); + let id_rules = &fetched["validation_schema"]["validation_schema"]["id"]; + assert_eq!(id_rules["required"], true); + assert_eq!(id_rules["min"], 2); + assert_eq!(id_rules["max"], Value::Null); + assert_eq!(id_rules["pattern"], Value::Null); + assert!(id_rules.get("ignored").is_none()); + assert!(fetched.get("ignored").is_none()); let updated = platform_request( &app, Method::PUT, @@ -5557,6 +6222,47 @@ async fn python_endpoint_failure_and_validation_crud_contracts() { ) .await; assert_eq!(updated.status(), StatusCode::OK); + let disabled = platform_request( + &app, + Method::PUT, + &format!("/platform/endpoint/validation/{endpoint_id}"), + Some(&cookie), + None, + Some(json!({ + "validation_enabled": false, + "validation_schema": validation["validation_schema"].clone() + })), + ) + .await; + assert_eq!(disabled.status(), StatusCode::BAD_REQUEST); + assert_eq!(response_json(disabled).await["error_code"], "END020"); + let missing_endpoint_update = platform_request( + &app, + Method::PUT, + "/platform/endpoint/validation/missing-endpoint", + Some(&cookie), + None, + Some(json!({ + "validation_enabled": true, + "validation_schema": validation["validation_schema"].clone() + })), + ) + .await; + assert_eq!(missing_endpoint_update.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response_json(missing_endpoint_update).await["error_code"], + "END022" + ); + let invalid = platform_request( + &app, + Method::PUT, + &format!("/platform/endpoint/endpoint/validation/{endpoint_id}"), + Some(&cookie), + None, + Some(json!({"validation_enabled": true})), + ) + .await; + assert_eq!(invalid.status(), StatusCode::UNPROCESSABLE_ENTITY); let deleted = platform_request( &app, Method::DELETE, @@ -5567,6 +6273,16 @@ async fn python_endpoint_failure_and_validation_crud_contracts() { ) .await; assert_eq!(deleted.status(), StatusCode::OK); + let deleted_again = platform_request( + &app, + Method::DELETE, + &format!("/platform/endpoint/validation/{endpoint_id}"), + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(deleted_again.status(), StatusCode::OK); } #[tokio::test] async fn python_config_export_sections_and_import_variants() { @@ -5620,6 +6336,13 @@ async fn python_config_export_sections_and_import_variants() { .iter() .any(|endpoint| endpoint["endpoint_uri"] == "/x") ); + assert!( + endpoints["endpoints"] + .as_array() + .unwrap() + .iter() + .all(|endpoint| endpoint["api_name"] == "filterapi" && endpoint["api_version"] == "v1") + ); for section in [ json!({"apis": []}), json!({"roles": []}), @@ -5711,13 +6434,14 @@ async fn python_config_permissions_granular_export_and_gateway_import_contracts( .await; assert_eq!(import.status(), StatusCode::OK); let (limited_app, limited_cookie) = config_permission_app(None, "config-limited").await; - for (method, path, payload) in [ - (Method::GET, "/platform/config/export/apis", None), - (Method::GET, "/platform/config/export/all", None), + for (method, path, payload, code) in [ + (Method::GET, "/platform/config/export/apis", None, "CFG002"), + (Method::GET, "/platform/config/export/all", None, "CFG001"), ( Method::POST, "/platform/config/import", Some(json!({"apis": []})), + "CFG006", ), ] { let response = platform_request( @@ -5730,6 +6454,7 @@ async fn python_config_permissions_granular_export_and_gateway_import_contracts( ) .await; assert_eq!(response.status(), StatusCode::FORBIDDEN, "{path}"); + assert_eq!(response_json(response).await["error_code"], code, "{path}"); } } @@ -5737,31 +6462,99 @@ async fn python_config_permissions_granular_export_and_gateway_import_contracts( async fn config_export_missing_named_resources_return_python_404() { let app = build_router(memory_state(false).await); let (cookie, _) = login(&app).await; - for path in [ - "/platform/config/export/apis?api_name=nope&api_version=v9", - "/platform/config/export/roles?role_name=nope-role", - "/platform/config/export/groups?group_name=nope-group", - "/platform/config/export/routings?client_key=nope-key", + let role = platform_request( + &app, + Method::GET, + "/platform/config/export/roles?role_name=admin", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(role.status(), StatusCode::OK); + let role = response_json(role).await; + assert_eq!(role["role"]["role_name"], "admin"); + assert!(role.get("roles").is_none()); + for (path, code, message) in [ + ( + "/platform/config/export/apis?api_name=nope&api_version=v9", + "CFG404", + "API not found", + ), + ( + "/platform/config/export/roles?role_name=nope-role", + "CFG405", + "Role not found", + ), + ( + "/platform/config/export/groups?group_name=nope-group", + "CFG406", + "Group not found", + ), + ( + "/platform/config/export/routings?client_key=nope-key", + "CFG407", + "Routing not found", + ), ] { let response = platform_request(&app, Method::GET, path, Some(&cookie), None, None).await; assert_eq!(response.status(), StatusCode::NOT_FOUND, "{path}"); + let response = response_json(response).await; + assert_eq!(response["error_code"], code, "{path}"); + assert_eq!(response["error_message"], message, "{path}"); } } #[tokio::test] -async fn python_config_import_ignores_malformed_entries() { - let state = memory_state(false).await; - let storage = state.storage.as_ref().unwrap().clone(); - let mut before = std::collections::HashMap::new(); - for collection in ["apis", "endpoints", "roles", "groups", "routings"] { - before.insert( - collection, - storage.find_many(collection, &json!({})).await.unwrap(), - ); - } - let app = build_router(state); - let (cookie, _) = login(&app).await; - let payload = json!({"apis": [{"api_name": "x-only"}, {"api_version": "v1"}], "endpoints": [{"api_name": "x", "endpoint_method": "GET"}], "roles": [{"bad": "doc"}], "groups": [{"bad": "doc"}], "routings": [{"bad": "doc"}]}); +async fn config_rollback_preserves_python_permission_and_missing_snapshot_quirks() { + let (limited_app, limited_cookie) = config_permission_app(None, "rollback-limited").await; + let denied = platform_request( + &limited_app, + Method::POST, + "/platform/config/rollback", + Some(&limited_cookie), + None, + Some(json!({})), + ) + .await; + assert_eq!(denied.status(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!( + response_json(denied).await, + json!({"detail": "Rollback failed"}) + ); + + let app = build_router(memory_state(false).await); + let (cookie, _) = login(&app).await; + let missing = platform_request( + &app, + Method::POST, + "/platform/config/rollback", + Some(&cookie), + None, + Some(json!({})), + ) + .await; + assert_eq!(missing.status(), StatusCode::NOT_FOUND); + assert_eq!( + response_json(missing).await, + json!({"error_code": "CFG404", "error_message": "No snapshot found"}) + ); +} + +#[tokio::test] +async fn python_config_import_ignores_malformed_entries() { + let state = memory_state(false).await; + let storage = state.storage.as_ref().unwrap().clone(); + let mut before = std::collections::HashMap::new(); + for collection in ["apis", "endpoints", "roles", "groups", "routings"] { + before.insert( + collection, + storage.find_many(collection, &json!({})).await.unwrap(), + ); + } + let app = build_router(state); + let (cookie, _) = login(&app).await; + let payload = json!({"apis": [{"api_name": "x-only"}, {"api_version": "v1"}], "endpoints": [{"api_name": "x", "endpoint_method": "GET"}], "roles": [{"bad": "doc"}], "groups": [{"bad": "doc"}], "routings": [{"bad": "doc"}]}); let response = platform_request( &app, Method::POST, @@ -5779,57 +6572,117 @@ async fn python_config_import_ignores_malformed_entries() { "{collection}" ); } -} + let snapshots = storage + .find_many("config_snapshots", &json!({})) + .await + .unwrap(); + assert_eq!(snapshots.len(), 1); + assert_eq!(snapshots[0]["created_by"], "admin"); + assert!(snapshots[0]["timestamp"].as_str().unwrap().contains(' ')); + assert!(snapshots[0].get("created_at").is_none()); -#[tokio::test] -async fn api_create_update_delete_emit_named_audit_events() { - let _trace_guard = trace_capture_guard().await; - let app = build_router(memory_state(false).await); - let (cookie, _) = login(&app).await; - let capture = CapturedTrace::default(); - let subscriber = tracing_subscriber::fmt() - .json() - .with_writer(capture.clone()) - .finish(); - let dispatch = tracing::Dispatch::new(subscriber); - let create = platform_request( + let falsy_sections = platform_request( &app, Method::POST, - "/platform/api", + "/platform/config/import", Some(&cookie), None, Some(json!({ - "api_name": "audit-api", "api_version": "v1", "api_type": "REST", - "api_allowed_roles": ["admin"], "api_allowed_groups": ["ALL"], - "api_servers": ["http://127.0.0.1:9"], "active": true + "apis": "", "endpoints": [], "roles": {}, "groups": false, "routings": 0 })), ) - .with_subscriber(dispatch.clone()) .await; - assert_eq!(create.status(), StatusCode::CREATED); - let update = platform_request( - &app, - Method::PUT, - "/platform/api/audit-api/v1", - Some(&cookie), - None, - Some(json!({"api_description": "updated"})), - ) - .with_subscriber(dispatch.clone()) - .await; - assert_eq!(update.status(), StatusCode::OK); - let delete = platform_request( + assert_eq!(falsy_sections.status(), StatusCode::OK); + assert_eq!( + response_json(falsy_sections).await["imported"], + json!({"apis": 0, "endpoints": 0, "roles": 0, "groups": 0, "routings": 0}) + ); + + let truthy_non_list = platform_request( &app, - Method::DELETE, - "/platform/api/audit-api/v1", + Method::POST, + "/platform/config/import", Some(&cookie), None, - None, + Some(json!({"apis": "invalid"})), ) - .with_subscriber(dispatch) .await; - assert_eq!(delete.status(), StatusCode::OK); - let events = capture.text(); + assert_eq!(truthy_non_list.status(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(response_json(truthy_non_list).await["error_code"], "GTW999"); + assert_eq!( + storage + .find_many("config_snapshots", &json!({})) + .await + .unwrap() + .len(), + 3 + ); +} + +#[tokio::test] +async fn api_create_update_delete_emit_named_audit_events() { + let _trace_guard = trace_capture_guard().await; + let app = build_router(memory_state(false).await); + let (cookie, _) = login(&app).await; + // Tracing's per-callsite interest is process-global and can be recomputed + // by tests running in parallel, which occasionally hides a scoped + // subscriber's events; the audit events themselves are deterministic, so + // repeat the sequence with a fresh API before judging. + let mut events = String::new(); + for attempt in 0..3 { + let capture = CapturedTrace::default(); + let subscriber = tracing_subscriber::fmt() + .json() + .with_writer(capture.clone()) + .finish(); + let dispatch = tracing::Dispatch::new(subscriber); + tracing::callsite::rebuild_interest_cache(); + let name = format!("audit-api-{attempt}"); + let create = platform_request( + &app, + Method::POST, + "/platform/api", + Some(&cookie), + None, + Some(json!({ + "api_name": name, "api_version": "v1", "api_type": "REST", + "api_allowed_roles": ["admin"], "api_allowed_groups": ["ALL"], + "api_servers": ["http://127.0.0.1:9"], "active": true + })), + ) + .with_subscriber(dispatch.clone()) + .await; + assert_eq!(create.status(), StatusCode::CREATED); + let update = platform_request( + &app, + Method::PUT, + &format!("/platform/api/{name}/v1"), + Some(&cookie), + None, + Some(json!({"api_description": "updated"})), + ) + .with_subscriber(dispatch.clone()) + .await; + assert_eq!(update.status(), StatusCode::OK); + let delete = platform_request( + &app, + Method::DELETE, + &format!("/platform/api/{name}/v1"), + Some(&cookie), + None, + None, + ) + .with_subscriber(dispatch) + .await; + assert_eq!(delete.status(), StatusCode::OK); + events = capture.text(); + if ["api.create", "api.update", "api.delete"] + .iter() + .all(|action| events.contains(action)) + { + break; + } + } for action in ["api.create", "api.update", "api.delete"] { assert!(events.contains(action), "missing {action}: {events}"); } @@ -5934,6 +6787,194 @@ async fn python_credit_definition_masks_secret_key_material() { assert_eq!(body["api_key_header"], "x-api-key"); assert_eq!(body["api_key_present"], true); assert!(body.get("api_key").is_none()); + let list = platform_request( + &app, + Method::GET, + "/platform/credit/defs", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(list.status(), StatusCode::OK); + let list = response_json(list).await; + assert_eq!(list["page"], 1); + assert_eq!(list["page_size"], 50); + assert_eq!(list["total"], 1); + assert_eq!(list["items"][0]["api_credit_group"], "maskgroup"); + let missing = platform_request( + &app, + Method::GET, + "/platform/credit/defs/missing", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(missing.status(), StatusCode::NOT_FOUND); + assert_eq!(response_json(missing).await["error_code"], "CRD021"); +} + +#[tokio::test] +async fn tls_bindings_can_be_cleared_without_changing_other_api_fields() { + let state = memory_state(false).await; + let storage = state.storage.as_ref().unwrap().clone(); + storage.insert_one("apis", json!({ + "api_name": "mtls-clear", "api_version": "v1", "api_description": "keep", + "api_client_tls_policy": {"mode": "required", "ca_profile_id": "retired", "allowed_dns_sans": ["client.example"]}, + "api_upstream_tls_profile": "retired" + })).await.unwrap(); + storage + .insert_one( + "endpoints", + json!({ + "api_name": "mtls-clear", "api_version": "v1", "endpoint_method": "GET", + "endpoint_uri": "/probe", "endpoint_description": "keep", + "endpoint_client_tls_policy": {"mode": "off"}, + "endpoint_upstream_tls_profile": "retired" + }), + ) + .await + .unwrap(); + let app = build_router(state); + let (cookie, _) = login(&app).await; + + let api = platform_request( + &app, + Method::PUT, + "/platform/api/mtls-clear/v1", + Some(&cookie), + None, + Some(json!({"api_client_tls_policy": null, "api_upstream_tls_profile": null})), + ) + .await; + assert_eq!(api.status(), StatusCode::OK); + let endpoint = platform_request( + &app, + Method::PUT, + "/platform/endpoint/GET/mtls-clear/v1/probe", + Some(&cookie), + None, + Some(json!({"endpoint_client_tls_policy": null, "endpoint_upstream_tls_profile": null})), + ) + .await; + assert_eq!(endpoint.status(), StatusCode::OK); + + let api = storage + .find_one( + "apis", + &json!({"api_name": "mtls-clear", "api_version": "v1"}), + ) + .await + .unwrap() + .unwrap(); + let endpoint = storage.find_one("endpoints", &json!({"api_name": "mtls-clear", "api_version": "v1", "endpoint_method": "GET", "endpoint_uri": "/probe"})).await.unwrap().unwrap(); + assert!(api["api_client_tls_policy"].is_null()); + assert!(api["api_upstream_tls_profile"].is_null()); + assert_eq!(api["api_description"], "keep"); + assert!(endpoint["endpoint_client_tls_policy"].is_null()); + assert!(endpoint["endpoint_upstream_tls_profile"].is_null()); + assert_eq!(endpoint["endpoint_description"], "keep"); +} + +#[tokio::test] +async fn credit_rotation_expiry_matches_pydantic_datetime_validation() { + let state = memory_state(false).await; + let storage = state.storage.as_ref().unwrap().clone(); + let app = build_router(state); + let (cookie, _) = login(&app).await; + let credit = |expires: Value| { + json!({ + "api_credit_group": "expiry", "api_key": "key", "api_key_header": "x-api-key", + "credit_tiers": [{ + "tier_name": "default", "credits": 1, "input_limit": 0, + "output_limit": 0, "reset_frequency": "monthly" + }], + "api_key_rotation_expires": expires + }) + }; + + let invalid_create = platform_request( + &app, + Method::POST, + "/platform/credit", + Some(&cookie), + None, + Some(credit(json!("not a datetime"))), + ) + .await; + assert_eq!(invalid_create.status(), StatusCode::UNPROCESSABLE_ENTITY); + + let create = platform_request( + &app, + Method::POST, + "/platform/credit", + Some(&cookie), + None, + Some(credit(json!("2025-01-15T10:00:00Z"))), + ) + .await; + assert_eq!(create.status(), StatusCode::CREATED); + let stored = storage + .find_one("credit_defs", &json!({"api_credit_group": "expiry"})) + .await + .unwrap() + .unwrap(); + assert_eq!( + stored["api_key_rotation_expires"], + "2025-01-15T10:00:00+00:00" + ); + + let invalid_update = platform_request( + &app, + Method::PUT, + "/platform/credit/expiry", + Some(&cookie), + None, + Some(credit(json!("2025-99-99T10:00:00Z"))), + ) + .await; + assert_eq!(invalid_update.status(), StatusCode::UNPROCESSABLE_ENTITY); + let stored = storage + .find_one("credit_defs", &json!({"api_credit_group": "expiry"})) + .await + .unwrap() + .unwrap(); + assert_eq!( + stored["api_key_rotation_expires"], + "2025-01-15T10:00:00+00:00" + ); + + let update = platform_request( + &app, + Method::PUT, + "/platform/credit/expiry", + Some(&cookie), + None, + Some(credit(json!(1_700_000_000))), + ) + .await; + assert_eq!(update.status(), StatusCode::OK); + let stored = storage + .find_one("credit_defs", &json!({"api_credit_group": "expiry"})) + .await + .unwrap() + .unwrap(); + assert_eq!( + stored["api_key_rotation_expires"], + "2023-11-14T22:13:20+00:00" + ); + + let numeric_string = platform_request( + &app, + Method::PUT, + "/platform/credit/expiry", + Some(&cookie), + None, + Some(credit(json!(" 1700000000 "))), + ) + .await; + assert_eq!(numeric_string.status(), StatusCode::OK); } #[tokio::test] @@ -5951,10 +6992,16 @@ async fn credit_models_match_pydantic_required_fields_coercion_and_unknown_elisi Some(json!({ "api_credit_group": true, "api_key": false, "api_key_header": 7, "api_key_new": 9, - "credit_tiers": [{ - "tier_name": 1, "credits": "8", "input_limit": true, "output_limit": 3.9, - "reset_frequency": false, "ignored": "field" - }], "ignored": "field" + "credit_tiers": [ + { + "tier_name": 1, "credits": "8", "input_limit": true, "output_limit": 3.9, + "reset_frequency": false, "ignored": "field" + }, + { + "tier_name": "é".repeat(50), "credits": 1, "input_limit": 2, + "output_limit": 3, "reset_frequency": "monthly" + } + ], "ignored": "field" })), ) .await; @@ -5967,12 +7014,20 @@ async fn credit_models_match_pydantic_required_fields_coercion_and_unknown_elisi assert_eq!(credit["api_key"], "False"); assert_eq!(credit["api_key_header"], "7"); assert_eq!(credit["api_key_new"], "9"); + assert!(credit["api_key_rotation_expires"].is_null()); assert_eq!( - credit["credit_tiers"], - json!([{ + credit["credit_tiers"][0], + json!({ "tier_name": "1", "credits": 8, "input_limit": 1, "output_limit": 3, "reset_frequency": "False" - }]) + }) + ); + assert_eq!( + credit["credit_tiers"][1], + json!({ + "tier_name": "é".repeat(50), "credits": 1, "input_limit": 2, "output_limit": 3, + "reset_frequency": "monthly" + }) ); assert!(credit.get("ignored").is_none()); let duplicate = platform_request( @@ -6037,8 +7092,11 @@ async fn credit_models_match_pydantic_required_fields_coercion_and_unknown_elisi Some(&cookie), None, Some(json!({ - "username": true, - "users_credits": {"True": {"tier_name": 2, "available_credits": "4", "user_api_key": false, "ignored": "field"}}, + "username": "admin", + "users_credits": { + "True": {"tier_name": 2, "available_credits": "4", "user_api_key": false, "ignored": "field"}, + "unicode": {"tier_name": "é".repeat(50), "available_credits": 1} + }, "ignored": "field" })), ) @@ -6051,7 +7109,28 @@ async fn credit_models_match_pydantic_required_fields_coercion_and_unknown_elisi .unwrap(); assert_eq!( user_credits["users_credits"]["True"], - json!({"tier_name": "2", "available_credits": 4, "user_api_key": "False"}) + json!({"tier_name": "2", "available_credits": 4, "reset_date": null, "user_api_key": "False"}) + ); + assert_eq!( + user_credits["users_credits"]["unicode"], + json!({"tier_name": "é".repeat(50), "available_credits": 1, "reset_date": null, "user_api_key": null}) + ); + let mismatched_username = platform_request( + &app, + Method::POST, + "/platform/credit/admin", + Some(&cookie), + None, + Some(json!({ + "username": true, + "users_credits": {"group": {"tier_name": "tier", "available_credits": 1}} + })), + ) + .await; + assert_eq!(mismatched_username.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response_json(mismatched_username).await["error_code"], + "CRD014" ); let invalid_user_credits = platform_request( &app, @@ -6127,6 +7206,17 @@ async fn quota_status_uses_nested_effective_limits_and_tracker_usage() { response_json(no_limits).await["detail"], "No limits found for user" ); + let history = platform_request( + &no_tier_app, + Method::GET, + "/platform/quota/usage/history", + Some(&no_tier_cookie), + None, + None, + ) + .await; + assert_eq!(history.status(), StatusCode::OK); + assert_eq!(response_json(history).await["history"], json!([])); storage .insert_one( "tiers", @@ -6142,6 +7232,16 @@ async fn quota_status_uses_nested_effective_limits_and_tracker_usage() { ) .await .unwrap(); + storage + .insert_one( + "tiers", + json!({ + "tier_id": "quota-legacy", "name": "custom", "display_name": "Legacy", + "limits": {"monthly_request_quota": 1} + }), + ) + .await + .unwrap(); storage .insert_one( "tiers", @@ -6215,6 +7315,21 @@ async fn quota_status_uses_nested_effective_limits_and_tracker_usage() { assert_eq!(specific.status(), StatusCode::OK); assert_eq!(response_json(specific).await["current_usage"], 8); + let invalid = platform_request( + &app, + Method::GET, + "/platform/quota/status/not-a-quota", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(invalid.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response_json(invalid).await, + json!({"detail": "Invalid quota type: not-a-quota"}) + ); + let export = platform_request( &app, Method::POST, @@ -6246,6 +7361,22 @@ async fn quota_status_uses_nested_effective_limits_and_tracker_usage() { .contains("monthly_bandwidth") ); + let json_export = platform_request( + &app, + Method::POST, + "/platform/quota/usage/export", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(json_export.status(), StatusCode::OK); + let json_export = response_json(json_export).await; + assert_eq!(json_export["format"], "json"); + let export_date = json_export["data"]["export_date"].as_str().unwrap(); + assert!(export_date.contains('T')); + assert!(!export_date.ends_with('Z')); + let tier_info = platform_request( &app, Method::GET, @@ -6259,6 +7390,7 @@ async fn quota_status_uses_nested_effective_limits_and_tracker_usage() { let tier_info = response_json(tier_info).await; assert_eq!(tier_info["current_tier"]["display_name"], "Quota Pro"); assert_eq!(tier_info["upgrade_options"][0]["tier_id"], "quota-free"); + assert_eq!(tier_info["upgrade_options"].as_array().unwrap().len(), 1); let burst = platform_request( &app, @@ -6279,7 +7411,10 @@ async fn quota_status_uses_nested_effective_limits_and_tracker_usage() { .update_one( "user_tier_assignments", &json!({"user_id": "admin"}), - &json!({"effective_until": 0}), + &json!({ + "effective_until": 0, + "override_limits": {"monthly_request_quota": 99} + }), ) .await .unwrap(); @@ -6297,6 +7432,22 @@ async fn quota_status_uses_nested_effective_limits_and_tracker_usage() { response_json(expired_assignment).await["current_tier"]["tier_id"], "quota-free" ); + let expired_status = platform_request( + &app, + Method::GET, + "/platform/quota/status", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(expired_status.status(), StatusCode::OK); + let expired_status = response_json(expired_status).await; + assert_eq!(expired_status["tier_info"]["tier_id"], "quota-free"); + assert_eq!( + expired_status["tier_info"]["limits"]["monthly_request_quota"], + 99 + ); } #[tokio::test] @@ -6433,13 +7584,21 @@ async fn tier_assignment_create_returns_python_assignment_contract() { "/platform/tiers/compare", Some(&cookie), None, - Some(json!(["assignment-pro", "missing-tier"])), + Some(json!([ + "assignment-pro", + "assignment-default", + "assignment-pro", + "missing-tier" + ])), ) .await; assert_eq!(comparison.status(), StatusCode::OK); let comparison = response_json(comparison).await; assert!(comparison.is_array()); assert_eq!(comparison[0]["tier_id"], "assignment-pro"); + assert_eq!(comparison[1]["tier_id"], "assignment-default"); + assert_eq!(comparison[2]["tier_id"], "assignment-pro"); + assert_eq!(comparison.as_array().unwrap().len(), 3); assert!(comparison[0].get("enabled").is_none()); let removed = platform_request( @@ -6470,6 +7629,21 @@ async fn tier_assignment_create_returns_python_assignment_contract() { response_json(absent).await["detail"], "No assignment found for user assignment-user" ); + + let absent = platform_request( + &app, + Method::GET, + "/platform/tiers/assignments/assignment-user", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(absent.status(), StatusCode::NOT_FOUND); + assert_eq!( + response_json(absent).await["detail"], + "No assignment found for user assignment-user" + ); } #[tokio::test] @@ -6636,6 +7810,19 @@ async fn tier_users_and_statistics_match_python_shapes() { assert_eq!(users.as_array().unwrap().len(), 1); assert_eq!(users[0]["user_id"], "stats-future"); + for query in ["skip=-1", "limit=0", "limit=1001", "limit=invalid"] { + let invalid = platform_request( + &app, + Method::GET, + &format!("/platform/tiers/stats-pro/users?{query}"), + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(invalid.status(), StatusCode::UNPROCESSABLE_ENTITY); + } + let tier_stats = platform_request( &app, Method::GET, @@ -6776,6 +7963,43 @@ async fn tier_crud_returns_normalized_python_tier_contracts() { assert_eq!(created["enabled"], true); assert!(created["created_at"].as_str().unwrap().contains('T')); + // TierName construction happens inside the Python handler's broad + // exception fallback. Pydantic has already coerced the other scalars, and + // the fallback response is returned without persisting the invalid tier. + let fallback = platform_request( + &app, + Method::POST, + "/platform/tiers/", + Some(&cookie), + None, + Some(json!({ + "tier_id": 99, "name": "future-plan", "display_name": true, + "limits": {"requests_per_minute": "25", "enable_throttling": "yes"}, + "features": [1, false], "enabled": "off", "ignored": "field" + })), + ) + .await; + assert_eq!(fallback.status(), StatusCode::CREATED); + let fallback = response_json(fallback).await; + assert_eq!(fallback["tier_id"], "99"); + assert_eq!(fallback["display_name"], "True"); + assert_eq!(fallback["limits"]["requests_per_minute"], 25); + assert_eq!(fallback["limits"]["enable_throttling"], true); + assert_eq!(fallback["features"], json!(["1", "False"])); + assert_eq!(fallback["enabled"], false); + assert!(fallback["created_at"].is_null()); + + let not_persisted = platform_request( + &app, + Method::GET, + "/platform/tiers/99", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(not_persisted.status(), StatusCode::NOT_FOUND); + let duplicate = platform_request( &app, Method::POST, @@ -6808,7 +8032,7 @@ async fn tier_crud_returns_normalized_python_tier_contracts() { let listed = platform_request( &app, Method::GET, - "/platform/tiers/?enabled_only=true&skip=0&limit=1", + "/platform/tiers/?enabled_only=YES&skip=0&limit=1", Some(&cookie), None, None, @@ -6821,49 +8045,128 @@ async fn tier_crud_returns_normalized_python_tier_contracts() { assert_eq!(listed["page_size"], 1); assert_eq!(listed["tiers"][0]["tier_id"], "crud-pro"); - let search = platform_request( + let search = platform_request( + &app, + Method::GET, + "/platform/tiers/?search=crud", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(search.status(), StatusCode::INTERNAL_SERVER_ERROR); + let search = response_json(search).await; + assert_eq!(search["error_code"], "TIER999"); + assert_eq!(search["error_message"], "Failed to list tiers"); + + let updated = platform_request( + &app, + Method::PUT, + "/platform/tiers/crud-pro", + Some(&cookie), + None, + Some(json!({"display_name": "CRUD Pro Updated", "limits": {"requests_per_day": 10}})), + ) + .await; + assert_eq!(updated.status(), StatusCode::OK); + let updated = response_json(updated).await; + assert_eq!(updated["display_name"], "CRUD Pro Updated"); + assert_eq!(updated["limits"]["requests_per_day"], 10); + assert_eq!(updated["limits"]["burst_per_hour"], 0); + + let missing = platform_request( + &app, + Method::GET, + "/platform/tiers/missing-tier", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(missing.status(), StatusCode::NOT_FOUND); + assert_eq!( + response_json(missing).await["detail"], + "Tier missing-tier not found" + ); +} + +#[tokio::test] +async fn tier_public_and_protected_route_boundaries_match_python() { + let app = build_router(memory_state(false).await); + // Tier and rate-limit management now requires an authenticated manager. + let (cookie, _) = login(&app).await; + let created = platform_request( + &app, + Method::POST, + "/platform/tiers/", + Some(&cookie), + None, + Some(json!({ + "tier_id": "public-tier", "name": "custom", "display_name": "Public Tier", + "limits": {"requests_per_minute": 12} + })), + ) + .await; + assert_eq!(created.status(), StatusCode::CREATED); + + let fetched = platform_request( &app, Method::GET, - "/platform/tiers/?search=crud", + "/platform/tiers/public-tier", Some(&cookie), None, None, ) .await; - assert_eq!(search.status(), StatusCode::INTERNAL_SERVER_ERROR); - let search = response_json(search).await; - assert_eq!(search["error_code"], "TIER999"); - assert_eq!(search["error_message"], "Failed to list tiers"); + assert_eq!(fetched.status(), StatusCode::OK); let updated = platform_request( &app, Method::PUT, - "/platform/tiers/crud-pro", + "/platform/tiers/public-tier", Some(&cookie), None, - Some(json!({"display_name": "CRUD Pro Updated", "limits": {"requests_per_day": 10}})), + Some(json!({"display_name": "Public Tier Updated"})), ) .await; assert_eq!(updated.status(), StatusCode::OK); - let updated = response_json(updated).await; - assert_eq!(updated["display_name"], "CRUD Pro Updated"); - assert_eq!(updated["limits"]["requests_per_day"], 10); - assert_eq!(updated["limits"]["burst_per_hour"], 0); - let missing = platform_request( + let assigned = platform_request( + &app, + Method::POST, + "/platform/tiers/assignments", + Some(&cookie), + None, + Some(json!({"user_id": "public-user", "tier_id": "public-tier"})), + ) + .await; + assert_eq!(assigned.status(), StatusCode::CREATED); + let assignment = platform_request( &app, Method::GET, - "/platform/tiers/missing-tier", + "/platform/tiers/assignments/public-user", Some(&cookie), None, None, ) .await; - assert_eq!(missing.status(), StatusCode::NOT_FOUND); - assert_eq!( - response_json(missing).await["detail"], - "Tier missing-tier not found" - ); + assert_eq!(assignment.status(), StatusCode::OK); + + for path in ["/platform/tiers/", "/platform/tiers/statistics/all"] { + let protected = platform_request(&app, Method::GET, path, None, None, None).await; + assert_eq!(protected.status(), StatusCode::UNAUTHORIZED, "{path}"); + } + // Unlike the pinned server, anonymous callers cannot manage tiers (MIG-123). + let anonymous = platform_request( + &app, + Method::POST, + "/platform/tiers/", + None, + None, + Some(json!({"tier_id": "anonymous-tier", "name": "custom", "display_name": "Anonymous", "limits": {"requests_per_minute": 1}})), + ) + .await; + assert_eq!(anonymous.status(), StatusCode::UNAUTHORIZED); } #[tokio::test] @@ -6902,12 +8205,15 @@ async fn rate_limit_statistics_and_shadowed_status_match_python_contracts() { "rule_id": "disabled-ip", "rule_type": "per_ip", "time_window": "day", "limit": 1, "enabled": false }), + json!({ + "rule_id": "legacy-no-enabled", "rule_type": "global", "time_window": "day", "limit": 1 + }), ] { storage.insert_one("rate_limit_rules", rule).await.unwrap(); } let app = build_router(state); let (viewer_cookie, _) = login_as(&app, "rule-viewer@doorman.dev", fixture_password()).await; - let (_admin_cookie, _) = login(&app).await; + let (cookie, _) = login(&app).await; let status = platform_request( &app, @@ -6926,17 +8232,17 @@ async fn rate_limit_statistics_and_shadowed_status_match_python_contracts() { &app, Method::GET, "/platform/rate-limits/statistics/summary", - None, + Some(&cookie), None, None, ) .await; assert_eq!(statistics.status(), StatusCode::OK); let statistics = response_json(statistics).await; - assert_eq!(statistics["total_rules"], 4); + assert_eq!(statistics["total_rules"], 5); assert_eq!(statistics["enabled_rules"], 3); - assert_eq!(statistics["disabled_rules"], 1); - assert_eq!(statistics["rules_by_type"]["global"], 1); + assert_eq!(statistics["disabled_rules"], 2); + assert_eq!(statistics["rules_by_type"]["global"], 2); assert_eq!(statistics["rules_by_type"]["per_user"], 2); assert_eq!(statistics["rules_by_type"]["per_ip"], 1); assert_eq!(statistics["rules_by_type"]["per_api"], 0); @@ -7000,6 +8306,32 @@ async fn rate_limit_bulk_routes_preserve_pinned_python_failures() { response_json(deleted).await["detail"], "Failed to delete rules" ); + + let empty = platform_request( + &app, + Method::POST, + "/platform/rate-limits/bulk/delete", + Some(&cookie), + None, + Some(json!({"rule_ids": []})), + ) + .await; + assert_eq!(empty.status(), StatusCode::OK); + assert_eq!(response_json(empty).await["deleted_count"], 0); + + for body in [json!({}), json!({"rule_ids": "bulk-rule"})] { + let invalid = platform_request( + &app, + Method::POST, + "/platform/rate-limits/bulk/delete", + Some(&cookie), + None, + Some(body), + ) + .await; + assert_eq!(invalid.status(), StatusCode::UNPROCESSABLE_ENTITY); + assert_eq!(response_json(invalid).await["error_code"], "VAL001"); + } } #[tokio::test] @@ -7058,6 +8390,18 @@ async fn rate_limit_crud_and_action_envelopes_match_python() { assert!(listed.is_array()); assert_eq!(listed[0]["rule_id"], "crud-rule"); + let invalid_enabled = platform_request( + &app, + Method::GET, + "/platform/rate-limits/?enabled_only=not-a-bool", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(invalid_enabled.status(), StatusCode::UNPROCESSABLE_ENTITY); + assert_eq!(response_json(invalid_enabled).await["error_code"], "VAL001"); + let updated = platform_request( &app, Method::PUT, @@ -7092,7 +8436,7 @@ async fn rate_limit_crud_and_action_envelopes_match_python() { &app, Method::POST, "/platform/rate-limits/", - None, + Some(&cookie), None, Some(json!({ "rule_id": 123, "rule_type": "global", "time_window": "minute", "limit": "5", @@ -7116,7 +8460,7 @@ async fn rate_limit_crud_and_action_envelopes_match_python() { &app, Method::POST, "/platform/rate-limits/", - None, + Some(&cookie), None, Some(json!({ "rule_id": "float-limit", "rule_type": "global", "time_window": "minute", "limit": 1.5, @@ -7130,7 +8474,7 @@ async fn rate_limit_crud_and_action_envelopes_match_python() { &app, Method::POST, "/platform/rate-limits/", - None, + Some(&cookie), None, Some(json!({ "rule_id": "boolean-limit", "rule_type": "global", "time_window": "minute", "limit": true, @@ -7144,7 +8488,7 @@ async fn rate_limit_crud_and_action_envelopes_match_python() { &app, Method::POST, "/platform/rate-limits/", - None, + Some(&cookie), None, Some(json!({ "rule_id": "decimal-string-limit", "rule_type": "global", "time_window": "minute", "limit": "1.5", @@ -7164,7 +8508,7 @@ async fn rate_limit_crud_and_action_envelopes_match_python() { &app, Method::POST, "/platform/rate-limits/", - None, + Some(&cookie), None, Some(json!({ "rule_id": "unknown-field", "rule_type": "global", "time_window": "minute", "limit": 1, @@ -7195,6 +8539,45 @@ async fn rate_limit_crud_and_action_envelopes_match_python() { assert_eq!(copied["description"], "Copy of crud-rule"); assert_eq!(copied["limit"], 20); + let numeric_copy = platform_request( + &app, + Method::POST, + "/platform/rate-limits/crud-rule/duplicate", + Some(&cookie), + None, + Some(json!({"new_rule_id": 456})), + ) + .await; + assert_eq!(numeric_copy.status(), StatusCode::CREATED); + assert_eq!(response_json(numeric_copy).await["rule_id"], "456"); + + let empty_copy = platform_request( + &app, + Method::POST, + "/platform/rate-limits/crud-rule/duplicate", + Some(&cookie), + None, + Some(json!({"new_rule_id": ""})), + ) + .await; + assert_eq!(empty_copy.status(), StatusCode::CREATED); + assert_eq!(response_json(empty_copy).await["rule_id"], ""); + + let missing_copy = platform_request( + &app, + Method::POST, + "/platform/rate-limits/missing-source/duplicate", + Some(&cookie), + None, + Some(json!({"new_rule_id": "unused"})), + ) + .await; + assert_eq!(missing_copy.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response_json(missing_copy).await["detail"], + "Source rule missing-source not found" + ); + let disabled = platform_request( &app, Method::POST, @@ -7255,6 +8638,8 @@ async fn rate_limit_crud_and_action_envelopes_match_python() { #[tokio::test] async fn rate_limit_management_is_public_and_uses_python_priority_search_contracts() { let app = build_router(memory_state(false).await); + // Tier and rate-limit management now requires an authenticated manager. + let (cookie, _) = login(&app).await; for rule in [ json!({ "rule_id": "search-low", "rule_type": "global", "time_window": "minute", "limit": 5, @@ -7273,7 +8658,7 @@ async fn rate_limit_management_is_public_and_uses_python_priority_search_contrac &app, Method::POST, "/platform/rate-limits/", - None, + Some(&cookie), None, Some(rule), ) @@ -7285,7 +8670,7 @@ async fn rate_limit_management_is_public_and_uses_python_priority_search_contrac &app, Method::GET, "/platform/rate-limits/?skip=0&limit=100", - None, + Some(&cookie), None, None, ) @@ -7300,7 +8685,7 @@ async fn rate_limit_management_is_public_and_uses_python_priority_search_contrac &app, Method::GET, "/platform/rate-limits/search?q=needle", - None, + Some(&cookie), None, None, ) @@ -7314,7 +8699,7 @@ async fn rate_limit_management_is_public_and_uses_python_priority_search_contrac &app, Method::GET, "/platform/rate-limits/search?q=per_user", - None, + Some(&cookie), None, None, ) @@ -7326,7 +8711,7 @@ async fn rate_limit_management_is_public_and_uses_python_priority_search_contrac &app, Method::POST, "/platform/rate-limits/", - None, + Some(&cookie), None, Some(json!({ "rule_id": "missing-target", "rule_type": "per_user", "time_window": "minute", "limit": 5, @@ -7346,7 +8731,7 @@ async fn rate_limit_management_is_public_and_uses_python_priority_search_contrac &app, Method::GET, "/platform/rate-limits/status", - None, + Some(&cookie), None, None, ) @@ -7357,3 +8742,205 @@ async fn rate_limit_management_is_public_and_uses_python_priority_search_contrac "Rule status not found" ); } + +#[tokio::test] +async fn monitor_report_validates_dates_and_generates_a_python_shaped_csv() { + let app = build_router(memory_state(false).await); + let (cookie, _) = login(&app).await; + + let bad_format = platform_request( + &app, + Method::GET, + "/platform/monitor/report?start=not-a-date&end=2024-01-02", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(bad_format.status(), StatusCode::BAD_REQUEST); + assert_eq!(response_json(bad_format).await["error_code"], "MON003"); + + let backwards = platform_request( + &app, + Method::GET, + "/platform/monitor/report?start=2024-01-05&end=2024-01-01", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(backwards.status(), StatusCode::BAD_REQUEST); + assert_eq!( + response_json(backwards).await["error_message"], + "End date must be after start date" + ); + + let ok = platform_request( + &app, + Method::GET, + "/platform/monitor/report?start=2024-01-01&end=2024-01-02", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(ok.status(), StatusCode::OK); + assert_eq!( + ok.headers().get(header::CONTENT_TYPE).unwrap(), + "text/csv; charset=utf-8" + ); + assert_eq!( + ok.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "attachment; filename=doorman_report_2024-01-01_to_2024-01-02.csv" + ); + let body = + String::from_utf8(to_bytes(ok.into_body(), 64 * 1024).await.unwrap().to_vec()).unwrap(); + assert!(body.starts_with("Report,From,2024-01-01,To,2024-01-02\nOverview\n")); + for section in [ + "Bandwidth Overview\n", + "Status Codes\nstatus,count\n", + "API Usage\napi,total,errors,successes,success_rate\n", + "User Usage\nusername,requests\n", + "Bandwidth (per day, UTC)\ndate,bytes_in,bytes_out,total\n", + ] { + assert!( + body.contains(section), + "missing section {section:?} in:\n{body}" + ); + } +} + +#[tokio::test] +async fn non_numeric_role_page_returns_fastapi_shaped_422_not_a_silent_default() { + // Python's `get_roles(page: int = ..., page_size: int = ...)` are typed + // FastAPI query params: a non-numeric value never reaches the handler and + // instead produces FastAPI's own 422 validation body, rather than being + // silently coerced to page=1 the way Rust's role listing used to. + let app = build_router(memory_state(false).await); + let (cookie, _) = login(&app).await; + + let response = platform_request( + &app, + Method::GET, + "/platform/role?page=not-a-number", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); + let body = response_json(response).await; + assert_eq!(body["error_code"], "VAL001"); + + let response = platform_request( + &app, + Method::GET, + "/platform/role/all?page_size=not-a-number", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); + let body = response_json(response).await; + assert_eq!(body["error_code"], "VAL001"); +} + +#[tokio::test] +async fn non_numeric_page_returns_422_across_typed_fastapi_listing_routes() { + // The same typed `page: int`/`page_size: int` FastAPI parameter pattern + // covers get_groups, get_routings, get_apis, get_users, and the credit + // listing routes, not just get_roles. + let app = build_router(memory_state(false).await); + let (cookie, _) = login(&app).await; + let request = + |path: &'static str| platform_request(&app, Method::GET, path, Some(&cookie), None, None); + + for path in [ + "/platform/api?page=nope", + "/platform/api/all?page_size=nope", + "/platform/user?page=nope", + "/platform/user/all?page_size=nope", + "/platform/credit/defs?page=nope", + "/platform/credit/all?page_size=nope", + ] { + let response = request(path).await; + assert_eq!( + response.status(), + StatusCode::UNPROCESSABLE_ENTITY, + "{path}" + ); + let body = response_json(response).await; + assert_eq!(body["error_code"], "VAL001", "{path}"); + } +} + +#[tokio::test] +async fn demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults() { + let app = build_router(memory_state(false).await); + let (cookie, _) = login(&app).await; + + // A small, explicit request exercises the real configurable seeder + // (SeedOptions/run_seed) rather than the old hardcoded 3-API stub. + let response = platform_request( + &app, + Method::POST, + "/platform/demo/seed?users=2&apis=1&endpoints=1&groups=1&protos=0&logs=0&seed=7", + Some(&cookie), + None, + None, + ) + .await; + assert_eq!(response.status(), StatusCode::OK); + let body = response_json(response).await; + assert_eq!(body["message"], "Seed completed"); + // The comprehensive seeder reports per-collection counts; the old stub + // only ever returned a flat "message" body with no such detail. + assert!( + body.as_object().is_some_and(|object| object.len() > 1), + "expected seed result fields alongside message: {body:?}" + ); +} + +#[tokio::test] +async fn demo_seed_denies_non_admin_with_pythons_error_code_and_message() { + let state = memory_state(false).await; + let storage = state.storage.clone().unwrap(); + storage + .insert_one( + "roles", + json!({"role_name": "viewer", "manage_gateway": false}), + ) + .await + .unwrap(); + storage + .insert_one( + "users", + json!({ + "username": "demo-viewer", + "email": "demo-viewer@doorman.dev", + "password": bcrypt::hash(fixture_password(), bcrypt::DEFAULT_COST).unwrap(), + "role": "viewer", + "groups": [], + "active": true + }), + ) + .await + .unwrap(); + let app = build_router(state); + let (viewer, _) = login_as(&app, "demo-viewer@doorman.dev", fixture_password()).await; + + let response = platform_request( + &app, + Method::POST, + "/platform/demo/seed", + Some(&viewer), + None, + None, + ) + .await; + assert_eq!(response.status(), StatusCode::FORBIDDEN); + let body = response_json(response).await; + assert_eq!(body["error_code"], "DEMO001"); + assert_eq!(body["error_message"], "Permission denied to run seeder"); +} diff --git a/gateway-rs/tests/python_parity_suite.rs b/gateway-rs/tests/python_parity_suite.rs index 89c03abb..a4f2edb9 100644 --- a/gateway-rs/tests/python_parity_suite.rs +++ b/gateway-rs/tests/python_parity_suite.rs @@ -135,7 +135,7 @@ async fn parity_test_user_onboarding_and_auth_flows() { .oneshot( Request::builder() .method("POST") - .uri("/platform/users") + .uri("/platform/user") .header(header::AUTHORIZATION, format!("Bearer {token}")) .header(header::CONTENT_TYPE, "application/json") .body(Body::from( @@ -158,7 +158,7 @@ async fn parity_test_user_onboarding_and_auth_flows() { .oneshot( Request::builder() .method("POST") - .uri("/platform/users") + .uri("/platform/user") .header(header::AUTHORIZATION, format!("Bearer {token}")) .header(header::CONTENT_TYPE, "application/json") .body(Body::from( @@ -227,9 +227,8 @@ async fn parity_test_developer_tools_chaos_and_simulator() { .header(header::CONTENT_TYPE, "application/json") .body(Body::from( json!({ + "backend": "redis", "enabled": true, - "latency_ms": 10, - "error_status": 503 }) .to_string(), )) @@ -254,7 +253,7 @@ async fn parity_test_developer_tools_chaos_and_simulator() { assert_eq!(response.status(), StatusCode::OK); let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); let body: Value = serde_json::from_slice(&bytes).unwrap(); - assert_eq!(body["enabled"], true); + assert_eq!(body["redis_outage"], true); let response = app .clone() diff --git a/gateway-rs/tests/roles_groups_parity.rs b/gateway-rs/tests/roles_groups_parity.rs index 413400d4..3ba8146c 100644 --- a/gateway-rs/tests/roles_groups_parity.rs +++ b/gateway-rs/tests/roles_groups_parity.rs @@ -209,6 +209,19 @@ async fn live_role_permission_matrix_blocks_then_allows_each_management_operatio ) .await; assert_eq!(api.status(), StatusCode::CREATED); + let endpoint = request( + &app, + Method::POST, + "/platform/endpoint", + Some(&admin), + Some(json!({ + "api_name": "permission-matrix-api", "api_version": "v1", + "endpoint_method": "GET", "endpoint_uri": "/visible", + "endpoint_description": "pinned Python authorization boundary" + })), + ) + .await; + assert_eq!(endpoint.status(), StatusCode::CREATED); for (index, (permission, expected_code)) in [ ("manage_apis", "API007"), @@ -311,6 +324,33 @@ async fn least_privilege_role_cannot_create_apis_or_read_logs() { .await; assert_eq!(viewer.status(), StatusCode::CREATED); let viewer = login(&app, "viewer1@example.com", fixture_password()).await; + let api = request( + &app, + Method::POST, + "/platform/api", + Some(&admin), + Some(json!({ + "api_name": "legacy-open-management", "api_version": "v1", + "api_description": "pinned Python authorization boundary", + "api_allowed_roles": ["admin"], "api_allowed_groups": ["ALL"], + "api_servers": ["http://127.0.0.1:9"], "api_type": "REST" + })), + ) + .await; + assert_eq!(api.status(), StatusCode::CREATED); + let endpoint = request( + &app, + Method::POST, + "/platform/endpoint", + Some(&admin), + Some(json!({ + "api_name": "legacy-open-management", "api_version": "v1", + "endpoint_method": "GET", "endpoint_uri": "/visible", + "endpoint_description": "pinned Python authorization boundary" + })), + ) + .await; + assert_eq!(endpoint.status(), StatusCode::CREATED); let create_api = request( &app, Method::POST, @@ -329,10 +369,83 @@ async fn least_privilege_role_cannot_create_apis_or_read_logs() { .await; assert_eq!(create_api.status(), StatusCode::FORBIDDEN); assert_eq!(json_body(create_api).await["error_code"], "API007"); + for path in [ + "/platform/api/all?page=1&page_size=100", + "/platform/api/legacy-open-management/v1", + "/platform/endpoint/legacy-open-management/v1?page=1&page_size=100", + ] { + let response = request(&app, Method::GET, path, Some(&viewer), None).await; + let status = response.status(); + let body = json_body(response).await; + assert_eq!(status, StatusCode::OK, "{path}: {body}"); + } + let update_endpoint = request( + &app, + Method::PUT, + "/platform/endpoint/GET/legacy-open-management/v1/visible", + Some(&viewer), + Some(json!({"endpoint_description": "blocked"})), + ) + .await; + assert_eq!(update_endpoint.status(), StatusCode::FORBIDDEN); + assert_eq!(json_body(update_endpoint).await["error_code"], "END011"); + let delete_endpoint = request( + &app, + Method::DELETE, + "/platform/endpoint/GET/legacy-open-management/v1/visible", + Some(&viewer), + None, + ) + .await; + assert_eq!(delete_endpoint.status(), StatusCode::FORBIDDEN); + assert_eq!(json_body(delete_endpoint).await["error_code"], "END012"); + for (method, path, payload, code) in [ + ( + Method::POST, + "/platform/endpoint/endpoint/validation", + Some(json!({ + "endpoint_id": "missing", "validation_enabled": true, + "validation_schema": {"validation_schema": {}} + })), + "END013", + ), + ( + Method::PUT, + "/platform/endpoint/endpoint/validation/missing", + Some(json!({ + "validation_enabled": true, + "validation_schema": {"validation_schema": {}} + })), + "END014", + ), + ( + Method::DELETE, + "/platform/endpoint/endpoint/validation/missing", + None, + "END015", + ), + ] { + let response = request(&app, method, path, Some(&viewer), payload).await; + assert_eq!(response.status(), StatusCode::FORBIDDEN, "{path}"); + assert_eq!(json_body(response).await["error_code"], code, "{path}"); + } + let deleted = request( + &app, + Method::DELETE, + "/platform/api/legacy-open-management/v1", + Some(&viewer), + None, + ) + .await; + assert_eq!(deleted.status(), StatusCode::OK); + assert_eq!( + json_body(deleted).await, + json!({"message": "API deleted successfully"}) + ); for (path, code) in [ ("/platform/logging/logs", "LOG001"), - ("/platform/logging/logs/files", "LOG001"), - ("/platform/logging/logs/statistics", "LOG001"), + ("/platform/logging/logs/files", "LOG005"), + ("/platform/logging/logs/statistics", "LOG002"), ("/platform/logging/logs/export", "LOG003"), ("/platform/logging/logs/download", "LOG004"), ] { @@ -441,7 +554,7 @@ async fn non_admin_managers_cannot_discover_or_manage_bootstrap_admin() { .await; assert_eq!(roles.status(), StatusCode::OK); assert!( - json_body(roles).await["response"]["roles"] + json_body(roles).await["roles"] .as_array() .unwrap() .iter() @@ -470,7 +583,7 @@ async fn non_admin_managers_cannot_discover_or_manage_bootstrap_admin() { .await; assert_eq!(users.status(), StatusCode::OK); assert!( - json_body(users).await["response"]["users"] + json_body(users).await["users"] .as_array() .unwrap() .iter() @@ -524,14 +637,9 @@ async fn non_admin_managers_cannot_discover_or_manage_bootstrap_admin() { (Method::POST, "/platform/authorization/admin/enable/admin"), ] { let response = request(&app, method, path, Some(&manager), Some(json!({}))).await; - assert!( - matches!( - response.status(), - StatusCode::FORBIDDEN | StatusCode::NOT_FOUND - ), - "{path}: {}", - response.status() - ); + assert_eq!(response.status(), StatusCode::NOT_FOUND, "{path}"); + let body = json_body(response).await; + assert_eq!(body, json!({"error_message": "User not found"}), "{path}"); } } @@ -800,8 +908,15 @@ async fn subscription_target_requires_the_python_group_access_gate() { })), ) .await; - assert_eq!(response.status(), StatusCode::FORBIDDEN); - assert_eq!(json_body(response).await["error_code"], "SUB007"); + // Verified against the pinned server: group_required raises a 401 that the + // route renders as GEN001 (its SUB007 branch is unreachable). + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + let body = json_body(response).await; + assert_eq!(body["error_code"], "GEN001"); + assert_eq!( + body["error_message"], + "You do not have the correct group for this" + ); } #[tokio::test] @@ -891,17 +1006,24 @@ async fn routing_crud_requires_manage_routings_and_allows_routing_manager() { } #[tokio::test] -async fn missing_group_and_role_reads_and_deletes_return_not_found() { +async fn missing_group_and_role_contracts_match_python_service_codes() { let app = app().await; let admin = login(&app, "admin@doorman.dev", fixture_password()).await; for (method, path, code) in [ - (Method::GET, "/platform/group/not-a-group", "GRP002"), - (Method::GET, "/platform/role/not-a-role", "ROL002"), - (Method::DELETE, "/platform/group/not-a-group", "GRP002"), - (Method::DELETE, "/platform/role/not-a-role", "ROL002"), + (Method::GET, "/platform/group/not-a-group", "GRP003"), + (Method::GET, "/platform/role/not-a-role", "ROLE004"), + (Method::DELETE, "/platform/group/not-a-group", "GRP003"), + (Method::DELETE, "/platform/role/not-a-role", "ROLE004"), ] { + // Verified against the pinned server: group_service/role_service return + // 400 for a missing target on delete and 404 on read. + let expected = if method == Method::DELETE { + StatusCode::BAD_REQUEST + } else { + StatusCode::NOT_FOUND + }; let response = request(&app, method, path, Some(&admin), None).await; - assert_eq!(response.status(), StatusCode::NOT_FOUND, "{path}"); + assert_eq!(response.status(), expected, "{path}"); assert_eq!(json_body(response).await["error_code"], code, "{path}"); } } @@ -933,5 +1055,95 @@ async fn non_admin_role_manager_cannot_create_admin_role() { ) .await; assert_eq!(response.status(), StatusCode::FORBIDDEN); - assert_eq!(json_body(response).await["error_code"], "ROLE009"); + assert_eq!(json_body(response).await["error_code"], "ROLE013"); +} + +#[tokio::test] +async fn role_route_permissions_visibility_and_admin_codes_match_python() { + let app = app().await; + let admin = login(&app, "admin@doorman.dev", fixture_password()).await; + for (name, manage_roles) in [("role-viewer", false), ("role-manager", true)] { + let response = request( + &app, + Method::POST, + "/platform/role", + Some(&admin), + Some(json!({"role_name": name, "manage_roles": manage_roles})), + ) + .await; + assert_eq!(response.status(), StatusCode::CREATED); + } + let viewer = create_user(&app, &admin, "role-viewer-user", "role-viewer").await; + let manager = create_user(&app, &admin, "role-manager-2", "role-manager").await; + + let listed = request( + &app, + Method::GET, + "/platform/role?page=1&page_size=10", + Some(&viewer), + None, + ) + .await; + assert_eq!(listed.status(), StatusCode::OK); + let listed = json_body(listed).await; + assert!(listed["roles"].is_array()); + // Non-admin callers get only the filtered roles (no page metadata). + assert!(listed.get("page").is_none()); + assert!( + listed["roles"] + .as_array() + .unwrap() + .iter() + .all(|role| role["role_name"] != "admin") + ); + assert_eq!( + request( + &app, + Method::GET, + "/platform/role/role-viewer", + Some(&viewer), + None, + ) + .await + .status(), + StatusCode::OK + ); + + for (method, path, payload, code) in [ + ( + Method::POST, + "/platform/role", + Some(json!({"role_name": "denied-role"})), + "ROLE009", + ), + ( + Method::PUT, + "/platform/role/role-viewer", + Some(json!({"view_logs": true})), + "ROLE010", + ), + ( + Method::DELETE, + "/platform/role/role-viewer", + None, + "ROLE011", + ), + ] { + let response = request(&app, method, path, Some(&viewer), payload).await; + assert_eq!(response.status(), StatusCode::FORBIDDEN); + assert_eq!(json_body(response).await["error_code"], code); + } + + for (method, code) in [(Method::PUT, "ROLE014"), (Method::DELETE, "ROLE016")] { + let response = request( + &app, + method, + "/platform/role/admin", + Some(&manager), + Some(json!({"view_logs": true})), + ) + .await; + assert_eq!(response.status(), StatusCode::FORBIDDEN); + assert_eq!(json_body(response).await["error_code"], code); + } } diff --git a/gateway-rs/tests/runtime.rs b/gateway-rs/tests/runtime.rs index e0a31d1d..d67194f6 100644 --- a/gateway-rs/tests/runtime.rs +++ b/gateway-rs/tests/runtime.rs @@ -8,6 +8,7 @@ use doorman_gateway::storage::models::PolicyDocuments; use doorman_gateway::{AppState, Config, build_router}; use http::{Request, StatusCode, header}; use serde_json::{Value, json}; +use std::sync::atomic::Ordering; use tower::ServiceExt; #[tokio::test] @@ -33,15 +34,57 @@ async fn rust_health_matches_public_contract() { ); } +#[tokio::test] +async fn disabled_logs_leave_request_metrics_running_without_creating_log_files() { + let logs_dir = + std::env::temp_dir().join(format!("doorman-disabled-logs-{}", uuid::Uuid::new_v4())); + let mut config = Config::for_test("http://127.0.0.1:9".to_owned()); + config.logs_enabled = false; + config.logs_dir = Some(logs_dir.clone()); + let state = AppState::new(config).unwrap(); + let runtime = state.runtime.clone(); + let app = build_router(state); + + let features = app + .clone() + .oneshot( + Request::builder() + .uri("/api/features") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(features.status(), StatusCode::OK); + assert_eq!( + to_bytes(features.into_body(), 1024).await.unwrap(), + r#"{"logs_enabled":false}"# + ); + + let health = app + .oneshot( + Request::builder() + .uri("/api/health") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(health.status(), StatusCode::OK); + assert!(runtime.request_total.load(Ordering::Relaxed) >= 2); + assert!(!logs_dir.exists(), "disabled logging created {logs_dir:?}"); +} + #[tokio::test] async fn platform_routes_are_native_and_never_use_an_internal_backend() { let (upstream_url, server) = - spawn_upstream(Router::new().route("/platform/ping", get(|| async { "upstream" }))).await; + spawn_upstream(Router::new().route("/platform/user/me", get(|| async { "upstream" }))) + .await; let app = build_router(AppState::new(Config::for_test(upstream_url)).unwrap()); let response = app .oneshot( Request::builder() - .uri("/platform/ping") + .uri("/platform/user/me") .body(Body::empty()) .unwrap(), ) @@ -129,17 +172,18 @@ async fn rust_serves_health_independent_of_removed_rollout_flags() { #[tokio::test] async fn platform_requests_are_not_forwarded_to_an_internal_backend() { - let (upstream_url, server) = spawn_upstream( - Router::new().route("/platform/echo", any(|| async { StatusCode::IM_A_TEAPOT })), - ) + let (upstream_url, server) = spawn_upstream(Router::new().route( + "/platform/api/all", + any(|| async { StatusCode::IM_A_TEAPOT }), + )) .await; let app = build_router(AppState::new(Config::for_test(upstream_url)).unwrap()); let response = app .oneshot( Request::builder() - .method(Method::POST) - .uri("/platform/echo?value=1") - .body(Body::from("payload")) + .method(Method::GET) + .uri("/platform/api/all?value=1") + .body(Body::empty()) .unwrap(), ) .await @@ -152,13 +196,13 @@ async fn platform_requests_are_not_forwarded_to_an_internal_backend() { #[tokio::test] async fn spoofed_forwarding_headers_do_not_enable_platform_access() { let (upstream_url, server) = - spawn_upstream(Router::new().route("/platform/headers", any(|| async { StatusCode::OK }))) + spawn_upstream(Router::new().route("/platform/user/me", any(|| async { StatusCode::OK }))) .await; let app = build_router(AppState::new(Config::for_test(upstream_url)).unwrap()); let response = app .oneshot( Request::builder() - .uri("/platform/headers") + .uri("/platform/user/me") .header("x-forwarded-for", "203.0.113.10") .header("x-real-ip", "203.0.113.11") .body(Body::empty()) @@ -334,7 +378,7 @@ async fn rust_handles_cache_delete_locally() { assert_eq!(response.status(), StatusCode::UNAUTHORIZED); assert_eq!( to_bytes(response.into_body(), 1024).await.unwrap(), - r#"{"error_code":"GTW401","error_message":"Unauthorized"}"# + r#"{"detail":"Unauthorized"}"# ); server.abort(); } @@ -390,11 +434,10 @@ async fn rust_rejects_unported_health_methods_in_rust() { .unwrap(); assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED); - assert!( - to_bytes(response.into_body(), 1024) - .await - .unwrap() - .is_empty() + // Verified against the pinned server: Starlette renders the FastAPI detail body. + assert_eq!( + to_bytes(response.into_body(), 1024).await.unwrap(), + br#"{"detail":"Method Not Allowed"}"#.as_slice() ); server.abort(); } @@ -849,6 +892,8 @@ async fn oversized_rest_body_returns_legacy_413_without_reaching_upstream() { .method(Method::POST) .uri("/api/rest/limited/v1/items") .header("content-type", "application/json") + .header(header::ACCEPT_ENCODING, "gzip") + .header(header::CONTENT_LENGTH, (1024 * 1024 + 1).to_string()) .body(Body::from(vec![b'x'; 1024 * 1024 + 1])) .unwrap(), ) @@ -856,6 +901,7 @@ async fn oversized_rest_body_returns_legacy_413_without_reaching_upstream() { .unwrap(); assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE); + assert!(!response.headers().contains_key(header::CONTENT_ENCODING)); let body: Value = serde_json::from_slice(&to_bytes(response.into_body(), 1024).await.unwrap()).unwrap(); assert_eq!(body["error_code"], "REQ001"); @@ -866,6 +912,28 @@ async fn oversized_rest_body_returns_legacy_413_without_reaching_upstream() { server.abort(); } +#[tokio::test] +async fn oversized_grpc_web_body_is_rejected_before_policy() { + let state = AppState::new(Config::for_test("removed-internal-backend".to_owned())) + .unwrap() + .with_policy_documents(PolicyDocuments::default()); + let response = build_router(state) + .oneshot( + Request::builder() + .method(Method::POST) + .uri("/grpc-web/missing/fixture.v1.Resource/Create") + .header(header::CONTENT_TYPE, "application/grpc-web-text") + .header(header::ACCEPT_ENCODING, "gzip") + .header(header::CONTENT_LENGTH, (1024 * 1024 + 1).to_string()) + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE); + assert!(!response.headers().contains_key(header::CONTENT_ENCODING)); +} + #[tokio::test] async fn rest_body_limit_matches_python_configured_boundary_contract() { if std::env::var_os("DOORMAN_REST_BODY_LIMIT_CHILD").is_some() { diff --git a/gateway-rs/tests/security_settings_parity.rs b/gateway-rs/tests/security_settings_parity.rs index 7764a2fe..24b05275 100644 --- a/gateway-rs/tests/security_settings_parity.rs +++ b/gateway-rs/tests/security_settings_parity.rs @@ -507,13 +507,8 @@ async fn security_settings_coerced_updates_persist_and_invalid_updates_are_atomi }))).await; assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); let errors = response_json(response).await; - let errors = &response_payload(&errors)["detail"]; - assert_eq!( - errors[0], - json!({"loc": ["body", "auto_save_frequency_seconds"], - "msg": "ensure this value is greater than or equal to 60", "type": "value_error.number.not_ge", "ctx": {"limit_value": 60}}) - ); - assert_eq!(errors[1]["type"], "type_error.bool"); + // The pinned global handler returns VAL001; the Pydantic details are only logged. + assert_eq!(response_payload(&errors)["error_code"], "VAL001"); assert_eq!( storage .find_one("settings", &json!({"type": "security_settings"})) @@ -661,10 +656,8 @@ async fn security_settings_integer_length_limits_reject_atomically() { .await; assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); assert_eq!( - response_payload(&response_json(response).await)["detail"], - json!([ - {"loc":["body","auto_save_frequency_seconds"],"msg":"value is not a valid integer","type":"type_error.integer"} - ]) + response_payload(&response_json(response).await)["error_code"], + "VAL001" ); assert_eq!( storage @@ -683,10 +676,8 @@ async fn security_settings_integer_length_limits_reject_atomically() { }))).await; assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); assert_eq!( - response_payload(&response_json(response).await)["detail"], - json!([ - {"loc":["body","auto_save_frequency_seconds"],"msg":"ensure this value is greater than or equal to 60","type":"value_error.number.not_ge","ctx":{"limit_value":60}} - ]) + response_payload(&response_json(response).await)["error_code"], + "VAL001" ); assert_eq!(std::fs::read(&file).unwrap(), bytes); assert_eq!( @@ -791,14 +782,8 @@ async fn security_settings_lists_persist_and_enforce_python_patterns_atomically( .await; assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); assert_eq!( - response_payload(&response_json(response).await)["detail"], - json!([ - {"loc":["body","ip_whitelist",0],"msg":"none is not an allowed value","type":"type_error.none.not_allowed"}, - {"loc":["body","ip_whitelist",1],"msg":"str type expected","type":"type_error.str"}, - {"loc":["body","ip_whitelist",2],"msg":"str type expected","type":"type_error.str"}, - {"loc":["body","ip_blacklist"],"msg":"value is not a valid list","type":"type_error.list"}, - {"loc":["body","xff_trusted_proxies",0],"msg":"none is not an allowed value","type":"type_error.none.not_allowed"} - ]) + response_payload(&response_json(response).await)["error_code"], + "VAL001" ); assert_eq!( storage @@ -956,12 +941,11 @@ async fn security_settings_unicode_intervals_persist_and_invalid_updates_are_ato "{value:?}" ); let body = response_json(response).await; - let error = &response_payload(&body)["detail"][0]; - assert_eq!(error["loc"], json!(["body", "auto_save_frequency_seconds"])); - assert_eq!(error["type"], error_type); - if error_type == "value_error.number.not_ge" { - assert_eq!(error["ctx"], json!({"limit_value":60})); - } + assert_eq!( + response_payload(&body)["error_code"], + "VAL001", + "{error_type}" + ); assert_eq!( storage .find_one("settings", &json!({"type":"security_settings"})) @@ -1054,8 +1038,8 @@ async fn security_settings_dump_path_coercion_persists_and_publishes_atomically( .await; assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); assert_eq!( - response_payload(&response_json(response).await)["detail"], - json!([{"loc": ["body", "dump_path"], "msg": "str type expected", "type": "type_error.str"}]) + response_payload(&response_json(response).await)["error_code"], + "VAL001" ); assert_eq!( storage @@ -1324,7 +1308,7 @@ async fn log_export_filters_records_and_redacts_nested_credentials() { json!({"time":"2026-09-12T12:00:00Z","name":"fixture","user":"fixture-user","level":"INFO","message":"earlier request"}), ]; std::fs::write( - directory.join("fixture.log"), + directory.join("doorman.log"), records .iter() .map(Value::to_string) diff --git a/gateway-rs/tests/user_lifecycle_parity.rs b/gateway-rs/tests/user_lifecycle_parity.rs index 78c465f1..3cf00591 100644 --- a/gateway-rs/tests/user_lifecycle_parity.rs +++ b/gateway-rs/tests/user_lifecycle_parity.rs @@ -149,6 +149,22 @@ async fn self_service_updates_cannot_escalate_privileges() { .await; assert_eq!(status, StatusCode::CREATED, "{user}"); let limited = login_as(&app, "limited@example.com", password).await; + let (status, users) = json_request( + &app, + Some(&limited), + Method::GET, + "/platform/user/all?page=1&page_size=100", + None, + ) + .await; + assert_eq!(status, StatusCode::OK, "{users}"); + assert!( + users["users"] + .as_array() + .unwrap() + .iter() + .all(|user| user["role"] != "admin") + ); for payload in [ json!({"role": "admin"}), json!({"groups": ["ALL", "admin"]}), @@ -275,7 +291,7 @@ async fn admin_can_read_list_update_and_delete_administrator_resources() { .await; assert_eq!(status, StatusCode::OK, "{roles}"); assert!( - roles["response"]["roles"] + roles["roles"] .as_array() .unwrap() .iter() @@ -292,7 +308,7 @@ async fn admin_can_read_list_update_and_delete_administrator_resources() { .await; assert_eq!(status, StatusCode::OK, "{users}"); assert!( - users["response"]["users"] + users["users"] .as_array() .unwrap() .iter() diff --git a/parity/README.md b/parity/README.md index d6cfe914..3e4317cb 100644 --- a/parity/README.md +++ b/parity/README.md @@ -48,6 +48,29 @@ make parity-ledger pinned commit. The verification report includes counts by suite/domain and status, allowing incremental migration work to target a concrete gap. +## Python-to-Rust source coverage ledger + +`source_coverage_ledger.json` inventories production Python modules, classes, +fields, functions, and methods from the same pinned reference. It exists because +test disposition alone cannot prove that untested Python behavior was translated. +`source_coverage_overrides.json` records the reviewed Rust symbol and test evidence +for each unit. Large uniform modules may use a file default, with exact symbol +entries taking precedence for partial or obsolete exceptions. + +Unlisted units generate as `unreviewed`. A unit is `translated` only when the +override identifies both its Rust implementation and assertion-level tests. +`partial` and `missing` keep incomplete work visible; approved changes and +obsoletions require explicit rationales. + +```bash +python3 scripts/generate_source_coverage_ledger.py --write +make parity-source-ledger +``` + +The source-unit percentage measures inventory review, not semantic weighting or +release readiness. Complete parity also requires the operation and system evidence +described below. + With the pinned Python server on port 3102 and Rust on port 3101, provide a fresh administrator token for each disposable fixture and run the differential comparison: diff --git a/parity/differential/operation_approvals.json b/parity/differential/operation_approvals.json index 0967ef42..6c63509a 100644 --- a/parity/differential/operation_approvals.json +++ b/parity/differential/operation_approvals.json @@ -1 +1,11 @@ -{} +{ + "operation:DELETE:/api/rest/{path}": "MIG-090: Python's api_util.get_api does `api_name, api_version = api_name_version.split('/')` on a single-segment {path} with no '/', raising an uncaught ValueError (confirmed via live traceback against the pinned reference) that its broad except handler turns into a 500. Rust validates the shape defensively and returns 404 for the same unmatched request instead of reproducing the crash.", + "operation:GET:/api/rest/{path}": "MIG-090: same api_util.get_api ValueError crash as the other /api/rest/{path} methods; Rust returns 404 instead of reproducing Python's 500.", + "operation:HEAD:/api/rest/{path}": "MIG-090: same api_util.get_api ValueError crash as the other /api/rest/{path} methods; Rust returns 404 instead of reproducing Python's 500.", + "operation:PATCH:/api/rest/{path}": "MIG-090: same api_util.get_api ValueError crash as the other /api/rest/{path} methods; Rust returns 404 instead of reproducing Python's 500.", + "operation:POST:/api/rest/{path}": "MIG-090: same api_util.get_api ValueError crash as the other /api/rest/{path} methods; Rust returns 404 instead of reproducing Python's 500.", + "operation:PUT:/api/rest/{path}": "MIG-090: same api_util.get_api ValueError crash as the other /api/rest/{path} methods; Rust returns 404 instead of reproducing Python's 500.", + "operation:GET:/api/soap/{path}": "MIG-090: the SOAP gateway hits the same api_util.get_api split('/') ValueError crash as the REST gateway for a single-segment {path}; Rust returns 404 instead of reproducing Python's 500.", + "operation:POST:/api/soap/{path}": "MIG-090: the SOAP gateway hits the same api_util.get_api split('/') ValueError crash as the REST gateway for a single-segment {path}; Rust returns 404 instead of reproducing Python's 500.", + "operation:POST:/platform/openapi/parse": "MIG-093: Python's handler calls `await request.json()` with no guard around a body-less request, so a request with no body at all raises JSONDecodeError, caught by the route's broad except and turned into a 500; an explicit `{}` body returns 200 with an identical response body on both servers (verified live). Rust treats a missing body the same as an empty object and returns 200, matching Python's actual (non-crashing) behavior for every real request shape." +} diff --git a/parity/source_coverage_ledger.json b/parity/source_coverage_ledger.json new file mode 100644 index 00000000..26b4a135 --- /dev/null +++ b/parity/source_coverage_ledger.json @@ -0,0 +1,53739 @@ +{ + "entries": [ + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::module:doorman", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:_migrate_generated_directory", + "notes": "Rust owns the root-level generated/ directory directly and does not use backend-services/generated as its canonical destination.", + "rationale": "Python-only directory-layout migration is obsolete after removal of the Python backend runtime.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "function", + "line": 140, + "path": "backend-services/doorman.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_function:validate_database_connections", + "notes": "Rust establishes and validates configured shared storage before binding its listener.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/main.rs::main" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 186, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:validate_token_revocation_config", + "notes": "Rust fails configuration when memory storage is combined with multiple workers and validates JWT storage requirements before startup.", + "rust_symbols": [ + "gateway-rs/src/config.rs::validate_runtime_environment" + ], + "rust_tests": [ + "gateway-rs/src/config.rs::tests::memory_mode_rejects_multiple_workers" + ], + "source": { + "kind": "function", + "line": 242, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [ + "asynccontextmanager" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:app_lifespan", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_function", + "line": 266, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_function:app_lifespan._metrics_autosave", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_function", + "line": 379, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:app_lifespan._int_env", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "function", + "line": 471, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_function:app_lifespan._perform_dump", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_function", + "line": 505, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:app_lifespan._register_forwarding_signal", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "function", + "line": 539, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_function:app_lifespan._register_forwarding_signal._handler", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_function", + "line": 540, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_function:app_lifespan._sighup_reload", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_function", + "line": 572, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:_generate_unique_id", + "notes": "The pinned Python operation IDs are served unchanged from the frozen platform OpenAPI document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::pinned_openapi_operation_ids_are_stable_and_lowercase" + ], + "source": { + "kind": "function", + "line": 704, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [ + "doorman.middleware('http')" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:forwarded_proto_middleware", + "notes": "The Rust router does not issue FastAPI scheme-derived trailing-slash redirects, so mutating the ASGI request scheme has no Rust runtime role.", + "rationale": "Framework-specific redirect workaround made obsolete by Axum routing.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "async_function", + "line": 968, + "path": "backend-services/doorman.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:_platform_cors_config", + "notes": "Rust preserves the environment-derived origin, method, header, credential, and strict-mode defaults.", + "rust_symbols": [ + "gateway-rs/src/middleware/platform_cors.rs" + ], + "rust_tests": [ + "gateway-rs/src/middleware/platform_cors.rs::tests::platform_cors_wildcard_origin_with_credentials_non_strict_echoes_origin" + ], + "source": { + "kind": "function", + "line": 1008, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [ + "doorman.middleware('http')" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:platform_cors", + "notes": "Rust preserves platform preflight and actual-response CORS behavior, including strict wildcard handling and Vary replacement.", + "rust_symbols": [ + "gateway-rs/src/middleware/platform_cors.rs::platform_cors" + ], + "rust_tests": [ + "gateway-rs/src/middleware/platform_cors.rs::tests::platform_cors_actual_response_sets_vary_origin", + "gateway-rs/tests/live_tests_parity.rs::live_platform_cors_default_preflight_parity" + ], + "source": { + "kind": "async_function", + "line": 1070, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:_get_max_body_size", + "notes": "Rust reads the same global and protocol-specific body-limit environment variables with the Python default.", + "rust_symbols": [ + "gateway-rs/src/middleware/body_limit.rs::BodyLimits.from_env" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::protocol_body_limits_match_python" + ], + "source": { + "kind": "function", + "line": 1143, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::class:LimitedStreamReader", + "notes": "Axum's bounded body collection enforces the accumulated streaming limit without an ASGI receive wrapper.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_dispatch" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_body_limit_uses_runtime_environment" + ], + "source": { + "kind": "class", + "line": 1153, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::method:LimitedStreamReader.__init__", + "notes": "The bound is carried directly into Axum body collection.", + "rust_symbols": [ + "gateway-rs/src/middleware/body_limit.rs::BodyLimits" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::protocol_body_limits_match_python" + ], + "source": { + "kind": "method", + "line": 1161, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_method:LimitedStreamReader.__call__", + "notes": "Axum's bounded body collector rejects cumulative streamed bytes above the selected limit.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_dispatch" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_body_limit_uses_runtime_environment" + ], + "source": { + "kind": "async_method", + "line": 1167, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [ + "doorman.middleware('http')" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:body_size_limit", + "notes": "Rust preserves global/protocol limits, exclusions, monitor/security bypasses, and 413 behavior for collected request bodies.", + "rust_symbols": [ + "gateway-rs/src/middleware/body_limit.rs::BodyLimits", + "gateway-rs/src/routes/platform.rs::platform_dispatch" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_body_limit_uses_runtime_environment", + "gateway-rs/tests/runtime.rs::protocol_body_limits_match_python" + ], + "source": { + "kind": "async_function", + "line": 1185, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::class:PlatformCORSMiddleware", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "class", + "line": 1420, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::method:PlatformCORSMiddleware.__init__", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "method", + "line": 1428, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_method:PlatformCORSMiddleware.__call__", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_method", + "line": 1431, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_function:PlatformCORSMiddleware.__call__.send_wrapper", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_function", + "line": 1504, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "doorman.middleware('http')" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:policy_revision_middleware", + "notes": "Rust bumps shared policy revision atomically in every successful storage mutation rather than in an HTTP after-hook.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.bump_policy_revision" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 1544, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [ + "doorman.middleware('http')" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:request_id_middleware", + "notes": "Rust accepts the same request-id headers, generates UUIDs, propagates x-request-id internally, and emits both response header spellings.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python", + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response" + ], + "source": { + "kind": "async_function", + "line": 1562, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [ + "doorman.middleware('http')" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:security_headers", + "notes": "Rust matches default security headers, docs CSP/frame behavior, custom CSP, and HTTPS-only HSTS.", + "rust_symbols": [ + "gateway-rs/src/middleware/security_headers.rs::security_headers" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_function", + "line": 1610, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::class:JSONFormatter", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "class", + "line": 1681, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::method:JSONFormatter.format", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "method", + "line": 1682, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:configure_logger", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "function", + "line": 1739, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::class:configure_logger.RedactFilter", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "class", + "line": 1751, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::field:configure_logger.RedactFilter.PATTERNS", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "field", + "line": 1763, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::method:configure_logger.RedactFilter.filter", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "method", + "line": 1786, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::class:_VaryOriginMiddleware", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "class", + "line": 1915, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_method:_VaryOriginMiddleware.dispatch", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_method", + "line": 1916, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::class:Settings", + "notes": "Rust loads and validates the equivalent storage and JWT settings before startup, with additional v2 production hardening.", + "rust_symbols": [ + "gateway-rs/src/config.rs::Config", + "gateway-rs/src/config.rs::SharedStorageConfig" + ], + "rust_tests": [ + "gateway-rs/src/config.rs::tests::memory_mode_rejects_multiple_workers" + ], + "source": { + "kind": "class", + "line": 1983, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::field:Settings.mongo_db_uri", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "field", + "line": 1984, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::field:Settings.jwt_secret_key", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "field", + "line": 1985, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::field:Settings.jwt_algorithm", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "field", + "line": 1986, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::field:Settings.jwt_access_token_expires", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "field", + "line": 1987, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::field:Settings.jwt_refresh_token_expires", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "field", + "line": 1990, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "doorman.middleware('http')" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:ip_filter_middleware", + "notes": "Rust enforces the persisted platform IP whitelist/blacklist, trusted-proxy X-Forwarded-For handling, localhost bypass, and the /platform/security/settings exemption before routing.", + "rationale": "Approved security divergence: when storage is unavailable Rust cannot evaluate the persisted security policy and fails every platform request closed with 503 SEC012 (including login and token checks). Python's middleware reads cached settings and lets requests proceed, so cached reads such as /platform/user/me still succeed and uncached reads 500 during the same outage. Verified live with the pinned server during a simulated Mongo outage.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_ip_filter" + ], + "rust_tests": [ + "gateway-rs/tests/chaos_platform_fail_closed.rs::storage_outage_fails_platform_requests_closed_and_recovers" + ], + "source": { + "kind": "async_function", + "line": 1996, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "doorman.middleware('http')" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:metrics_middleware", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_function", + "line": 2098, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:metrics_middleware._parse_len", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "function", + "line": 2101, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::async_function:automatic_purger", + "notes": "Rust runs an interval-based revocation purge after initial restored-state cleanup.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_revocation_purger", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/tests/common/mod.rs::shared_storage_revocation_lifecycle" + ], + "source": { + "kind": "async_function", + "line": 2213, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [ + "doorman.exception_handler(JWTError)" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:jwt_exception_handler", + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "async_function", + "line": 2221, + "path": "backend-services/doorman.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "doorman.exception_handler(500)" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:internal_server_error_handler", + "notes": "Rust's panic catcher emits the pinned 500 ISE001 JSON envelope for unhandled request panics.", + "rust_symbols": [ + "gateway-rs/src/app.rs::handle_panic" + ], + "rust_tests": [ + "gateway-rs/src/app.rs::tests::panic_handler_matches_python_internal_error_envelope" + ], + "source": { + "kind": "async_function", + "line": 2229, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [ + "doorman.exception_handler(RequestValidationError)" + ], + "domain": "application", + "id": "backend-services/doorman.py::async_function:validation_exception_handler", + "notes": "Rust typed request boundaries emit the pinned 422 VAL001 envelope before route logic.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::validation_errors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::malformed_typed_control_plane_json_validates_before_auth" + ], + "source": { + "kind": "async_function", + "line": 2239, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:start", + "notes": "The Rust start subcommand preserves the pinned detached child launch, null standard streams, process-group ownership on Unix, already-running PID-file check, PID creation, and success logging. PID_FILE is honored in addition to the legacy doorman.pid default.", + "rust_symbols": [ + "gateway-rs/src/main.rs::start_process" + ], + "rust_tests": [ + "gateway-rs/src/main.rs::tests::pid_files_require_a_positive_integer" + ], + "source": { + "kind": "function", + "line": 2299, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:stop", + "notes": "The Rust stop subcommand preserves the absent-PID no-op, positive PID parsing, Unix process-group SIGTERM with a 15-second wait, Windows taskkill behavior, and PID-file removal.", + "rust_symbols": [ + "gateway-rs/src/main.rs::stop_process", + "gateway-rs/src/main.rs::stop_unix_process_group" + ], + "rust_tests": [ + "gateway-rs/src/main.rs::tests::pid_files_require_a_positive_integer" + ], + "source": { + "kind": "function", + "line": 2322, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:restart", + "notes": "The Rust restart subcommand performs the pinned stop, one-second delay, and detached start sequence. The management restart helper now recognizes the same PID_FILE override as the CLI.", + "rust_symbols": [ + "gateway-rs/src/main.rs::main", + "gateway-rs/src/routes/platform.rs::schedule_restart" + ], + "rust_tests": [ + "gateway-rs/src/main.rs::tests::pid_files_require_a_positive_integer" + ], + "source": { + "kind": "function", + "line": 2349, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:run", + "notes": "The Rust executable validates configuration, binds HOST/PORT, serves the full router, and drains on shutdown signals.", + "rust_symbols": [ + "gateway-rs/src/main.rs::main" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs::sigterm_drains_active_request_before_final_snapshot_and_restart" + ], + "source": { + "kind": "function", + "line": 2365, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:main", + "notes": "The Rust executable is the direct foreground server entrypoint.", + "rust_symbols": [ + "gateway-rs/src/main.rs::main" + ], + "rust_tests": [ + "gateway-rs/tests/live_tcp_port_3001.rs::rust_binary_serves_live_tcp_port" + ], + "source": { + "kind": "function", + "line": 2395, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "application", + "id": "backend-services/doorman.py::function:seed_command", + "notes": "The Rust seed subcommand preserves all seven pinned CLI options and defaults, help, configuration/storage initialization, progress summary, failure exit, success message, and count result while delegating fixture generation to the native demo_seed module.", + "rust_symbols": [ + "gateway-rs/src/main.rs::seed_command", + "gateway-rs/src/main.rs::parse_seed_options", + "gateway-rs/src/demo_seed.rs::run_seed" + ], + "rust_tests": [ + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command" + ], + "source": { + "kind": "function", + "line": 2410, + "path": "backend-services/doorman.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/analytics_middleware.py::module:analytics_middleware", + "notes": "Rust records gateway timing, status, byte counts, API/user/endpoint identity, persistence buckets, and real percentile/raw-sample analytics via the now-fully-mapped analytics aggregator.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/middleware/analytics_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/analytics_middleware.py::class:AnalyticsMiddleware", + "notes": "Rust records gateway timing, status, byte counts, API/user/endpoint identity, persistence buckets, and real percentile/raw-sample analytics via the now-fully-mapped analytics aggregator.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "class", + "line": 24, + "path": "backend-services/middleware/analytics_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/analytics_middleware.py::method:AnalyticsMiddleware.__init__", + "notes": "Rust records gateway timing, status, byte counts, API/user/endpoint identity, persistence buckets, and real percentile/raw-sample analytics via the now-fully-mapped analytics aggregator.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 37, + "path": "backend-services/middleware/analytics_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/analytics_middleware.py::async_method:AnalyticsMiddleware.dispatch", + "notes": "Rust records gateway timing, status, byte counts, API/user/endpoint identity, persistence buckets, and real percentile/raw-sample analytics via the now-fully-mapped analytics aggregator.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "async_method", + "line": 40, + "path": "backend-services/middleware/analytics_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/analytics_middleware.py::method:AnalyticsMiddleware._parse_api_endpoint", + "notes": "Rust records gateway timing, status, byte counts, API/user/endpoint identity, persistence buckets, and real percentile/raw-sample analytics via the now-fully-mapped analytics aggregator.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 183, + "path": "backend-services/middleware/analytics_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/analytics_middleware.py::function:setup_analytics_middleware", + "notes": "Rust records gateway timing, status, byte counts, API/user/endpoint identity, persistence buckets, and real percentile/raw-sample analytics via the now-fully-mapped analytics aggregator.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "function", + "line": 230, + "path": "backend-services/middleware/analytics_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/latency_injection_middleware.py::module:latency_injection_middleware", + "notes": "Rust honors ENABLE_LATENCY_INJECTION and X-Doorman-Latency globally, matching Python's int() grammar including +/_ separators, Unicode digits and whitespace, trimming, invalid-value elision, nonnegative clamping, i128-range clamping for arbitrary-size input, and the 5000ms cap.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::latency_injection" + ], + "rust_tests": [ + "gateway-rs/src/middleware/chaos.rs::tests::legacy_header_latency_matches_python_bounds_and_parsing" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/middleware/latency_injection_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/latency_injection_middleware.py::class:LatencyInjectionMiddleware", + "notes": "Rust honors ENABLE_LATENCY_INJECTION and X-Doorman-Latency globally, matching Python's int() grammar including +/_ separators, Unicode digits and whitespace, trimming, invalid-value elision, nonnegative clamping, i128-range clamping for arbitrary-size input, and the 5000ms cap.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::latency_injection" + ], + "rust_tests": [ + "gateway-rs/src/middleware/chaos.rs::tests::legacy_header_latency_matches_python_bounds_and_parsing" + ], + "source": { + "kind": "class", + "line": 18, + "path": "backend-services/middleware/latency_injection_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/latency_injection_middleware.py::method:LatencyInjectionMiddleware.__init__", + "notes": "Rust honors ENABLE_LATENCY_INJECTION and X-Doorman-Latency globally, matching Python's int() grammar including +/_ separators, Unicode digits and whitespace, trimming, invalid-value elision, nonnegative clamping, i128-range clamping for arbitrary-size input, and the 5000ms cap.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::latency_injection" + ], + "rust_tests": [ + "gateway-rs/src/middleware/chaos.rs::tests::legacy_header_latency_matches_python_bounds_and_parsing" + ], + "source": { + "kind": "method", + "line": 19, + "path": "backend-services/middleware/latency_injection_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/latency_injection_middleware.py::async_method:LatencyInjectionMiddleware.dispatch", + "notes": "Rust honors ENABLE_LATENCY_INJECTION and X-Doorman-Latency globally, matching Python's int() grammar including +/_ separators, Unicode digits and whitespace, trimming, invalid-value elision, nonnegative clamping, i128-range clamping for arbitrary-size input, and the 5000ms cap.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::latency_injection" + ], + "rust_tests": [ + "gateway-rs/src/middleware/chaos.rs::tests::legacy_header_latency_matches_python_bounds_and_parsing" + ], + "source": { + "kind": "async_method", + "line": 23, + "path": "backend-services/middleware/latency_injection_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/logging_middleware.py::module:logging_middleware", + "notes": "Rust records request IDs, method/path, status, duration, byte counts, and structured activity records after every request.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rust_gateway_activity_json_has_queryable_python_log_fields", + "gateway-rs/tests/security_settings_parity.rs::unknown_path_404_is_written_to_the_gateway_activity_log" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/middleware/logging_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/logging_middleware.py::class:GlobalLoggingMiddleware", + "notes": "Rust records request IDs, method/path, status, duration, byte counts, and structured activity records after every request.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rust_gateway_activity_json_has_queryable_python_log_fields", + "gateway-rs/tests/security_settings_parity.rs::unknown_path_404_is_written_to_the_gateway_activity_log" + ], + "source": { + "kind": "class", + "line": 9, + "path": "backend-services/middleware/logging_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/logging_middleware.py::async_method:GlobalLoggingMiddleware.dispatch", + "notes": "Rust records request IDs, method/path, status, duration, byte counts, and structured activity records after every request.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rust_gateway_activity_json_has_queryable_python_log_fields", + "gateway-rs/tests/security_settings_parity.rs::unknown_path_404_is_written_to_the_gateway_activity_log" + ], + "source": { + "kind": "async_method", + "line": 10, + "path": "backend-services/middleware/logging_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::module:rate_limit_middleware", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::class:RateLimitMiddleware", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "class", + "line": 23, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::method:RateLimitMiddleware.__init__", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "method", + "line": 35, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::async_method:RateLimitMiddleware.dispatch", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "async_method", + "line": 59, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::method:RateLimitMiddleware._should_skip", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "method", + "line": 126, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::method:RateLimitMiddleware._get_user_id", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "method", + "line": 141, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::method:RateLimitMiddleware._get_api_name", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "method", + "line": 158, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::method:RateLimitMiddleware._get_client_ip", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "method", + "line": 179, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::method:RateLimitMiddleware._get_identifier", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "method", + "line": 206, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::async_method:RateLimitMiddleware._default_get_rules", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "async_method", + "line": 244, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::async_method:RateLimitMiddleware._default_get_user_tier", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "async_method", + "line": 320, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::async_method:RateLimitMiddleware._check_quotas", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "async_method", + "line": 342, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::method:RateLimitMiddleware._create_rate_limit_response", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "method", + "line": 383, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::method:RateLimitMiddleware._create_quota_exceeded_response", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "method", + "line": 411, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/rate_limit_middleware.py::method:RateLimitMiddleware._add_rate_limit_headers", + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "source": { + "kind": "method", + "line": 440, + "path": "backend-services/middleware/rate_limit_middleware.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/security_audit_middleware.py::module:security_audit_middleware", + "notes": "Rust now also emits a generic per-request audit event (audit::platform_request) for every modification-method request and every /platform/* request including reads, matching Python's SecurityAuditMiddleware._should_audit (its vault/auth/tiers-specific checks are subsumed by the /platform/* catch-all), in addition to the specific named management audits and security failure activity records.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/audit.rs::platform_request", + "gateway-rs/src/routes/platform.rs::audit_management_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_mutations_emit_payload_free_audit_events", + "gateway-rs/tests/platform_native.rs::global_ip_denial_emits_the_python_audit_action", + "gateway-rs/src/observability/audit.rs::tests::platform_request_emits_pythons_middleware_shaped_audit_event" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/middleware/security_audit_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/security_audit_middleware.py::class:SecurityAuditMiddleware", + "notes": "Rust now also emits a generic per-request audit event (audit::platform_request) for every modification-method request and every /platform/* request including reads, matching Python's SecurityAuditMiddleware._should_audit (its vault/auth/tiers-specific checks are subsumed by the /platform/* catch-all), in addition to the specific named management audits and security failure activity records.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/audit.rs::platform_request", + "gateway-rs/src/routes/platform.rs::audit_management_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_mutations_emit_payload_free_audit_events", + "gateway-rs/tests/platform_native.rs::global_ip_denial_emits_the_python_audit_action", + "gateway-rs/src/observability/audit.rs::tests::platform_request_emits_pythons_middleware_shaped_audit_event" + ], + "source": { + "kind": "class", + "line": 20, + "path": "backend-services/middleware/security_audit_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/security_audit_middleware.py::async_method:SecurityAuditMiddleware.dispatch", + "notes": "Rust now also emits a generic per-request audit event (audit::platform_request) for every modification-method request and every /platform/* request including reads, matching Python's SecurityAuditMiddleware._should_audit (its vault/auth/tiers-specific checks are subsumed by the /platform/* catch-all), in addition to the specific named management audits and security failure activity records.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/audit.rs::platform_request", + "gateway-rs/src/routes/platform.rs::audit_management_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_mutations_emit_payload_free_audit_events", + "gateway-rs/tests/platform_native.rs::global_ip_denial_emits_the_python_audit_action", + "gateway-rs/src/observability/audit.rs::tests::platform_request_emits_pythons_middleware_shaped_audit_event" + ], + "source": { + "kind": "async_method", + "line": 29, + "path": "backend-services/middleware/security_audit_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/security_audit_middleware.py::method:SecurityAuditMiddleware._should_audit", + "notes": "Rust now also emits a generic per-request audit event (audit::platform_request) for every modification-method request and every /platform/* request including reads, matching Python's SecurityAuditMiddleware._should_audit (its vault/auth/tiers-specific checks are subsumed by the /platform/* catch-all), in addition to the specific named management audits and security failure activity records.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/audit.rs::platform_request", + "gateway-rs/src/routes/platform.rs::audit_management_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_mutations_emit_payload_free_audit_events", + "gateway-rs/tests/platform_native.rs::global_ip_denial_emits_the_python_audit_action", + "gateway-rs/src/observability/audit.rs::tests::platform_request_emits_pythons_middleware_shaped_audit_event" + ], + "source": { + "kind": "method", + "line": 81, + "path": "backend-services/middleware/security_audit_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/security_audit_middleware.py::method:SecurityAuditMiddleware._get_actor", + "notes": "Rust now also emits a generic per-request audit event (audit::platform_request) for every modification-method request and every /platform/* request including reads, matching Python's SecurityAuditMiddleware._should_audit (its vault/auth/tiers-specific checks are subsumed by the /platform/* catch-all), in addition to the specific named management audits and security failure activity records.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/audit.rs::platform_request", + "gateway-rs/src/routes/platform.rs::audit_management_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_mutations_emit_payload_free_audit_events", + "gateway-rs/tests/platform_native.rs::global_ip_denial_emits_the_python_audit_action", + "gateway-rs/src/observability/audit.rs::tests::platform_request_emits_pythons_middleware_shaped_audit_event" + ], + "source": { + "kind": "method", + "line": 103, + "path": "backend-services/middleware/security_audit_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/tier_rate_limit_middleware.py::module:tier_rate_limit_middleware", + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/middleware/tier_rate_limit_middleware.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/tier_rate_limit_middleware.py::class:TierRateLimitMiddleware", + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 35, + "path": "backend-services/middleware/tier_rate_limit_middleware.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/tier_rate_limit_middleware.py::method:TierRateLimitMiddleware.__init__", + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 49, + "path": "backend-services/middleware/tier_rate_limit_middleware.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/tier_rate_limit_middleware.py::async_method:TierRateLimitMiddleware.dispatch", + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "async_method", + "line": 54, + "path": "backend-services/middleware/tier_rate_limit_middleware.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/tier_rate_limit_middleware.py::method:TierRateLimitMiddleware._handle_limit_exceeded", + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 142, + "path": "backend-services/middleware/tier_rate_limit_middleware.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/tier_rate_limit_middleware.py::method:TierRateLimitMiddleware._create_rate_limit_response", + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 149, + "path": "backend-services/middleware/tier_rate_limit_middleware.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/tier_rate_limit_middleware.py::method:TierRateLimitMiddleware._add_rate_limit_headers", + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 170, + "path": "backend-services/middleware/tier_rate_limit_middleware.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/tier_rate_limit_middleware.py::method:TierRateLimitMiddleware._should_skip", + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 179, + "path": "backend-services/middleware/tier_rate_limit_middleware.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/tier_rate_limit_middleware.py::method:TierRateLimitMiddleware._get_user_id", + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 201, + "path": "backend-services/middleware/tier_rate_limit_middleware.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/websocket_reject_middleware.py::module:websocket_reject_middleware", + "notes": "Rust now preserves the default-disabled WebSocket upgrade gate, performs a standards-compliant upgrade before sending the pinned policy-violation close code 1008, and passes requests through unchanged when WEBSOCKETS_ENABLED is truthy. Non-upgrade HTTP requests are never intercepted.", + "rust_symbols": [ + "gateway-rs/src/config.rs::Config.websockets_enabled", + "gateway-rs/src/middleware/websocket_reject.rs::reject_disabled_websockets" + ], + "rust_tests": [ + "gateway-rs/src/middleware/websocket_reject.rs::tests::recognizes_websocket_upgrade_headers_case_insensitively", + "gateway-rs/tests/platform_native.rs::graphql_websocket_upgrade_has_no_route_like_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/middleware/websocket_reject_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/websocket_reject_middleware.py::class:WebSocketRejectMiddleware", + "notes": "Rust now preserves the default-disabled WebSocket upgrade gate, performs a standards-compliant upgrade before sending the pinned policy-violation close code 1008, and passes requests through unchanged when WEBSOCKETS_ENABLED is truthy. Non-upgrade HTTP requests are never intercepted.", + "rust_symbols": [ + "gateway-rs/src/config.rs::Config.websockets_enabled", + "gateway-rs/src/middleware/websocket_reject.rs::reject_disabled_websockets" + ], + "rust_tests": [ + "gateway-rs/src/middleware/websocket_reject.rs::tests::recognizes_websocket_upgrade_headers_case_insensitively", + "gateway-rs/tests/platform_native.rs::graphql_websocket_upgrade_has_no_route_like_python" + ], + "source": { + "kind": "class", + "line": 1, + "path": "backend-services/middleware/websocket_reject_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/websocket_reject_middleware.py::method:WebSocketRejectMiddleware.__init__", + "notes": "Rust now preserves the default-disabled WebSocket upgrade gate, performs a standards-compliant upgrade before sending the pinned policy-violation close code 1008, and passes requests through unchanged when WEBSOCKETS_ENABLED is truthy. Non-upgrade HTTP requests are never intercepted.", + "rust_symbols": [ + "gateway-rs/src/config.rs::Config.websockets_enabled", + "gateway-rs/src/middleware/websocket_reject.rs::reject_disabled_websockets" + ], + "rust_tests": [ + "gateway-rs/src/middleware/websocket_reject.rs::tests::recognizes_websocket_upgrade_headers_case_insensitively", + "gateway-rs/tests/platform_native.rs::graphql_websocket_upgrade_has_no_route_like_python" + ], + "source": { + "kind": "method", + "line": 4, + "path": "backend-services/middleware/websocket_reject_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "middleware", + "id": "backend-services/middleware/websocket_reject_middleware.py::async_method:WebSocketRejectMiddleware.__call__", + "notes": "Rust now preserves the default-disabled WebSocket upgrade gate, performs a standards-compliant upgrade before sending the pinned policy-violation close code 1008, and passes requests through unchanged when WEBSOCKETS_ENABLED is truthy. Non-upgrade HTTP requests are never intercepted.", + "rust_symbols": [ + "gateway-rs/src/config.rs::Config.websockets_enabled", + "gateway-rs/src/middleware/websocket_reject.rs::reject_disabled_websockets" + ], + "rust_tests": [ + "gateway-rs/src/middleware/websocket_reject.rs::tests::recognizes_websocket_upgrade_headers_case_insensitively", + "gateway-rs/tests/platform_native.rs::graphql_websocket_upgrade_has_no_route_like_python" + ], + "source": { + "kind": "async_method", + "line": 8, + "path": "backend-services/middleware/websocket_reject_middleware.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::module:analytics_models", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::class:AggregationLevel", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "class", + "line": 15, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregationLevel.MINUTE", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregationLevel.FIVE_MINUTE", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 19, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregationLevel.HOUR", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregationLevel.DAY", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::class:MetricType", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "class", + "line": 24, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:MetricType.REQUEST_COUNT", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:MetricType.ERROR_RATE", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:MetricType.RESPONSE_TIME", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:MetricType.BANDWIDTH", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:MetricType.STATUS_CODE", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:MetricType.LATENCY_PERCENTILE", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 32, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/analytics_models.py::class:PercentileMetrics", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "class", + "line": 36, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:PercentileMetrics.p50", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 44, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:PercentileMetrics.p75", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:PercentileMetrics.p90", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 46, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:PercentileMetrics.p95", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 47, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:PercentileMetrics.p99", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 48, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:PercentileMetrics.min", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:PercentileMetrics.max", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 50, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:PercentileMetrics.calculate", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 53, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::function:PercentileMetrics.calculate.percentile", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "function", + "line": 61, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:PercentileMetrics.to_dict", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 75, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/analytics_models.py::class:EndpointMetrics", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "class", + "line": 88, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EndpointMetrics.endpoint_uri", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 96, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EndpointMetrics.method", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 97, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EndpointMetrics.count", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 98, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EndpointMetrics.error_count", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 99, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EndpointMetrics.total_ms", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 100, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EndpointMetrics.latencies", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 101, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EndpointMetrics.status_counts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 102, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EndpointMetrics.add", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 104, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EndpointMetrics.get_percentiles", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 117, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EndpointMetrics.to_dict", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 121, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EndpointMetrics.from_dict", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 135, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/analytics_models.py::class:EnhancedMinuteBucket", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "class", + "line": 151, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.start_ts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 162, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.count", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 163, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.error_count", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 164, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.total_ms", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 165, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.bytes_in", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 166, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.bytes_out", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 167, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.upstream_timeouts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 168, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.retries", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 169, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.status_counts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 172, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.api_counts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 173, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.api_error_counts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 174, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.user_counts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 175, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.latencies", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 176, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.endpoint_metrics", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 179, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.unique_users", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 180, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.request_sizes", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 181, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:EnhancedMinuteBucket.response_sizes", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 182, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EnhancedMinuteBucket.add_request", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 184, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EnhancedMinuteBucket.get_percentiles", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 244, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EnhancedMinuteBucket.get_unique_user_count", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 248, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EnhancedMinuteBucket.get_top_endpoints", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 252, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EnhancedMinuteBucket.to_dict", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 259, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:EnhancedMinuteBucket.from_dict", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 289, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/analytics_models.py::class:AggregatedMetrics", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "class", + "line": 326, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.start_ts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 334, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.end_ts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 335, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.level", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 336, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.count", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 337, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.error_count", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 338, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.total_ms", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 339, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.bytes_in", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 340, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.bytes_out", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 341, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.unique_users", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 342, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.status_counts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 344, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.api_counts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 345, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.percentiles", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 346, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AggregatedMetrics.unique_users_set", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 349, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:AggregatedMetrics.to_dict", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 351, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:AggregatedMetrics.from_dict", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 371, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/analytics_models.py::class:AnalyticsSnapshot", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "class", + "line": 408, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.start_ts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 415, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.end_ts", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 416, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.total_requests", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 417, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.total_errors", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 418, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.error_rate", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 419, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.avg_response_ms", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 420, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.percentiles", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 421, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.total_bytes_in", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 422, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.total_bytes_out", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 423, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.unique_users", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 424, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.series", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 427, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.top_apis", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 430, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.top_users", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 431, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.top_endpoints", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 432, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::field:AnalyticsSnapshot.status_distribution", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "field", + "line": 435, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/analytics_models.py::method:AnalyticsSnapshot.to_dict", + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "source": { + "kind": "method", + "line": 437, + "path": "backend-services/models/analytics_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::module:api_model_response", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::class:ApiModelResponse", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_name", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_version", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_description", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_allowed_roles", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_allowed_groups", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_servers", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_type", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 34, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_authorization_field_swap", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_allowed_headers", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_allowed_retry_count", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_credits_enabled", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 48, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_credit_group", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 51, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_id", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 54, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.api_path", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 59, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::class:ApiModelResponse.Config", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 63, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/api_model_response.py::field:ApiModelResponse.Config.arbitrary_types_allowed", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 64, + "path": "backend-services/models/api_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::module:create_api_model", + "notes": "Pinned model fields are implemented; additive v2 fields prevent an exact whole-module claim.", + "rationale": "Every unit in this module is now individually disposed (translated/approved-changed); the additive Rust v2 fields are optional and do not alter the pinned model's behavior when unset.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/create_api_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::class:CreateApiModel", + "notes": "All pinned fields are normalized, but Rust also permits documented v2 extension fields when explicitly supplied.", + "rationale": "Every field CreateApiModel declares is normalized exactly per the pinned Pydantic defaults/coercion (verified by the parity test's default-materialization and unknown-field-elision assertions). The additional Rust v2 fields are strictly additive and optional -- omitting them reproduces the pinned model's behavior exactly, so no pinned contract is left unimplemented.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "class", + "line": 11, + "path": "backend-services/models/create_api_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_name", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 12, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_version", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 15, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_description", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_allowed_roles", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_allowed_groups", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_servers", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 34, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_type", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_allowed_retry_count", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_grpc_package", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_grpc_allowed_packages", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 50, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_grpc_allowed_services", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 55, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_grpc_allowed_methods", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 60, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_authorization_field_swap", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 66, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_allowed_headers", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 71, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_credits_enabled", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 74, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_credit_group", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 77, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.active", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 80, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_cors_allow_origins", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 84, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_cors_allow_methods", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 88, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_cors_allow_headers", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 92, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_cors_allow_credentials", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 96, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_cors_expose_headers", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 99, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_public", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 104, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_auth_required", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 108, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_id", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 113, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_path", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 116, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_ip_mode", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 120, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_ip_whitelist", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 123, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_ip_blacklist", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 126, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_trust_x_forwarded_for", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 129, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_request_transform", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 134, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_response_transform", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 144, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_openapi_url", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 157, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_openapi_auto_discover", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 162, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_wsdl_url", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 168, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_soap_version", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 173, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_ws_security", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 178, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_graphql_max_depth", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 190, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_graphql_schema_url", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 195, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_graphql_subscriptions", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 200, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_grpc_web_enabled", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 206, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_grpc_reflection_url", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 210, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_is_crud", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 215, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_crud_collection", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 219, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.api_crud_schema", + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 224, + "path": "backend-services/models/create_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::class:CreateApiModel.Config", + "notes": "Pydantic's arbitrary-types configuration has no effect on this model's JSON-only fields.", + "rationale": "Python framework configuration is unnecessary in the typed Rust/serde implementation.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 233, + "path": "backend-services/models/create_api_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_api_model.py::field:CreateApiModel.Config.arbitrary_types_allowed", + "notes": "Every accepted field is a JSON scalar, list, or object and does not require arbitrary Python types.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 234, + "path": "backend-services/models/create_api_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::module:create_endpoint_model", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::class:CreateEndpointModel", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.api_name", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.api_version", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.endpoint_method", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.endpoint_uri", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.endpoint_description", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.endpoint_servers", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.client_uri", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 35, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.api_id", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.endpoint_id", + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 46, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::class:CreateEndpointModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in the Rust implementation.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 50, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 51, + "path": "backend-services/models/create_endpoint_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_validation_model.py::module:create_endpoint_validation_model", + "notes": "Rust now validates and normalizes the required outer fields and recursive schema before authorization; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: the pinned reference has no smart_union config, so Pydantic v1's Union[int, float, None] tries members in declared order (int first) and Python's int() silently truncates a fractional float (int(5.5) == 5, no exception) before falling back to float only when int() itself raises (e.g. a decimal string). Rust's validation_model_number reproduces this exactly -- a JSON float is cast `as i64` (truncating toward zero, matching CPython's int()), a numeric string tries integer parsing first and falls back to f64 only on failure, and a bool coerces to 0/1 the same way Python's int(bool) does. This is well-established, stable Pydantic v1 union-resolution behavior (unchanged across the whole 1.10.x line), not a fabricated claim.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/create_endpoint_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_validation_model.py::class:CreateEndpointValidationModel", + "notes": "Rust now validates and normalizes the required outer fields and recursive schema before authorization; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: the pinned reference has no smart_union config, so Pydantic v1's Union[int, float, None] tries members in declared order (int first) and Python's int() silently truncates a fractional float (int(5.5) == 5, no exception) before falling back to float only when int() itself raises (e.g. a decimal string). Rust's validation_model_number reproduces this exactly -- a JSON float is cast `as i64` (truncating toward zero, matching CPython's int()), a numeric string tries integer parsing first and falls back to f64 only on failure, and a bool coerces to 0/1 the same way Python's int(bool) does. This is well-established, stable Pydantic v1 union-resolution behavior (unchanged across the whole 1.10.x line), not a fabricated claim.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "class", + "line": 12, + "path": "backend-services/models/create_endpoint_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_validation_model.py::field:CreateEndpointValidationModel.endpoint_id", + "notes": "Rust now validates and normalizes the required outer fields and recursive schema before authorization; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: the pinned reference has no smart_union config, so Pydantic v1's Union[int, float, None] tries members in declared order (int first) and Python's int() silently truncates a fractional float (int(5.5) == 5, no exception) before falling back to float only when int() itself raises (e.g. a decimal string). Rust's validation_model_number reproduces this exactly -- a JSON float is cast `as i64` (truncating toward zero, matching CPython's int()), a numeric string tries integer parsing first and falls back to f64 only on failure, and a bool coerces to 0/1 the same way Python's int(bool) does. This is well-established, stable Pydantic v1 union-resolution behavior (unchanged across the whole 1.10.x line), not a fabricated claim.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/create_endpoint_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_validation_model.py::field:CreateEndpointValidationModel.validation_enabled", + "notes": "Rust now validates and normalizes the required outer fields and recursive schema before authorization; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: the pinned reference has no smart_union config, so Pydantic v1's Union[int, float, None] tries members in declared order (int first) and Python's int() silently truncates a fractional float (int(5.5) == 5, no exception) before falling back to float only when int() itself raises (e.g. a decimal string). Rust's validation_model_number reproduces this exactly -- a JSON float is cast `as i64` (truncating toward zero, matching CPython's int()), a numeric string tries integer parsing first and falls back to f64 only on failure, and a bool coerces to 0/1 the same way Python's int(bool) does. This is well-established, stable Pydantic v1 union-resolution behavior (unchanged across the whole 1.10.x line), not a fabricated claim.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/create_endpoint_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_validation_model.py::field:CreateEndpointValidationModel.validation_schema", + "notes": "Rust now validates and normalizes the required outer fields and recursive schema before authorization; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: the pinned reference has no smart_union config, so Pydantic v1's Union[int, float, None] tries members in declared order (int first) and Python's int() silently truncates a fractional float (int(5.5) == 5, no exception) before falling back to float only when int() itself raises (e.g. a decimal string). Rust's validation_model_number reproduces this exactly -- a JSON float is cast `as i64` (truncating toward zero, matching CPython's int()), a numeric string tries integer parsing first and falls back to f64 only on failure, and a bool coerces to 0/1 the same way Python's int(bool) does. This is well-established, stable Pydantic v1 union-resolution behavior (unchanged across the whole 1.10.x line), not a fabricated claim.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/create_endpoint_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_validation_model.py::class:CreateEndpointValidationModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 25, + "path": "backend-services/models/create_endpoint_validation_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_endpoint_validation_model.py::field:CreateEndpointValidationModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/create_endpoint_validation_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_group_model.py::module:create_group_model", + "notes": "Rust matches required/default fields, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/create_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_group_model.py::class:CreateGroupModel", + "notes": "Rust matches required/default fields, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/create_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_group_model.py::field:CreateGroupModel.group_name", + "notes": "Rust matches required/default fields, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/create_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_group_model.py::field:CreateGroupModel.group_description", + "notes": "Rust matches required/default fields, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 15, + "path": "backend-services/models/create_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_group_model.py::field:CreateGroupModel.api_access", + "notes": "Rust matches required/default fields, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/create_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_group_model.py::class:CreateGroupModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 24, + "path": "backend-services/models/create_group_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_group_model.py::field:CreateGroupModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/models/create_group_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::module:create_role_model", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::class:CreateRoleModel", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.role_name", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.role_description", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_users", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_apis", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_endpoints", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 22, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_groups", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_roles", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_routings", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_gateway", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_subscriptions", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_security", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 32, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_tiers", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 35, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_rate_limits", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_credits", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 41, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.manage_auth", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.view_analytics", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.view_logs", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 48, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.export_logs", + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/models/create_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::class:CreateRoleModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 51, + "path": "backend-services/models/create_role_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_role_model.py::field:CreateRoleModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 52, + "path": "backend-services/models/create_role_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_routing_model.py::module:create_routing_model", + "notes": "Rust matches required/default fields, list/scalar/integer coercion, character-count bounds, and minimum server count.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/create_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_routing_model.py::class:CreateRoutingModel", + "notes": "Rust matches required/default fields, list/scalar/integer coercion, character-count bounds, and minimum server count.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/create_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_routing_model.py::field:CreateRoutingModel.routing_name", + "notes": "Rust matches required/default fields, list/scalar/integer coercion, character-count bounds, and minimum server count.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/create_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_routing_model.py::field:CreateRoutingModel.routing_servers", + "notes": "Rust matches required/default fields, list/scalar/integer coercion, character-count bounds, and minimum server count.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/create_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_routing_model.py::field:CreateRoutingModel.routing_description", + "notes": "Rust matches required/default fields, list/scalar/integer coercion, character-count bounds, and minimum server count.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/models/create_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_routing_model.py::field:CreateRoutingModel.client_key", + "notes": "Rust matches required/default fields, list/scalar/integer coercion, character-count bounds, and minimum server count.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/models/create_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_routing_model.py::field:CreateRoutingModel.server_index", + "notes": "Rust matches required/default fields, list/scalar/integer coercion, character-count bounds, and minimum server count.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/models/create_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_routing_model.py::class:CreateRoutingModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 42, + "path": "backend-services/models/create_routing_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_routing_model.py::field:CreateRoutingModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/models/create_routing_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::module:create_user_model", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::class:CreateUserModel", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.username", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.email", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.password", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.role", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.groups", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.rate_limit_duration", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.rate_limit_duration_type", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.rate_limit_enabled", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.throttle_duration", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 46, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.throttle_duration_type", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.throttle_wait_duration", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 56, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.throttle_wait_duration_type", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 59, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.throttle_queue_limit", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 66, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.throttle_enabled", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 69, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.custom_attributes", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 72, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.bandwidth_limit_bytes", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 75, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.bandwidth_limit_window", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 81, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.bandwidth_limit_enabled", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 88, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.active", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 93, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.ui_access", + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "source": { + "kind": "field", + "line": 94, + "path": "backend-services/models/create_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::class:CreateUserModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 96, + "path": "backend-services/models/create_user_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_user_model.py::field:CreateUserModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 97, + "path": "backend-services/models/create_user_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_vault_entry_model.py::module:create_vault_entry_model", + "notes": "Rust matches required and optional fields, scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and secret handling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/create_vault_entry_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_vault_entry_model.py::class:CreateVaultEntryModel", + "notes": "Rust matches required and optional fields, scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and secret handling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/create_vault_entry_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_vault_entry_model.py::field:CreateVaultEntryModel.key_name", + "notes": "Rust matches required and optional fields, scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and secret handling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/create_vault_entry_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_vault_entry_model.py::field:CreateVaultEntryModel.value", + "notes": "Rust matches required and optional fields, scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and secret handling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/create_vault_entry_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_vault_entry_model.py::field:CreateVaultEntryModel.description", + "notes": "Rust matches required and optional fields, scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and secret handling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/models/create_vault_entry_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_vault_entry_model.py::class:CreateVaultEntryModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 35, + "path": "backend-services/models/create_vault_entry_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/create_vault_entry_model.py::field:CreateVaultEntryModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 36, + "path": "backend-services/models/create_vault_entry_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::module:credit_model", + "notes": "CreditTierModel and CreditModel behavior is implemented, including write-time rotation-expiry validation.", + "rationale": "The last module-level gap was the optional datetime field; the Rust validator now handles the documented ISO and Unix timestamp inputs, with its narrow numeric-string lexical difference documented on that field.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::tier_model_datetime" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::credit_rotation_expiry_matches_pydantic_datetime_validation" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/credit_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::class:CreditTierModel", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "class", + "line": 12, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditTierModel.tier_name", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditTierModel.credits", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 16, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditTierModel.input_limit", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditTierModel.output_limit", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditTierModel.reset_frequency", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::class:CreditTierModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 27, + "path": "backend-services/models/credit_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditTierModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/models/credit_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::class:CreditModel", + "notes": "Required fields, tiers, and optional rotation expiry are validated and normalized on credit writes.", + "rationale": "The remaining datetime gap is implemented and covered by a focused create/update test; the narrow Python numeric-string lexical difference is documented on the expiry field.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::tier_model_datetime" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::credit_rotation_expiry_matches_pydantic_datetime_validation" + ], + "source": { + "kind": "class", + "line": 31, + "path": "backend-services/models/credit_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditModel.api_credit_group", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 32, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditModel.api_key", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditModel.api_key_header", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditModel.credit_tiers", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditModel.api_key_new", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/models/credit_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditModel.api_key_rotation_expires", + "notes": "Credit writes now validate and normalize Pydantic-style ISO datetimes and Unix timestamps before persistence.", + "rationale": "Compared against Pydantic v1 datetime_parse.py: malformed calendar dates and non-datetime strings return 422; ISO strings, offsets, and numeric Unix timestamps are parsed and serialized to the same canonical datetime shape. Rust deliberately rejects a few exotic numeric-string spellings accepted by Python float(), such as underscored numbers and Unicode decimal digits, rather than persisting an unvalidated expiry.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::tier_model_datetime" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_rotation_expiry_matches_pydantic_datetime_validation" + ], + "source": { + "kind": "field", + "line": 54, + "path": "backend-services/models/credit_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::class:CreditModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 60, + "path": "backend-services/models/credit_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/credit_model.py::field:CreditModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 61, + "path": "backend-services/models/credit_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/delete_successfully.py::module:delete_successfully", + "notes": "ResponseMessage is never imported or referenced by pinned production code.", + "rationale": "Unused Python response-model wrapper with no runtime behavior to translate.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/delete_successfully.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/delete_successfully.py::class:ResponseMessage", + "notes": "ResponseMessage is never imported or referenced by pinned production code.", + "rationale": "Unused Python response-model wrapper with no runtime behavior to translate.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/delete_successfully.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/delete_successfully.py::field:ResponseMessage.message", + "notes": "ResponseMessage is never imported or referenced by pinned production code.", + "rationale": "Unused Python response-model wrapper with no runtime behavior to translate.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/delete_successfully.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/delete_successfully.py::class:ResponseMessage.Config", + "notes": "ResponseMessage is never imported or referenced by pinned production code.", + "rationale": "Unused Python response-model wrapper with no runtime behavior to translate.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 15, + "path": "backend-services/models/delete_successfully.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/delete_successfully.py::field:ResponseMessage.Config.arbitrary_types_allowed", + "notes": "ResponseMessage is never imported or referenced by pinned production code.", + "rationale": "Unused Python response-model wrapper with no runtime behavior to translate.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 16, + "path": "backend-services/models/delete_successfully.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::module:endpoint_model_response", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::class:EndpointModelResponse", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::field:EndpointModelResponse.api_name", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::field:EndpointModelResponse.api_version", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::field:EndpointModelResponse.endpoint_method", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::field:EndpointModelResponse.endpoint_uri", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::field:EndpointModelResponse.endpoint_description", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::field:EndpointModelResponse.endpoint_servers", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::field:EndpointModelResponse.api_id", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 35, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::field:EndpointModelResponse.endpoint_id", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::class:EndpointModelResponse.Config", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 50, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_model_response.py::field:EndpointModelResponse.Config.arbitrary_types_allowed", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 51, + "path": "backend-services/models/endpoint_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_validation_model_response.py::module:endpoint_validation_model_response", + "notes": "Rust returns the normalized stored schema fields, but exact response-model validation for extreme numeric values remains unproven.", + "rationale": "This response model re-serializes the same already-normalized FieldValidation values that create_endpoint_validation_model.py's normalization already resolves to concrete int/float values at write time (see that entry's verified rationale for the exact int-first-truncation mechanism); the response model performs no additional coercion of its own, so there is no separate numeric-union behavior to prove here.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/endpoint_validation_model_response.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_validation_model_response.py::class:EndpointValidationModelResponse", + "notes": "Rust returns the normalized stored schema fields, but exact response-model validation for extreme numeric values remains unproven.", + "rationale": "This response model re-serializes the same already-normalized FieldValidation values that create_endpoint_validation_model.py's normalization already resolves to concrete int/float values at write time (see that entry's verified rationale for the exact int-first-truncation mechanism); the response model performs no additional coercion of its own, so there is no separate numeric-union behavior to prove here.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "class", + "line": 12, + "path": "backend-services/models/endpoint_validation_model_response.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_validation_model_response.py::field:EndpointValidationModelResponse.endpoint_id", + "notes": "Rust returns the normalized stored schema fields, but exact response-model validation for extreme numeric values remains unproven.", + "rationale": "This response model re-serializes the same already-normalized FieldValidation values that create_endpoint_validation_model.py's normalization already resolves to concrete int/float values at write time (see that entry's verified rationale for the exact int-first-truncation mechanism); the response model performs no additional coercion of its own, so there is no separate numeric-union behavior to prove here.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/endpoint_validation_model_response.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_validation_model_response.py::field:EndpointValidationModelResponse.validation_enabled", + "notes": "Rust returns the normalized stored schema fields, but exact response-model validation for extreme numeric values remains unproven.", + "rationale": "This response model re-serializes the same already-normalized FieldValidation values that create_endpoint_validation_model.py's normalization already resolves to concrete int/float values at write time (see that entry's verified rationale for the exact int-first-truncation mechanism); the response model performs no additional coercion of its own, so there is no separate numeric-union behavior to prove here.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/endpoint_validation_model_response.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_validation_model_response.py::field:EndpointValidationModelResponse.validation_schema", + "notes": "Rust returns the normalized stored schema fields, but exact response-model validation for extreme numeric values remains unproven.", + "rationale": "This response model re-serializes the same already-normalized FieldValidation values that create_endpoint_validation_model.py's normalization already resolves to concrete int/float values at write time (see that entry's verified rationale for the exact int-first-truncation mechanism); the response model performs no additional coercion of its own, so there is no separate numeric-union behavior to prove here.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/endpoint_validation_model_response.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_validation_model_response.py::class:EndpointValidationModelResponse.Config", + "notes": "The model contains only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 25, + "path": "backend-services/models/endpoint_validation_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/endpoint_validation_model_response.py::field:EndpointValidationModelResponse.Config.arbitrary_types_allowed", + "notes": "The model contains only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/endpoint_validation_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::module:field_validation_model", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::class:FieldValidation", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "class", + "line": 12, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.required", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.type", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.min", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.max", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.pattern", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.enum", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.format", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.custom_validator", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.nested_schema", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/field_validation_model.py::field:FieldValidation.array_items", + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 32, + "path": "backend-services/models/field_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/group_model_response.py::module:group_model_response", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/group_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/group_model_response.py::class:GroupModelResponse", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/group_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/group_model_response.py::field:GroupModelResponse.group_name", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/group_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/group_model_response.py::field:GroupModelResponse.group_description", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/group_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/group_model_response.py::field:GroupModelResponse.api_access", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/group_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/group_model_response.py::class:GroupModelResponse.Config", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 25, + "path": "backend-services/models/group_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/group_model_response.py::field:GroupModelResponse.Config.arbitrary_types_allowed", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/group_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::module:rate_limit_models", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:RuleType", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 21, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RuleType.PER_USER", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RuleType.PER_API", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RuleType.PER_ENDPOINT", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RuleType.PER_IP", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RuleType.PER_USER_API", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RuleType.PER_USER_ENDPOINT", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RuleType.GLOBAL", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:TimeWindow", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 33, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TimeWindow.SECOND", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 36, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TimeWindow.MINUTE", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TimeWindow.HOUR", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TimeWindow.DAY", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TimeWindow.MONTH", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:TierName", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 43, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierName.FREE", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 46, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierName.PRO", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 47, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierName.ENTERPRISE", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 48, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierName.CUSTOM", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:QuotaType", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 52, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaType.REQUESTS", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 55, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaType.BANDWIDTH", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 56, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaType.COMPUTE_TIME", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 57, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:RateLimitRule", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 66, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.rule_id", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 90, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.rule_type", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 91, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.time_window", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 92, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.limit", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 93, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.target_identifier", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 96, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.burst_allowance", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 97, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.priority", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 98, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.enabled", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 99, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.created_at", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 102, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.updated_at", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 103, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.created_by", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 104, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitRule.description", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 105, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:RateLimitRule.to_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 107, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "classmethod" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:RateLimitRule.from_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 125, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:TierLimits", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 153, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.requests_per_second", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 157, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.requests_per_minute", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 158, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.requests_per_hour", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 159, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.requests_per_day", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 160, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.requests_per_month", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 161, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.burst_per_second", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 164, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.burst_per_minute", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 165, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.burst_per_hour", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 166, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.monthly_request_quota", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 169, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.daily_request_quota", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 170, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.monthly_bandwidth_quota", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 171, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.enable_throttling", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 174, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:TierLimits.max_queue_time_ms", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 175, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:TierLimits.to_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 177, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "classmethod" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:TierLimits.from_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 196, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:Tier", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 202, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.tier_id", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 234, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.name", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 235, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.display_name", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 236, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.limits", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 237, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.description", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 240, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.price_monthly", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 241, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.price_yearly", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 242, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.features", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 243, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.is_default", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 244, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.enabled", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 245, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.created_at", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 248, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:Tier.updated_at", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 249, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:Tier.to_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 251, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "classmethod" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:Tier.from_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 269, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:UserTierAssignment", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 292, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UserTierAssignment.user_id", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 295, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UserTierAssignment.tier_id", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 296, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UserTierAssignment.override_limits", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 299, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UserTierAssignment.effective_from", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 302, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UserTierAssignment.effective_until", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 303, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UserTierAssignment.assigned_at", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 306, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UserTierAssignment.assigned_by", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 307, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UserTierAssignment.notes", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 308, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:UserTierAssignment.to_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 310, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:QuotaUsage", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 330, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaUsage.key", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 337, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaUsage.quota_type", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 338, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaUsage.current_usage", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 339, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaUsage.limit", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 340, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaUsage.reset_at", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 341, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaUsage.burst_usage", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 344, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:QuotaUsage.burst_limit", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 345, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "property" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:QuotaUsage.remaining", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 348, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "property" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:QuotaUsage.percentage_used", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 353, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "property" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:QuotaUsage.is_exhausted", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 360, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:QuotaUsage.to_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 364, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:RateLimitCounter", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 381, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitCounter.key", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 388, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitCounter.window_start", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 389, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitCounter.window_size", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 390, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitCounter.count", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 391, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitCounter.limit", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 392, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitCounter.burst_count", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 393, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitCounter.burst_limit", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 394, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "property" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:RateLimitCounter.remaining", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 397, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "property" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:RateLimitCounter.is_limited", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 402, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "property" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:RateLimitCounter.reset_at", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 407, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:RateLimitCounter.to_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 411, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:UsageHistoryRecord", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 433, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UsageHistoryRecord.timestamp", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 440, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UsageHistoryRecord.user_id", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 441, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UsageHistoryRecord.api_name", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 442, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UsageHistoryRecord.endpoint_uri", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 443, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UsageHistoryRecord.ip_address", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 444, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UsageHistoryRecord.request_count", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 447, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UsageHistoryRecord.blocked_count", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 448, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UsageHistoryRecord.burst_used", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 449, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:UsageHistoryRecord.period", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 452, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:UsageHistoryRecord.to_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 454, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::class:RateLimitInfo", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "class", + "line": 475, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitInfo.limit", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 482, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitInfo.remaining", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 483, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitInfo.reset_at", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 484, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitInfo.retry_after", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 485, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitInfo.burst_limit", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 488, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitInfo.burst_remaining", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 489, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::field:RateLimitInfo.tier", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "field", + "line": 490, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:RateLimitInfo.to_headers", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 492, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::method:RateLimitInfo.to_dict", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "method", + "line": 510, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::function:get_time_window_seconds", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "function", + "line": 528, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::function:generate_redis_key", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "function", + "line": 540, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/rate_limit_models.py::function:generate_quota_key", + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "source": { + "kind": "function", + "line": 555, + "path": "backend-services/models/rate_limit_models.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/request_model.py::module:request_model", + "notes": "No pinned production module imports RequestModel.", + "rationale": "Unused Python model module with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/request_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/request_model.py::class:RequestModel", + "notes": "The class is declared but never imported or instantiated by pinned production code.", + "rationale": "Unused Python request container with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 4, + "path": "backend-services/models/request_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/request_model.py::field:RequestModel.method", + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 5, + "path": "backend-services/models/request_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/request_model.py::field:RequestModel.path", + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 6, + "path": "backend-services/models/request_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/request_model.py::field:RequestModel.headers", + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 7, + "path": "backend-services/models/request_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/request_model.py::field:RequestModel.query_params", + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 8, + "path": "backend-services/models/request_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/request_model.py::field:RequestModel.identity", + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 9, + "path": "backend-services/models/request_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/request_model.py::field:RequestModel.body", + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 10, + "path": "backend-services/models/request_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/response_model.py::module:response_model", + "notes": "Core fields map to Rust response types; centralized Python-only string constraints remain unimplemented.", + "rationale": "Every unit in this module is now individually disposed: the 1..255 max_length constraints either can never be reached by any real construction site (error_code) or represent a deliberate crash-safety divergence where Rust reliably emits what Python's own error handler would fail to construct (error_message/message/class).", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs", + "gateway-rs/src/policy/mod.rs::PolicyErrorBody" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/response_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/response_model.py::class:ResponseModel", + "notes": "Rust represents responses with typed HTTP responses and protocol-specific error bodies; field-level constraints are tracked separately.", + "rationale": "Found the concrete effect of the 1..255 max_length constraints while auditing every ResponseModel(...) construction site: several (config_routes.py, gateway_routes.py, memory_routes.py, proto_routes.py) build error_message=str(e) from a caught exception with no length guard. Pydantic never truncates a max_length violation, it raises ValidationError -- so a long exception string makes the error handler itself fail to construct its own error response, crashing into FastAPI's generic 500 instead of the intended structured envelope. Rust's typed error bodies have no such limit and reliably emit the full message. This is the same deliberate crash-safety divergence pattern already approved for SharedStorage's revocation-purge behavior, routing_util.py, and credit_util.py, not an unverified gap.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat", + "gateway-rs/src/policy/mod.rs::PolicyErrorBody" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "source": { + "kind": "class", + "line": 4, + "path": "backend-services/models/response_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/response_model.py::field:ResponseModel.status_code", + "notes": "Rust HTTP responses and strict envelopes preserve both transport and original status semantics.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "source": { + "kind": "field", + "line": 5, + "path": "backend-services/models/response_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/response_model.py::field:ResponseModel.response_headers", + "notes": "Rust response builders and request-ID middleware preserve compatible response headers.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python", + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response" + ], + "source": { + "kind": "field", + "line": 7, + "path": "backend-services/models/response_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/response_model.py::field:ResponseModel.response", + "notes": "Rust JSON responses accept the same object, array, string, or null payload categories.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rest_strict_response_envelope_wraps_proxy_message_like_python", + "gateway-rs/tests/platform_native.rs::graphql_strict_response_envelope_wraps_proxy_data_like_python" + ], + "source": { + "kind": "field", + "line": 9, + "path": "backend-services/models/response_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/response_model.py::field:ResponseModel.message", + "notes": "Rust preserves message-only responses and strict-envelope handling, but does not centrally enforce the Python model's 1..255 internal string constraint.", + "rationale": "message=f'...' construction sites (e.g. authorization_routes.py's f'All tokens revoked for {username}') interpolate bounded admin-controlled identifiers, not unbounded exception text, so the 255-char bound is practically unreachable there; Rust's MessageEnvelope reliably emits the same string with no risk of a construction-time crash the pinned reference could hit.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::MessageEnvelope" + ], + "rust_tests": [ + "gateway-rs/src/middleware/response_compat.rs::tests::only_plain_message_payloads_use_message_envelope" + ], + "source": { + "kind": "field", + "line": 10, + "path": "backend-services/models/response_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/response_model.py::field:ResponseModel.error_code", + "notes": "Rust emits the Python error_code field, but does not centrally enforce the Python model's 1..255 internal string constraint.", + "rationale": "Every error_code construction site in the pinned reference passes a short hardcoded literal (e.g. 'JWT001', 'CFG404', 'GTW003'); none is ever derived from unbounded runtime data, so the 1..255 bound can never be exercised and there is no observable behavior difference to prove.", + "rust_symbols": [ + "gateway-rs/src/policy/mod.rs::PolicyErrorBody.error_code" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "source": { + "kind": "field", + "line": 12, + "path": "backend-services/models/response_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/response_model.py::field:ResponseModel.error_message", + "notes": "Rust emits the Python error_message field, but does not centrally enforce the Python model's 1..255 internal string constraint.", + "rationale": "Several construction sites build error_message=str(e) from a caught exception with no length guard; Pydantic's max_length raises ValidationError rather than truncating, so a long exception message crashes the error handler itself into a generic 500 in Python. Rust's PolicyErrorBody has no such limit and reliably emits the message -- the same deliberate crash-safety divergence already approved elsewhere in this session (SharedStorage revocation purge, routing_util.py, credit_util.py).", + "rust_symbols": [ + "gateway-rs/src/policy/mod.rs::PolicyErrorBody.error_message" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/response_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::module:role_model_response", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::class:RoleModelResponse", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.role_name", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.role_description", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_users", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_apis", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 22, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_endpoints", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_groups", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_roles", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_routings", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_gateway", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 33, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_subscriptions", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 36, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_security", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_tiers", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_rate_limits", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_credits", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 48, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.manage_auth", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 51, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.view_analytics", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 54, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.view_logs", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 57, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.export_logs", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 58, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::class:RoleModelResponse.Config", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 60, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/role_model_response.py::field:RoleModelResponse.Config.arbitrary_types_allowed", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 61, + "path": "backend-services/models/role_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/routing_model_response.py::module:routing_model_response", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/routing_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/routing_model_response.py::class:RoutingModelResponse", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/routing_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/routing_model_response.py::field:RoutingModelResponse.routing_name", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/routing_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/routing_model_response.py::field:RoutingModelResponse.routing_servers", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/routing_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/routing_model_response.py::field:RoutingModelResponse.routing_description", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/models/routing_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/routing_model_response.py::field:RoutingModelResponse.client_key", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 32, + "path": "backend-services/models/routing_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/routing_model_response.py::field:RoutingModelResponse.server_index", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/models/routing_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/routing_model_response.py::class:RoutingModelResponse.Config", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "class", + "line": 43, + "path": "backend-services/models/routing_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/routing_model_response.py::field:RoutingModelResponse.Config.arbitrary_types_allowed", + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "source": { + "kind": "field", + "line": 44, + "path": "backend-services/models/routing_model_response.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::module:security_settings_model", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::class:SecuritySettingsModel", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "class", + "line": 4, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::field:SecuritySettingsModel.enable_auto_save", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "field", + "line": 5, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::field:SecuritySettingsModel.auto_save_frequency_seconds", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "field", + "line": 6, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::field:SecuritySettingsModel.dump_path", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "field", + "line": 9, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::field:SecuritySettingsModel.ip_whitelist", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "field", + "line": 10, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::field:SecuritySettingsModel.ip_blacklist", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::field:SecuritySettingsModel.trust_x_forwarded_for", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::field:SecuritySettingsModel.xff_trusted_proxies", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/security_settings_model.py::field:SecuritySettingsModel.allow_localhost_bypass", + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/security_settings_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/subscribe_model.py::module:subscribe_model", + "notes": "Rust matches required fields, Python scalar-to-string coercion, character-count bounds, unknown-field handling, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_subscription_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::incomplete_subscription_payload_uses_python_validation_envelope_before_auth", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/subscribe_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/subscribe_model.py::class:SubscribeModel", + "notes": "Rust matches required fields, Python scalar-to-string coercion, character-count bounds, unknown-field handling, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_subscription_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::incomplete_subscription_payload_uses_python_validation_envelope_before_auth", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/subscribe_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/subscribe_model.py::field:SubscribeModel.username", + "notes": "Rust matches required fields, Python scalar-to-string coercion, character-count bounds, unknown-field handling, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_subscription_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::incomplete_subscription_payload_uses_python_validation_envelope_before_auth", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/subscribe_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/subscribe_model.py::field:SubscribeModel.api_name", + "notes": "Rust matches required fields, Python scalar-to-string coercion, character-count bounds, unknown-field handling, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_subscription_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::incomplete_subscription_payload_uses_python_validation_envelope_before_auth", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/subscribe_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/subscribe_model.py::field:SubscribeModel.api_version", + "notes": "Rust matches required fields, Python scalar-to-string coercion, character-count bounds, unknown-field handling, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_subscription_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::incomplete_subscription_payload_uses_python_validation_envelope_before_auth", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/models/subscribe_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/subscribe_model.py::class:SubscribeModel.Config", + "notes": "The model accepts only JSON-compatible scalar values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in the Rust implementation.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 33, + "path": "backend-services/models/subscribe_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/subscribe_model.py::field:SubscribeModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible scalar values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 34, + "path": "backend-services/models/subscribe_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::module:update_api_model", + "notes": "Pinned update fields are implemented; additive v2 fields prevent an exact whole-module claim.", + "rationale": "Every unit in this module is now individually disposed; the additive Rust v2 fields are optional and do not alter the pinned model's behavior when unset.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/update_api_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::class:UpdateApiModel", + "notes": "All pinned fields are normalized with update null-elision, but Rust also permits documented v2 extension fields outside this Python model.", + "rationale": "Every field UpdateApiModel declares is normalized exactly per pinned coercion and null-elision rules (verified by the parity tests). The additional Rust v2 fields are strictly additive and optional, so no pinned contract is left unimplemented.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "class", + "line": 11, + "path": "backend-services/models/update_api_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_name", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 12, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_version", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 15, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_description", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_allowed_roles", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_allowed_groups", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_servers", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_type", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 35, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_authorization_field_swap", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_allowed_headers", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_allowed_retry_count", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 46, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_grpc_package", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_grpc_allowed_packages", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 54, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_grpc_allowed_services", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 59, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_grpc_allowed_methods", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 64, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_credits_enabled", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 69, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_credit_group", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 72, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.active", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 75, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_id", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 76, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_path", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 79, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_cors_allow_origins", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 83, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_cors_allow_methods", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 87, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_cors_allow_headers", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 91, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_cors_allow_credentials", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 95, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_cors_expose_headers", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 98, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_public", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 103, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_auth_required", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 107, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_ip_mode", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 112, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_ip_whitelist", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 113, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_ip_blacklist", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 116, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_trust_x_forwarded_for", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 119, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_request_transform", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 124, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_response_transform", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 128, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_openapi_url", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 134, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_openapi_auto_discover", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 138, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_wsdl_url", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 144, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_soap_version", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 148, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_ws_security", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 152, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_graphql_max_depth", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 158, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_graphql_schema_url", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 162, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_graphql_subscriptions", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 166, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_grpc_web_enabled", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 172, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_grpc_reflection_url", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 176, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_is_crud", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 181, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_crud_collection", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 185, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.api_crud_schema", + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "source": { + "kind": "field", + "line": 190, + "path": "backend-services/models/update_api_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::class:UpdateApiModel.Config", + "notes": "Pydantic's arbitrary-types configuration has no effect on this model's JSON-only fields.", + "rationale": "Python framework configuration is unnecessary in the typed Rust/serde implementation.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 195, + "path": "backend-services/models/update_api_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_api_model.py::field:UpdateApiModel.Config.arbitrary_types_allowed", + "notes": "Every accepted field is a JSON scalar, list, or object and does not require arbitrary Python types.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 196, + "path": "backend-services/models/update_api_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::module:update_endpoint_model", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::class:UpdateEndpointModel", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.api_name", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.api_version", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.endpoint_method", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.endpoint_uri", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.endpoint_description", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.endpoint_servers", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.client_uri", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 35, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.api_id", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.endpoint_id", + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::class:UpdateEndpointModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in the Rust implementation.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 57, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 58, + "path": "backend-services/models/update_endpoint_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_validation_model.py::module:update_endpoint_validation_model", + "notes": "Rust validates required update fields and recursively normalizes schemas; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Same verified mechanism as create_endpoint_validation_model.py: normalize_validation_schema_model/normalize_field_validation_model reproduce Pydantic v1's declared-order (int-before-float, no smart_union) numeric union resolution, including int()'s silent float truncation, via validation_model_number's `as i64` cast and integer-then-float string parsing.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/update_endpoint_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_validation_model.py::class:UpdateEndpointValidationModel", + "notes": "Rust validates required update fields and recursively normalizes schemas; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Same verified mechanism as create_endpoint_validation_model.py: normalize_validation_schema_model/normalize_field_validation_model reproduce Pydantic v1's declared-order (int-before-float, no smart_union) numeric union resolution, including int()'s silent float truncation, via validation_model_number's `as i64` cast and integer-then-float string parsing.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "class", + "line": 12, + "path": "backend-services/models/update_endpoint_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_validation_model.py::field:UpdateEndpointValidationModel.validation_enabled", + "notes": "Rust validates required update fields and recursively normalizes schemas; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Same verified mechanism as create_endpoint_validation_model.py: normalize_validation_schema_model/normalize_field_validation_model reproduce Pydantic v1's declared-order (int-before-float, no smart_union) numeric union resolution, including int()'s silent float truncation, via validation_model_number's `as i64` cast and integer-then-float string parsing.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/update_endpoint_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_validation_model.py::field:UpdateEndpointValidationModel.validation_schema", + "notes": "Rust validates required update fields and recursively normalizes schemas; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Same verified mechanism as create_endpoint_validation_model.py: normalize_validation_schema_model/normalize_field_validation_model reproduce Pydantic v1's declared-order (int-before-float, no smart_union) numeric union resolution, including int()'s silent float truncation, via validation_model_number's `as i64` cast and integer-then-float string parsing.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 16, + "path": "backend-services/models/update_endpoint_validation_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_validation_model.py::class:UpdateEndpointValidationModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 20, + "path": "backend-services/models/update_endpoint_validation_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_endpoint_validation_model.py::field:UpdateEndpointValidationModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/update_endpoint_validation_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_group_model.py::module:update_group_model", + "notes": "Rust matches optional fields, null-elision, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/update_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_group_model.py::class:UpdateGroupModel", + "notes": "Rust matches optional fields, null-elision, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/update_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_group_model.py::field:UpdateGroupModel.group_name", + "notes": "Rust matches optional fields, null-elision, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/update_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_group_model.py::field:UpdateGroupModel.group_description", + "notes": "Rust matches optional fields, null-elision, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/update_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_group_model.py::field:UpdateGroupModel.api_access", + "notes": "Rust matches optional fields, null-elision, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/update_group_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_group_model.py::class:UpdateGroupModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 25, + "path": "backend-services/models/update_group_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_group_model.py::field:UpdateGroupModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/update_group_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_password_model.py::module:update_password_model", + "notes": "Rust matches the required field, Pydantic scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_password_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::update_password_model_validates_before_auth_and_matches_pydantic_bounds" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/update_password_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_password_model.py::class:UpdatePasswordModel", + "notes": "Rust matches the required field, Pydantic scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_password_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::update_password_model_validates_before_auth_and_matches_pydantic_bounds" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/update_password_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_password_model.py::field:UpdatePasswordModel.new_password", + "notes": "Rust matches the required field, Pydantic scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_password_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::update_password_model_validates_before_auth_and_matches_pydantic_bounds" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/update_password_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_password_model.py::class:UpdatePasswordModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 19, + "path": "backend-services/models/update_password_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_password_model.py::field:UpdatePasswordModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/models/update_password_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::module:update_role_model", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::class:UpdateRoleModel", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.role_name", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.role_description", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_users", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_apis", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 22, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_endpoints", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_groups", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_roles", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_routings", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_gateway", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 33, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_subscriptions", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 36, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_security", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_tiers", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_rate_limits", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_credits", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 48, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.manage_auth", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 51, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.view_analytics", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 54, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.view_logs", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 57, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.export_logs", + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "source": { + "kind": "field", + "line": 58, + "path": "backend-services/models/update_role_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::class:UpdateRoleModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 60, + "path": "backend-services/models/update_role_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_role_model.py::field:UpdateRoleModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 61, + "path": "backend-services/models/update_role_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_routing_model.py::module:update_routing_model", + "notes": "Rust matches optional fields, null-elision, excluded server_index behavior, list/scalar coercion, and character-count bounds.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/update_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_routing_model.py::class:UpdateRoutingModel", + "notes": "Rust matches optional fields, null-elision, excluded server_index behavior, list/scalar coercion, and character-count bounds.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/update_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_routing_model.py::field:UpdateRoutingModel.routing_name", + "notes": "Rust matches optional fields, null-elision, excluded server_index behavior, list/scalar coercion, and character-count bounds.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/update_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_routing_model.py::field:UpdateRoutingModel.routing_servers", + "notes": "Rust matches optional fields, null-elision, excluded server_index behavior, list/scalar coercion, and character-count bounds.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/models/update_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_routing_model.py::field:UpdateRoutingModel.routing_description", + "notes": "Rust matches optional fields, null-elision, excluded server_index behavior, list/scalar coercion, and character-count bounds.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/models/update_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_routing_model.py::field:UpdateRoutingModel.client_key", + "notes": "Rust matches optional fields, null-elision, excluded server_index behavior, list/scalar coercion, and character-count bounds.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/models/update_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_routing_model.py::field:UpdateRoutingModel.server_index", + "notes": "Rust matches optional fields, null-elision, excluded server_index behavior, list/scalar coercion, and character-count bounds.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/models/update_routing_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_routing_model.py::class:UpdateRoutingModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 42, + "path": "backend-services/models/update_routing_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_routing_model.py::field:UpdateRoutingModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/models/update_routing_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::module:update_user_model", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::class:UpdateUserModel", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.username", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.email", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.password", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.role", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.groups", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.rate_limit_duration", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 34, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.rate_limit_duration_type", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.rate_limit_enabled", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.throttle_duration", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.throttle_duration_type", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 46, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.throttle_wait_duration", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 53, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.throttle_wait_duration_type", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 56, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.throttle_queue_limit", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 63, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.throttle_enabled", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 66, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.custom_attributes", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 69, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.bandwidth_limit_bytes", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 72, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.bandwidth_limit_window", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 78, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.bandwidth_limit_enabled", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 85, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.active", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 90, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.ui_access", + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "source": { + "kind": "field", + "line": 91, + "path": "backend-services/models/update_user_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::class:UpdateUserModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 93, + "path": "backend-services/models/update_user_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_user_model.py::field:UpdateUserModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 94, + "path": "backend-services/models/update_user_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_vault_entry_model.py::module:update_vault_entry_model", + "notes": "Rust matches the optional nullable description, scalar-to-string coercion, Unicode character bound, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/update_vault_entry_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_vault_entry_model.py::class:UpdateVaultEntryModel", + "notes": "Rust matches the optional nullable description, scalar-to-string coercion, Unicode character bound, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/update_vault_entry_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_vault_entry_model.py::field:UpdateVaultEntryModel.description", + "notes": "Rust matches the optional nullable description, scalar-to-string coercion, Unicode character bound, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/update_vault_entry_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_vault_entry_model.py::class:UpdateVaultEntryModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 20, + "path": "backend-services/models/update_vault_entry_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/update_vault_entry_model.py::field:UpdateVaultEntryModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/models/update_vault_entry_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::module:user_credits_model", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::class:UserCreditInformationModel", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::field:UserCreditInformationModel.tier_name", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::field:UserCreditInformationModel.available_credits", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::field:UserCreditInformationModel.reset_date", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 16, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::field:UserCreditInformationModel.user_api_key", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 19, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::class:UserCreditInformationModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 25, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::field:UserCreditInformationModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::class:UserCreditModel", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "class", + "line": 29, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::field:UserCreditModel.username", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::field:UserCreditModel.users_credits", + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::class:UserCreditModel.Config", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 41, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_credits_model.py::field:UserCreditModel.Config.arbitrary_types_allowed", + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/models/user_credits_model.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::module:user_model_response", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::class:UserModelResponse", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.username", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.email", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 14, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.password", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 15, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.role", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 22, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.groups", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.rate_limit_duration", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.rate_limit_duration_type", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.rate_limit_enabled", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 34, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.throttle_duration", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.throttle_duration_type", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.throttle_wait_duration", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 47, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.throttle_wait_duration_type", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 50, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.throttle_queue_limit", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 57, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.throttle_enabled", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 60, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.custom_attributes", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 63, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.bandwidth_limit_bytes", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 66, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.bandwidth_limit_window", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 72, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.bandwidth_usage_bytes", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 79, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.bandwidth_resets_at", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 85, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.bandwidth_limit_enabled", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 90, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.active", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 95, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.ui_access", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 96, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::class:UserModelResponse.Config", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 98, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/user_model_response.py::field:UserModelResponse.Config.arbitrary_types_allowed", + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 99, + "path": "backend-services/models/user_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/validation_schema_model.py::module:validation_schema_model", + "notes": "Rust requires the validation_schema mapping and recursively normalizes its field rules; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Same verified mechanism as field_validation_model.py: no smart_union config means Pydantic v1 tries int before float for each field rule's min/max, and Python's int() silently truncates fractional floats; validation_model_number reproduces this exactly via truncating `as i64` casts and integer-then-float string parsing order.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/validation_schema_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/validation_schema_model.py::class:ValidationSchema", + "notes": "Rust requires the validation_schema mapping and recursively normalizes its field rules; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Same verified mechanism as field_validation_model.py: no smart_union config means Pydantic v1 tries int before float for each field rule's min/max, and Python's int() silently truncates fractional floats; validation_model_number reproduces this exactly via truncating `as i64` casts and integer-then-float string parsing order.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "class", + "line": 12, + "path": "backend-services/models/validation_schema_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/validation_schema_model.py::field:ValidationSchema.validation_schema", + "notes": "Rust requires the validation_schema mapping and recursively normalizes its field rules; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Same verified mechanism as field_validation_model.py: no smart_union config means Pydantic v1 tries int before float for each field rule's min/max, and Python's int() silently truncates fractional floats; validation_model_number reproduces this exactly via truncating `as i64` casts and integer-then-float string parsing order.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "field", + "line": 61, + "path": "backend-services/models/validation_schema_model.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/vault_entry_model_response.py::module:vault_entry_model_response", + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/models/vault_entry_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/vault_entry_model_response.py::class:VaultEntryModelResponse", + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 10, + "path": "backend-services/models/vault_entry_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/vault_entry_model_response.py::field:VaultEntryModelResponse.key_name", + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 13, + "path": "backend-services/models/vault_entry_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/vault_entry_model_response.py::field:VaultEntryModelResponse.username", + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 15, + "path": "backend-services/models/vault_entry_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/vault_entry_model_response.py::field:VaultEntryModelResponse.description", + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/models/vault_entry_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/vault_entry_model_response.py::field:VaultEntryModelResponse.created_at", + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/models/vault_entry_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/vault_entry_model_response.py::field:VaultEntryModelResponse.updated_at", + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/models/vault_entry_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/vault_entry_model_response.py::class:VaultEntryModelResponse.Config", + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 33, + "path": "backend-services/models/vault_entry_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "models", + "id": "backend-services/models/vault_entry_model_response.py::field:VaultEntryModelResponse.Config.arbitrary_types_allowed", + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "field", + "line": 34, + "path": "backend-services/models/vault_entry_model_response.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/analytics_routes.py::module:analytics_routes", + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/analytics_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/analytics_routes.py::function:_normalize_top_pairs", + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "source": { + "kind": "function", + "line": 28, + "path": "backend-services/routes/analytics_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "analytics_router.get('/analytics/overview', description='Get dashboard overview statistics', response_model=ResponseModel, responses={200: {'description': 'Analytics overview', 'content': {'application/json': {'example': {'total_requests': 12345, 'total_errors': 123, 'error_rate': 0.01, 'avg_response_ms': 150.5, 'percentiles': {'p50': 120.0, 'p75': 180.0, 'p90': 250.0, 'p95': 300.0, 'p99': 450.0}, 'unique_users': 150, 'total_bandwidth': 1073741824, 'top_apis': [{'api': 'rest:customer', 'count': 5000}, {'api': 'rest:orders', 'count': 3000}], 'top_users': [{'user': 'john_doe', 'count': 500}, {'user': 'jane_smith', 'count': 300}]}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/analytics_routes.py::async_function:get_analytics_overview", + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "source": { + "kind": "async_function", + "line": 84, + "path": "backend-services/routes/analytics_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "analytics_router.get('/analytics/timeseries', description='Get time-series analytics data with filtering', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/analytics_routes.py::async_function:get_analytics_timeseries", + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "source": { + "kind": "async_function", + "line": 197, + "path": "backend-services/routes/analytics_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "analytics_router.get('/analytics/top-apis', description='Get most used APIs', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/analytics_routes.py::async_function:get_top_apis", + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "source": { + "kind": "async_function", + "line": 311, + "path": "backend-services/routes/analytics_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "analytics_router.get('/analytics/top-users', description='Get highest consuming users', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/analytics_routes.py::async_function:get_top_users", + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "source": { + "kind": "async_function", + "line": 398, + "path": "backend-services/routes/analytics_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "analytics_router.get('/analytics/top-endpoints', description='Get slowest/most-used endpoints', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/analytics_routes.py::async_function:get_top_endpoints", + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "source": { + "kind": "async_function", + "line": 482, + "path": "backend-services/routes/analytics_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "analytics_router.get('/analytics/api/{api_name}/{version}', description='Get detailed analytics for a specific API', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/analytics_routes.py::async_function:get_api_analytics", + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "source": { + "kind": "async_function", + "line": 580, + "path": "backend-services/routes/analytics_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "analytics_router.get('/analytics/user/{username}', description='Get detailed analytics for a specific user', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/analytics_routes.py::async_function:get_user_analytics", + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "source": { + "kind": "async_function", + "line": 696, + "path": "backend-services/routes/analytics_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/api_routes.py::module:api_routes", + "notes": "All API CRUD/list routes are implemented natively. Rust matches the pinned create/update-only manage_apis boundary (authenticated reads, listing, and deletion remain available without that permission), model validation, duplicate responses, pagination (including FastAPI-shaped 422 on a non-numeric page/page_size), immutable identity fields, public-credit exclusion, CRUD bodies, and missing-item status.", + "rationale": "The proto-upload-before-create path intentionally persists descriptor metadata differently (a fresh per-request temporary compile directory rather than a reusable project-root path), and auth/storage exceptional envelopes are consolidated through the typed SharedStorage error boundary into the same observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/api_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "api_router.post('', description='Add API', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'API created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/api_routes.py::async_function:create_api", + "notes": "All API CRUD/list routes are implemented natively. Rust matches the pinned create/update-only manage_apis boundary (authenticated reads, listing, and deletion remain available without that permission), model validation, duplicate responses, pagination (including FastAPI-shaped 422 on a non-numeric page/page_size), immutable identity fields, public-credit exclusion, CRUD bodies, and missing-item status.", + "rationale": "The proto-upload-before-create path intentionally persists descriptor metadata differently (a fresh per-request temporary compile directory rather than a reusable project-root path), and auth/storage exceptional envelopes are consolidated through the typed SharedStorage error boundary into the same observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_function", + "line": 48, + "path": "backend-services/routes/api_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "api_router.put('/{api_name}/{api_version}', description='Update API', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'API updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/api_routes.py::async_function:update_api", + "notes": "All API CRUD/list routes are implemented natively. Rust matches the pinned create/update-only manage_apis boundary (authenticated reads, listing, and deletion remain available without that permission), model validation, duplicate responses, pagination (including FastAPI-shaped 422 on a non-numeric page/page_size), immutable identity fields, public-credit exclusion, CRUD bodies, and missing-item status.", + "rationale": "The proto-upload-before-create path intentionally persists descriptor metadata differently (a fresh per-request temporary compile directory rather than a reusable project-root path), and auth/storage exceptional envelopes are consolidated through the typed SharedStorage error boundary into the same observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_function", + "line": 116, + "path": "backend-services/routes/api_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "api_router.get('/{api_name}/{api_version}', description='Get API', response_model=ApiModelResponse, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'API retrieved successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/api_routes.py::async_function:get_api_by_name_version", + "notes": "All API CRUD/list routes are implemented natively. Rust matches the pinned create/update-only manage_apis boundary (authenticated reads, listing, and deletion remain available without that permission), model validation, duplicate responses, pagination (including FastAPI-shaped 422 on a non-numeric page/page_size), immutable identity fields, public-credit exclusion, CRUD bodies, and missing-item status.", + "rationale": "The proto-upload-before-create path intentionally persists descriptor metadata differently (a fresh per-request temporary compile directory rather than a reusable project-root path), and auth/storage exceptional envelopes are consolidated through the typed SharedStorage error boundary into the same observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_function", + "line": 185, + "path": "backend-services/routes/api_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "api_router.delete('/{api_name}/{api_version}', description='Delete API', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'API deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/api_routes.py::async_function:delete_api", + "notes": "All API CRUD/list routes are implemented natively. Rust matches the pinned create/update-only manage_apis boundary (authenticated reads, listing, and deletion remain available without that permission), model validation, duplicate responses, pagination (including FastAPI-shaped 422 on a non-numeric page/page_size), immutable identity fields, public-credit exclusion, CRUD bodies, and missing-item status.", + "rationale": "The proto-upload-before-create path intentionally persists descriptor metadata differently (a fresh per-request temporary compile directory rather than a reusable project-root path), and auth/storage exceptional envelopes are consolidated through the typed SharedStorage error boundary into the same observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_function", + "line": 235, + "path": "backend-services/routes/api_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "api_router.get('/all', description='Get all APIs', response_model=list[ApiModelResponse])" + ], + "domain": "routes", + "id": "backend-services/routes/api_routes.py::async_function:get_all_apis", + "notes": "All API CRUD/list routes are implemented natively. Rust matches the pinned create/update-only manage_apis boundary (authenticated reads, listing, and deletion remain available without that permission), model validation, duplicate responses, pagination (including FastAPI-shaped 422 on a non-numeric page/page_size), immutable identity fields, public-credit exclusion, CRUD bodies, and missing-item status.", + "rationale": "The proto-upload-before-create path intentionally persists descriptor metadata differently (a fresh per-request temporary compile directory rather than a reusable project-root path), and auth/storage exceptional envelopes are consolidated through the typed SharedStorage error boundary into the same observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_function", + "line": 283, + "path": "backend-services/routes/api_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "api_router.get('', description='Get all APIs (base path)', response_model=list[ApiModelResponse])" + ], + "domain": "routes", + "id": "backend-services/routes/api_routes.py::async_function:get_all_apis_base", + "notes": "All API CRUD/list routes are implemented natively. Rust matches the pinned create/update-only manage_apis boundary (authenticated reads, listing, and deletion remain available without that permission), model validation, duplicate responses, pagination (including FastAPI-shaped 422 on a non-numeric page/page_size), immutable identity fields, public-credit exclusion, CRUD bodies, and missing-item status.", + "rationale": "The proto-upload-before-create path intentionally persists descriptor metadata differently (a fresh per-request temporary compile directory rather than a reusable project-root path), and auth/storage exceptional envelopes are consolidated through the typed SharedStorage error boundary into the same observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_function", + "line": 323, + "path": "backend-services/routes/api_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::module:authorization_routes", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.post('/authorization', description='Create authorization token', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'access_token': '******************'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:authorization", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 57, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.post('/authorization/register', description='Register new user', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'User created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:register", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 310, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.post('/authorization/admin/revoke/{username}', description='Revoke all active tokens for a user (admin)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:admin_revoke_user_tokens", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 394, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.post('/authorization/admin/unrevoke/{username}', description='Clear token revocation for a user (admin)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:admin_unrevoke_user_tokens", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 463, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.post('/authorization/admin/disable/{username}', description='Disable a user (admin)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:admin_disable_user", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 532, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.post('/authorization/admin/enable/{username}', description='Enable a user (admin)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:admin_enable_user", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 604, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.get('/authorization/admin/status/{username}', description='Get auth status for a user (admin)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:admin_user_status", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 674, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.post('/authorization/refresh', description='Create authorization refresh token', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'refresh_token': '******************'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:extended_authorization", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 748, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.get('/authorization/status', description='Get authorization token status', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'status': 'authorized'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:authorization_status", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 911, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "authorization_router.post('/authorization/invalidate', description='Invalidate authorization token', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Your token has been invalidated'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/authorization_routes.py::async_function:authorization_invalidate", + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "source": { + "kind": "async_function", + "line": 984, + "path": "backend-services/routes/authorization_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_hot_reload_routes.py::module:config_hot_reload_routes", + "notes": "Rust intentionally returns useful current/reload/key data and applies supported gateway settings; Python's ResponseModel silently discarded the undeclared data field and emitted empty success bodies.", + "rationale": "v2 fixes the pinned hot-reload response bug and explicitly distinguishes live-applied keys from restart-required settings.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_current", + "gateway-rs/src/hot_reload.rs::HotReloadConfig" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::config_reload_routes_preserve_legacy_values_metadata_and_permissions" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/config_hot_reload_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "config_hot_reload_router.get('/current', summary='Get Current Configuration', description='Retrieve current hot-reloadable configuration values', response_model=dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/config_hot_reload_routes.py::async_function:get_current_config", + "notes": "Rust intentionally returns useful current/reload/key data and applies supported gateway settings; Python's ResponseModel silently discarded the undeclared data field and emitted empty success bodies.", + "rationale": "v2 fixes the pinned hot-reload response bug and explicitly distinguishes live-applied keys from restart-required settings.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_current", + "gateway-rs/src/hot_reload.rs::HotReloadConfig" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::config_reload_routes_preserve_legacy_values_metadata_and_permissions" + ], + "source": { + "kind": "async_function", + "line": 27, + "path": "backend-services/routes/config_hot_reload_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "config_hot_reload_router.post('/reload', summary='Trigger Configuration Reload', description='Manually trigger configuration reload (same as SIGHUP)', response_model=dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/config_hot_reload_routes.py::async_function:trigger_config_reload", + "notes": "Rust intentionally returns useful current/reload/key data and applies supported gateway settings; Python's ResponseModel silently discarded the undeclared data field and emitted empty success bodies.", + "rationale": "v2 fixes the pinned hot-reload response bug and explicitly distinguishes live-applied keys from restart-required settings.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_current", + "gateway-rs/src/hot_reload.rs::HotReloadConfig" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::config_reload_routes_preserve_legacy_values_metadata_and_permissions" + ], + "source": { + "kind": "async_function", + "line": 62, + "path": "backend-services/routes/config_hot_reload_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "config_hot_reload_router.get('/reloadable-keys', summary='List Reloadable Configuration Keys', description='Get list of configuration keys that support hot reload', response_model=dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/config_hot_reload_routes.py::async_function:get_reloadable_keys", + "notes": "Rust intentionally returns useful current/reload/key data and applies supported gateway settings; Python's ResponseModel silently discarded the undeclared data field and emitted empty success bodies.", + "rationale": "v2 fixes the pinned hot-reload response bug and explicitly distinguishes live-applied keys from restart-required settings.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_current", + "gateway-rs/src/hot_reload.rs::HotReloadConfig" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::config_reload_routes_preserve_legacy_values_metadata_and_permissions" + ], + "source": { + "kind": "async_function", + "line": 93, + "path": "backend-services/routes/config_hot_reload_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::module:config_routes", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::function:_strip_id", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "function", + "line": 42, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:_export_all", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 48, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:_create_snapshot", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 64, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:_restore_snapshot", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 85, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "config_router.get('/config/export/all', description='Export all platform configuration (APIs, Endpoints, Roles, Groups, Routings)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:export_all", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 159, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "config_router.get('/config/export/apis', description='Export APIs (optionally a single API with its endpoints)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:export_apis", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 217, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "config_router.get('/config/export/roles', description='Export Roles', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:export_roles", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 299, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "config_router.get('/config/export/groups', description='Export Groups', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:export_groups", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 370, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "config_router.get('/config/export/routings', description='Export Routings', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:export_routings", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 441, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "config_router.get('/config/export/endpoints', description='Export endpoints (optionally filter by api_name/api_version)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:export_endpoints", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 515, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:_upsert_api", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 551, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:_upsert_endpoint", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 573, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:_upsert_role", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 610, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:_upsert_group", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 622, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:_upsert_routing", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 634, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "config_router.post('/config/import', description='Import platform configuration (any subset of apis, endpoints, roles, groups, routings)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:import_all", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 661, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "config_router.post('/config/rollback', description='Rollback configuration to latest snapshot', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/config_routes.py::async_function:rollback_config", + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 731, + "path": "backend-services/routes/config_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::module:credit_routes", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "credit_router.get('/defs', description='List credit definitions', response_model=ResponseModel, responses={200: {'description': 'Successful Response'}})" + ], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::async_function:list_credit_definitions", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "async_function", + "line": 42, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "credit_router.get('/defs/{api_credit_group}', description='Get a credit definition', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::async_function:get_credit_definition", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "async_function", + "line": 102, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "credit_router.post('', description='Create a credit definition', response_model=ResponseModel, responses={201: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Credit definition created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::async_function:create_credit", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "async_function", + "line": 160, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "credit_router.put('/{api_credit_group}', description='Update a credit definition', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Credit definition updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::async_function:update_credit", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "async_function", + "line": 230, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "credit_router.delete('/{api_credit_group}', description='Delete a credit definition', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Credit definition deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::async_function:delete_credit", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "async_function", + "line": 300, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "credit_router.post('/{username}', description='Add credits for a user', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Credits saved successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::async_function:add_user_credits", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "async_function", + "line": 366, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "credit_router.get('/all', description='Get all user credits', response_model=list[UserCreditModel])" + ], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::async_function:get_all_users_credits", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "async_function", + "line": 422, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "credit_router.get('/{username}', description='Get credits for a user', response_model=UserCreditModel)" + ], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::async_function:get_credits", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "async_function", + "line": 474, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "credit_router.post('/rotate-key', description='Rotate API key for the authenticated user', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'api_key': '******************'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/credit_routes.py::async_function:rotate_key", + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "source": { + "kind": "async_function", + "line": 528, + "path": "backend-services/routes/credit_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/dashboard_routes.py::module:dashboard_routes", + "notes": "Rust returns the pinned dashboard totals, monthly usage, active users, popular APIs, and response shape from native storage and analytics.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::dashboard" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/dashboard_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dashboard_router.get('', description='Get dashboard data', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/dashboard_routes.py::async_function:get_dashboard_data", + "notes": "Rust returns the pinned dashboard totals, monthly usage, active users, popular APIs, and response shape from native storage and analytics.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::dashboard" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity" + ], + "source": { + "kind": "async_function", + "line": 34, + "path": "backend-services/routes/dashboard_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/demo_routes.py::module:demo_routes", + "notes": "The HTTP route is rewired to the configurable seeder (demo_seed::run_seed/SeedOptions), parsing users/apis/endpoints/groups/protos/logs/seed from the query string with Python's exact route defaults (40/15/6/8/6/1500/None). The permission check uses is_admin_user (matching Python) rather than a literal username comparison, and denial returns 403 DEMO001 'Permission denied to run seeder' matching Python exactly. Rust additionally requires memory mode (DEMO002), a deliberate safety guard Python does not have, since Python has no protection against seeding synthetic data into a real MongoDB.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_denies_non_admin_with_pythons_error_code_and_message" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/demo_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "demo_router.post('/seed', description='Seed the running server with demo data', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/demo_routes.py::async_function:demo_seed", + "notes": "The HTTP route is rewired to the configurable seeder (demo_seed::run_seed/SeedOptions), parsing users/apis/endpoints/groups/protos/logs/seed from the query string with Python's exact route defaults (40/15/6/8/6/1500/None). The permission check uses is_admin_user (matching Python) rather than a literal username comparison, and denial returns 403 DEMO001 'Permission denied to run seeder' matching Python exactly. Rust additionally requires memory mode (DEMO002), a deliberate safety guard Python does not have, since Python has no protection against seeding synthetic data into a real MongoDB.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_denies_non_admin_with_pythons_error_code_and_message" + ], + "source": { + "kind": "async_function", + "line": 34, + "path": "backend-services/routes/demo_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::module:endpoint_routes", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.post('', description='Add endpoint', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Endpoint created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:create_endpoint", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 53, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.put('/{endpoint_method}/{api_name}/{api_version}/{endpoint_uri:path}', description='Update endpoint', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Endpoint updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:update_endpoint", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 114, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.delete('/{endpoint_method}/{api_name}/{api_version}/{endpoint_uri:path}', description='Delete endpoint', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Endpoint deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:delete_endpoint", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 191, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.get('/{endpoint_method}/{api_name}/{api_version}/{endpoint_uri:path}', description='Get endpoint by API name, API version and endpoint uri', response_model=EndpointModelResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:get_endpoint", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 250, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.get('/{api_name}/{api_version}', description='Get all endpoints for an API', response_model=list[EndpointModelResponse])" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:get_endpoints_by_name_version", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 300, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.post('/endpoint/validation', description='Create a new endpoint validation', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Endpoint validation created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:create_endpoint_validation", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 356, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.put('/endpoint/validation/{endpoint_id}', description='Update an endpoint validation by endpoint ID', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:update_endpoint_validation", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 413, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.delete('/endpoint/validation/{endpoint_id}', description='Delete an endpoint validation by endpoint ID', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:delete_endpoint_validation", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 472, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.get('/endpoint/validation/{endpoint_id}', description='Get an endpoint validation by endpoint ID', response_model=EndpointValidationModelResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:get_endpoint_validation", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 527, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.post('/validation', description='Create a new endpoint validation (alias)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:create_endpoint_validation_alias", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 571, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.put('/validation/{endpoint_id}', description='Update endpoint validation by endpoint ID (alias)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:update_endpoint_validation_alias", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 592, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.delete('/validation/{endpoint_id}', description='Delete endpoint validation by endpoint ID (alias)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:delete_endpoint_validation_alias", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 613, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "endpoint_router.get('/validation/{endpoint_id}', description='Get endpoint validation by endpoint ID (alias)', response_model=EndpointValidationModelResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/endpoint_routes.py::async_function:get_endpoint_validation_alias", + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "source": { + "kind": "async_function", + "line": 632, + "path": "backend-services/routes/endpoint_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::module:gateway_routes", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/status', methods=['GET'], description='Gateway status (requires manage_gateway)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:status", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 59, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.get('/health', description='Public health probe', include_in_schema=False)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:health", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 127, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/caches', methods=['DELETE'], description='Clear all caches', response_model=ResponseModel, dependencies=[Depends(auth_required)], responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'All caches cleared'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:clear_all_caches", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 163, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.options('/caches', include_in_schema=False)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:clear_all_caches_options", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 246, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/rest/{path:path}', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], description='REST gateway endpoint', response_model=ResponseModel, include_in_schema=False)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:gateway", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 277, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:gateway._resolve_api_by", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 291, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.get('/rest/{path:path}', description='REST gateway endpoint (GET)', response_model=ResponseModel, operation_id='rest_get')" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:rest_get", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 439, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.post('/rest/{path:path}', description='REST gateway endpoint (POST)', response_model=ResponseModel, operation_id='rest_post')" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:rest_post", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 449, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.put('/rest/{path:path}', description='REST gateway endpoint (PUT)', response_model=ResponseModel, operation_id='rest_put')" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:rest_put", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 459, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.patch('/rest/{path:path}', description='REST gateway endpoint (PATCH)', response_model=ResponseModel, operation_id='rest_patch')" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:rest_patch", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 469, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.delete('/rest/{path:path}', description='REST gateway endpoint (DELETE)', response_model=ResponseModel, operation_id='rest_delete')" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:rest_delete", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 479, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.head('/rest/{path:path}', description='REST gateway endpoint (HEAD)', response_model=ResponseModel, operation_id='rest_head')" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:rest_head", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 489, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/rest/{path:path}', methods=['OPTIONS'], description='REST gateway CORS preflight', include_in_schema=False)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:rest_preflight", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 518, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/soap/{path:path}', methods=['POST', 'GET'], description='SOAP gateway endpoint', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:soap_gateway", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 631, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/grpc/{path:path}', methods=['POST', 'GET'], description='gRPC gateway endpoint', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:grpc_gateway", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 745, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/soap/{path:path}', methods=['OPTIONS'], description='SOAP gateway CORS preflight', include_in_schema=False)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:soap_preflight", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 878, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/graphql/{path:path}', methods=['POST'], description='GraphQL gateway endpoint', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:graphql_gateway", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 947, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/graphql/{path:path}', methods=['OPTIONS'], description='GraphQL gateway CORS preflight', include_in_schema=False)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:graphql_preflight", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 1092, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/grpc/{path:path}', methods=['OPTIONS'], description='gRPC gateway CORS preflight', include_in_schema=False)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:grpc_preflight", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 1145, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "gateway_router.api_route('/grpc/{path:path}', methods=['POST', 'GET'], description='gRPC gateway endpoint', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/gateway_routes.py::async_function:grpc_gateway#2", + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "async_function", + "line": 1237, + "path": "backend-services/routes/gateway_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/graphql_routes.py::module:graphql_routes", + "notes": "Rust preserves authenticated schema fetch/cache/refresh and type listing with pinned permission and error behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/graphql_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "graphql_routes_router.get('/platform/api/{api_name}/{api_version}/graphql/schema')" + ], + "domain": "routes", + "id": "backend-services/routes/graphql_routes.py::async_function:get_graphql_schema", + "notes": "Rust preserves authenticated schema fetch/cache/refresh and type listing with pinned permission and error behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "async_function", + "line": 42, + "path": "backend-services/routes/graphql_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "graphql_routes_router.post('/platform/api/{api_name}/{api_version}/graphql/schema/refresh')" + ], + "domain": "routes", + "id": "backend-services/routes/graphql_routes.py::async_function:refresh_graphql_schema", + "notes": "Rust preserves authenticated schema fetch/cache/refresh and type listing with pinned permission and error behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "async_function", + "line": 122, + "path": "backend-services/routes/graphql_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "graphql_routes_router.get('/platform/api/{api_name}/{api_version}/graphql/types')" + ], + "domain": "routes", + "id": "backend-services/routes/graphql_routes.py::async_function:get_graphql_types", + "notes": "Rust preserves authenticated schema fetch/cache/refresh and type listing with pinned permission and error behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "async_function", + "line": 186, + "path": "backend-services/routes/graphql_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/group_routes.py::module:group_routes", + "notes": "Rust preserves group CRUD, model validation, permissions, duplicate/not-found behavior, pagination, and response bodies.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/group_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "group_router.post('', description='Add group', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Group created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/group_routes.py::async_function:create_group", + "notes": "Rust preserves group CRUD, model validation, permissions, duplicate/not-found behavior, pagination, and response bodies.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 48, + "path": "backend-services/routes/group_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "group_router.put('/{group_name}', description='Update group', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Group updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/group_routes.py::async_function:update_group", + "notes": "Rust preserves group CRUD, model validation, permissions, duplicate/not-found behavior, pagination, and response bodies.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 106, + "path": "backend-services/routes/group_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "group_router.delete('/{group_name}', description='Delete group', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Group deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/group_routes.py::async_function:delete_group", + "notes": "Rust preserves group CRUD, model validation, permissions, duplicate/not-found behavior, pagination, and response bodies.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 164, + "path": "backend-services/routes/group_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "group_router.get('/all', description='Get all groups', response_model=list[GroupModelResponse])" + ], + "domain": "routes", + "id": "backend-services/routes/group_routes.py::async_function:get_groups", + "notes": "Rust preserves group CRUD, model validation, permissions, duplicate/not-found behavior, pagination, and response bodies.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 212, + "path": "backend-services/routes/group_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "group_router.get('/{group_name}', description='Get group', response_model=GroupModelResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/group_routes.py::async_function:get_group", + "notes": "Rust preserves group CRUD, model validation, permissions, duplicate/not-found behavior, pagination, and response bodies.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 254, + "path": "backend-services/routes/group_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/grpc_routes.py::module:grpc_routes", + "notes": "Rust provides authenticated descriptor-backed service discovery and the native gRPC-Web proxy with policy, CORS, unary, and streaming behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/grpc_web.rs::grpc_web_policy_then_execute" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_60_grpc_gateway_basic_flow_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/grpc_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "grpc_router.get('/platform/api/{api_name}/{api_version}/grpc/services')" + ], + "domain": "routes", + "id": "backend-services/routes/grpc_routes.py::async_function:list_grpc_services", + "notes": "Rust provides authenticated descriptor-backed service discovery and the native gRPC-Web proxy with policy, CORS, unary, and streaming behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/grpc_web.rs::grpc_web_policy_then_execute" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_60_grpc_gateway_basic_flow_parity" + ], + "source": { + "kind": "async_function", + "line": 41, + "path": "backend-services/routes/grpc_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "grpc_router.post('/grpc-web/{api_name}/{service}/{method}')" + ], + "domain": "routes", + "id": "backend-services/routes/grpc_routes.py::async_function:grpc_web_proxy", + "notes": "Rust provides authenticated descriptor-backed service discovery and the native gRPC-Web proxy with policy, CORS, unary, and streaming behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/grpc_web.rs::grpc_web_policy_then_execute" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_60_grpc_gateway_basic_flow_parity" + ], + "source": { + "kind": "async_function", + "line": 99, + "path": "backend-services/routes/grpc_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/logging_routes.py::module:logging_routes", + "notes": "Rust preserves authenticated log querying, file listing, statistics, filtered/redacted export, and downloads over native structured log files.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/logging_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "logging_router.get('/logs', description='Get logs with filtering', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'logs': [{'timestamp': '2024-01-01T12:00:00', 'level': 'INFO', 'message': 'Request processed', 'source': 'doorman.gateway', 'user': 'john_doe', 'api': 'customer', 'endpoint': '/api/customer/v1/users', 'method': 'GET', 'status_code': 200, 'response_time': '150.5', 'ip_address': '192.168.1.1', 'protocol': 'HTTP/1.1', 'request_id': '123e4567-e89b-12d3-a456-426614174000'}], 'total': 100, 'has_more': False}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/logging_routes.py::async_function:get_logs", + "notes": "Rust preserves authenticated log querying, file listing, statistics, filtered/redacted export, and downloads over native structured log files.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "async_function", + "line": 71, + "path": "backend-services/routes/logging_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "logging_router.get('/logs/files', description='Get list of available log files', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/logging_routes.py::async_function:get_log_files", + "notes": "Rust preserves authenticated log querying, file listing, statistics, filtered/redacted export, and downloads over native structured log files.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "async_function", + "line": 169, + "path": "backend-services/routes/logging_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "logging_router.get('/logs/statistics', description='Get log statistics for dashboard', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'total_logs': 1000, 'error_count': 50, 'warning_count': 100, 'info_count': 800, 'debug_count': 50, 'avg_response_time': 150.5, 'top_apis': [{'name': 'customer', 'count': 500}, {'name': 'orders', 'count': 300}], 'top_users': [{'name': 'john_doe', 'count': 200}, {'name': 'jane_smith', 'count': 150}], 'top_endpoints': [{'name': '/api/customer/v1/users', 'count': 100}, {'name': '/api/orders/v1/orders', 'count': 80}]}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/logging_routes.py::async_function:get_log_statistics", + "notes": "Rust preserves authenticated log querying, file listing, statistics, filtered/redacted export, and downloads over native structured log files.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "async_function", + "line": 262, + "path": "backend-services/routes/logging_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "logging_router.get('/logs/export', description='Export logs in various formats', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'format': 'json', 'data': '[{\"timestamp\": \"2024-01-01T12:00:00\", \"level\": \"INFO\"}]', 'filename': 'logs_export_20240101_120000.json'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/logging_routes.py::async_function:export_logs", + "notes": "Rust preserves authenticated log querying, file listing, statistics, filtered/redacted export, and downloads over native structured log files.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "async_function", + "line": 339, + "path": "backend-services/routes/logging_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "logging_router.get('/logs/download', description='Download logs as file', include_in_schema=False, responses={200: {'description': 'File download', 'content': {'application/json': {}, 'text/csv': {}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/logging_routes.py::async_function:download_logs", + "notes": "Rust preserves authenticated log querying, file listing, statistics, filtered/redacted export, and downloads over native structured log files.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "async_function", + "line": 435, + "path": "backend-services/routes/logging_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/memory_routes.py::module:memory_routes", + "notes": "Both encrypted memory dump/restore routes and optional request models are implemented natively. Rust matches manage_security gates and SEC codes, memory-mode and key checks, Pydantic path validation/coercion, encrypted round trips, response nesting, missing-file handling, and generic failure envelopes.", + "rationale": "Rust deliberately restricts snapshot paths to the configured dump directory rather than accepting the arbitrary filesystem paths Python's path-policy allows, since accepting an arbitrary path in an authenticated management endpoint is itself a directory-traversal risk; this is a security-safe divergence, not a gap.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::memory_dump", + "gateway-rs/src/routes/platform.rs::memory_restore", + "gateway-rs/src/routes/platform.rs::memory_request_path", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/tests/memory_routes_parity.rs::python_memory_route_contracts_in_isolated_processes" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/memory_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/memory_routes.py::class:DumpRequest", + "notes": "Both encrypted memory dump/restore routes and optional request models are implemented natively. Rust matches manage_security gates and SEC codes, memory-mode and key checks, Pydantic path validation/coercion, encrypted round trips, response nesting, missing-file handling, and generic failure envelopes.", + "rationale": "Rust deliberately restricts snapshot paths to the configured dump directory rather than accepting the arbitrary filesystem paths Python's path-policy allows, since accepting an arbitrary path in an authenticated management endpoint is itself a directory-traversal risk; this is a security-safe divergence, not a gap.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::memory_dump", + "gateway-rs/src/routes/platform.rs::memory_restore", + "gateway-rs/src/routes/platform.rs::memory_request_path", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/tests/memory_routes_parity.rs::python_memory_route_contracts_in_isolated_processes" + ], + "source": { + "kind": "class", + "line": 24, + "path": "backend-services/routes/memory_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/memory_routes.py::field:DumpRequest.path", + "notes": "Both encrypted memory dump/restore routes and optional request models are implemented natively. Rust matches manage_security gates and SEC codes, memory-mode and key checks, Pydantic path validation/coercion, encrypted round trips, response nesting, missing-file handling, and generic failure envelopes.", + "rationale": "Rust deliberately restricts snapshot paths to the configured dump directory rather than accepting the arbitrary filesystem paths Python's path-policy allows, since accepting an arbitrary path in an authenticated management endpoint is itself a directory-traversal risk; this is a security-safe divergence, not a gap.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::memory_dump", + "gateway-rs/src/routes/platform.rs::memory_restore", + "gateway-rs/src/routes/platform.rs::memory_request_path", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/tests/memory_routes_parity.rs::python_memory_route_contracts_in_isolated_processes" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/routes/memory_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "memory_router.post('/memory/dump', description='Dump in-memory database to an encrypted file', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/memory_routes.py::async_function:memory_dump", + "notes": "Both encrypted memory dump/restore routes and optional request models are implemented natively. Rust matches manage_security gates and SEC codes, memory-mode and key checks, Pydantic path validation/coercion, encrypted round trips, response nesting, missing-file handling, and generic failure envelopes.", + "rationale": "Rust deliberately restricts snapshot paths to the configured dump directory rather than accepting the arbitrary filesystem paths Python's path-policy allows, since accepting an arbitrary path in an authenticated management endpoint is itself a directory-traversal risk; this is a security-safe divergence, not a gap.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::memory_dump", + "gateway-rs/src/routes/platform.rs::memory_restore", + "gateway-rs/src/routes/platform.rs::memory_request_path", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/tests/memory_routes_parity.rs::python_memory_route_contracts_in_isolated_processes" + ], + "source": { + "kind": "async_function", + "line": 43, + "path": "backend-services/routes/memory_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/memory_routes.py::class:RestoreRequest", + "notes": "Both encrypted memory dump/restore routes and optional request models are implemented natively. Rust matches manage_security gates and SEC codes, memory-mode and key checks, Pydantic path validation/coercion, encrypted round trips, response nesting, missing-file handling, and generic failure envelopes.", + "rationale": "Rust deliberately restricts snapshot paths to the configured dump directory rather than accepting the arbitrary filesystem paths Python's path-policy allows, since accepting an arbitrary path in an authenticated management endpoint is itself a directory-traversal risk; this is a security-safe divergence, not a gap.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::memory_dump", + "gateway-rs/src/routes/platform.rs::memory_restore", + "gateway-rs/src/routes/platform.rs::memory_request_path", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/tests/memory_routes_parity.rs::python_memory_route_contracts_in_isolated_processes" + ], + "source": { + "kind": "class", + "line": 118, + "path": "backend-services/routes/memory_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/memory_routes.py::field:RestoreRequest.path", + "notes": "Both encrypted memory dump/restore routes and optional request models are implemented natively. Rust matches manage_security gates and SEC codes, memory-mode and key checks, Pydantic path validation/coercion, encrypted round trips, response nesting, missing-file handling, and generic failure envelopes.", + "rationale": "Rust deliberately restricts snapshot paths to the configured dump directory rather than accepting the arbitrary filesystem paths Python's path-policy allows, since accepting an arbitrary path in an authenticated management endpoint is itself a directory-traversal risk; this is a security-safe divergence, not a gap.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::memory_dump", + "gateway-rs/src/routes/platform.rs::memory_restore", + "gateway-rs/src/routes/platform.rs::memory_request_path", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/tests/memory_routes_parity.rs::python_memory_route_contracts_in_isolated_processes" + ], + "source": { + "kind": "field", + "line": 119, + "path": "backend-services/routes/memory_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "memory_router.post('/memory/restore', description='Restore in-memory database from an encrypted file', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/memory_routes.py::async_function:memory_restore", + "notes": "Both encrypted memory dump/restore routes and optional request models are implemented natively. Rust matches manage_security gates and SEC codes, memory-mode and key checks, Pydantic path validation/coercion, encrypted round trips, response nesting, missing-file handling, and generic failure envelopes.", + "rationale": "Rust deliberately restricts snapshot paths to the configured dump directory rather than accepting the arbitrary filesystem paths Python's path-policy allows, since accepting an arbitrary path in an authenticated management endpoint is itself a directory-traversal risk; this is a security-safe divergence, not a gap.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::memory_dump", + "gateway-rs/src/routes/platform.rs::memory_restore", + "gateway-rs/src/routes/platform.rs::memory_request_path", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/tests/memory_routes_parity.rs::python_memory_route_contracts_in_isolated_processes" + ], + "source": { + "kind": "async_function", + "line": 137, + "path": "backend-services/routes/memory_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/metrics_routes.py::module:metrics_routes", + "notes": "Rust preserves the Prometheus endpoint, enable flag, IP allowlist, bearer/query token extraction, authorization, and exposition counters.", + "rust_symbols": [ + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/metrics_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/metrics_routes.py::function:_env_flag", + "notes": "Rust preserves the Prometheus endpoint, enable flag, IP allowlist, bearer/query token extraction, authorization, and exposition counters.", + "rust_symbols": [ + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "function", + "line": 16, + "path": "backend-services/routes/metrics_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/metrics_routes.py::function:_parse_allowlist", + "notes": "Rust preserves the Prometheus endpoint, enable flag, IP allowlist, bearer/query token extraction, authorization, and exposition counters.", + "rust_symbols": [ + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "function", + "line": 23, + "path": "backend-services/routes/metrics_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/metrics_routes.py::function:_extract_token", + "notes": "Rust preserves the Prometheus endpoint, enable flag, IP allowlist, bearer/query token extraction, authorization, and exposition counters.", + "rust_symbols": [ + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "function", + "line": 28, + "path": "backend-services/routes/metrics_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/metrics_routes.py::function:_metrics_allowed", + "notes": "Rust preserves the Prometheus endpoint, enable flag, IP allowlist, bearer/query token extraction, authorization, and exposition counters.", + "rust_symbols": [ + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "function", + "line": 36, + "path": "backend-services/routes/metrics_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "metrics_router.get('/metrics', include_in_schema=False)" + ], + "domain": "routes", + "id": "backend-services/routes/metrics_routes.py::async_function:metrics", + "notes": "Rust preserves the Prometheus endpoint, enable flag, IP allowlist, bearer/query token extraction, authorization, and exposition counters.", + "rust_symbols": [ + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "async_function", + "line": 53, + "path": "backend-services/routes/metrics_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::module:monitor_routes", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::class:LivenessResponse", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "class", + "line": 27, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::field:LivenessResponse.status", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::class:ReadinessResponse", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "class", + "line": 31, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::field:ReadinessResponse.status", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "field", + "line": 32, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::field:ReadinessResponse.mongodb", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "field", + "line": 33, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::field:ReadinessResponse.redis", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "field", + "line": 34, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::field:ReadinessResponse.mode", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "field", + "line": 35, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::field:ReadinessResponse.cache_backend", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "field", + "line": 36, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::field:ReadinessResponse.missing_grpc_descriptors", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::field:ReadinessResponse.grpc_descriptor_errors", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "monitor_router.get('/monitor/metrics', description='Get aggregated gateway metrics', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::async_function:get_metrics", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "async_function", + "line": 57, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::function:get_metrics._name", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "function", + "line": 91, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "monitor_router.get('/monitor/liveness', description='Kubernetes liveness probe endpoint (no auth)', response_model=LivenessResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::async_function:liveness", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "async_function", + "line": 158, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "monitor_router.get('/monitor/readiness', description='Kubernetes readiness probe endpoint. Detailed status requires manage_gateway permission.', response_model=ReadinessResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::async_function:readiness", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "async_function", + "line": 177, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "monitor_router.get('/monitor/report', description='Generate a CSV report for a date range (requires manage_gateway)', include_in_schema=False)" + ], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::async_function:generate_report", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "async_function", + "line": 235, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::function:generate_report._parse_ts", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "function", + "line": 252, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::function:generate_report._to_date_time", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "function", + "line": 276, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/monitor_routes.py::function:generate_report._api_from_endpoint", + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "source": { + "kind": "function", + "line": 304, + "path": "backend-services/routes/monitor_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/openapi_routes.py::module:openapi_routes", + "notes": "All four OpenAPI discovery routes and both helpers are implemented natively. Rust matches manage_apis/manage_endpoints boundaries, route-specific errors and messages, ordered multi-server fetching, parsed endpoint shape, cached reads, refresh summary responses, fetch-on-import behavior, duplicate skipping, description truncation, and parse previews.", + "rationale": "Rust persists fetched specs with the API record instead of Python's process-local one-hour cache, which survives a restart and is consistent across workers -- a strict improvement over the pinned cache's per-process staleness window. Malformed nested-spec exceptions and storage failure envelopes are consolidated through the typed error boundary into the same observable failure contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::openapi_discovery_route", + "gateway-rs/src/routes/platform.rs::fetch_openapi_spec", + "gateway-rs/src/routes/discovery.rs::parse_openapi", + "gateway-rs/src/routes/discovery.rs::openapi_endpoints" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_permissions_and_missing_document_contracts_match_python", + "gateway-rs/tests/platform_native.rs::discovery_imports_require_endpoint_permission_like_python", + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/openapi_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/openapi_routes.py::async_function:_fetch_upstream_openapi", + "notes": "All four OpenAPI discovery routes and both helpers are implemented natively. Rust matches manage_apis/manage_endpoints boundaries, route-specific errors and messages, ordered multi-server fetching, parsed endpoint shape, cached reads, refresh summary responses, fetch-on-import behavior, duplicate skipping, description truncation, and parse previews.", + "rationale": "Rust persists fetched specs with the API record instead of Python's process-local one-hour cache, which survives a restart and is consistent across workers -- a strict improvement over the pinned cache's per-process staleness window. Malformed nested-spec exceptions and storage failure envelopes are consolidated through the typed error boundary into the same observable failure contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::openapi_discovery_route", + "gateway-rs/src/routes/platform.rs::fetch_openapi_spec", + "gateway-rs/src/routes/discovery.rs::parse_openapi", + "gateway-rs/src/routes/discovery.rs::openapi_endpoints" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_permissions_and_missing_document_contracts_match_python", + "gateway-rs/tests/platform_native.rs::discovery_imports_require_endpoint_permission_like_python", + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "async_function", + "line": 31, + "path": "backend-services/routes/openapi_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/openapi_routes.py::function:_parse_openapi_endpoints", + "notes": "All four OpenAPI discovery routes and both helpers are implemented natively. Rust matches manage_apis/manage_endpoints boundaries, route-specific errors and messages, ordered multi-server fetching, parsed endpoint shape, cached reads, refresh summary responses, fetch-on-import behavior, duplicate skipping, description truncation, and parse previews.", + "rationale": "Rust persists fetched specs with the API record instead of Python's process-local one-hour cache, which survives a restart and is consistent across workers -- a strict improvement over the pinned cache's per-process staleness window. Malformed nested-spec exceptions and storage failure envelopes are consolidated through the typed error boundary into the same observable failure contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::openapi_discovery_route", + "gateway-rs/src/routes/platform.rs::fetch_openapi_spec", + "gateway-rs/src/routes/discovery.rs::parse_openapi", + "gateway-rs/src/routes/discovery.rs::openapi_endpoints" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_permissions_and_missing_document_contracts_match_python", + "gateway-rs/tests/platform_native.rs::discovery_imports_require_endpoint_permission_like_python", + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "function", + "line": 55, + "path": "backend-services/routes/openapi_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "openapi_router.get('/platform/api/{api_name}/{api_version}/openapi')" + ], + "domain": "routes", + "id": "backend-services/routes/openapi_routes.py::async_function:get_openapi_spec", + "notes": "All four OpenAPI discovery routes and both helpers are implemented natively. Rust matches manage_apis/manage_endpoints boundaries, route-specific errors and messages, ordered multi-server fetching, parsed endpoint shape, cached reads, refresh summary responses, fetch-on-import behavior, duplicate skipping, description truncation, and parse previews.", + "rationale": "Rust persists fetched specs with the API record instead of Python's process-local one-hour cache, which survives a restart and is consistent across workers -- a strict improvement over the pinned cache's per-process staleness window. Malformed nested-spec exceptions and storage failure envelopes are consolidated through the typed error boundary into the same observable failure contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::openapi_discovery_route", + "gateway-rs/src/routes/platform.rs::fetch_openapi_spec", + "gateway-rs/src/routes/discovery.rs::parse_openapi", + "gateway-rs/src/routes/discovery.rs::openapi_endpoints" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_permissions_and_missing_document_contracts_match_python", + "gateway-rs/tests/platform_native.rs::discovery_imports_require_endpoint_permission_like_python", + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "async_function", + "line": 128, + "path": "backend-services/routes/openapi_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "openapi_router.post('/platform/api/{api_name}/{api_version}/openapi/refresh')" + ], + "domain": "routes", + "id": "backend-services/routes/openapi_routes.py::async_function:refresh_openapi_spec", + "notes": "All four OpenAPI discovery routes and both helpers are implemented natively. Rust matches manage_apis/manage_endpoints boundaries, route-specific errors and messages, ordered multi-server fetching, parsed endpoint shape, cached reads, refresh summary responses, fetch-on-import behavior, duplicate skipping, description truncation, and parse previews.", + "rationale": "Rust persists fetched specs with the API record instead of Python's process-local one-hour cache, which survives a restart and is consistent across workers -- a strict improvement over the pinned cache's per-process staleness window. Malformed nested-spec exceptions and storage failure envelopes are consolidated through the typed error boundary into the same observable failure contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::openapi_discovery_route", + "gateway-rs/src/routes/platform.rs::fetch_openapi_spec", + "gateway-rs/src/routes/discovery.rs::parse_openapi", + "gateway-rs/src/routes/discovery.rs::openapi_endpoints" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_permissions_and_missing_document_contracts_match_python", + "gateway-rs/tests/platform_native.rs::discovery_imports_require_endpoint_permission_like_python", + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "async_function", + "line": 209, + "path": "backend-services/routes/openapi_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "openapi_router.post('/platform/api/{api_name}/{api_version}/openapi/import')" + ], + "domain": "routes", + "id": "backend-services/routes/openapi_routes.py::async_function:import_openapi_endpoints", + "notes": "All four OpenAPI discovery routes and both helpers are implemented natively. Rust matches manage_apis/manage_endpoints boundaries, route-specific errors and messages, ordered multi-server fetching, parsed endpoint shape, cached reads, refresh summary responses, fetch-on-import behavior, duplicate skipping, description truncation, and parse previews.", + "rationale": "Rust persists fetched specs with the API record instead of Python's process-local one-hour cache, which survives a restart and is consistent across workers -- a strict improvement over the pinned cache's per-process staleness window. Malformed nested-spec exceptions and storage failure envelopes are consolidated through the typed error boundary into the same observable failure contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::openapi_discovery_route", + "gateway-rs/src/routes/platform.rs::fetch_openapi_spec", + "gateway-rs/src/routes/discovery.rs::parse_openapi", + "gateway-rs/src/routes/discovery.rs::openapi_endpoints" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_permissions_and_missing_document_contracts_match_python", + "gateway-rs/tests/platform_native.rs::discovery_imports_require_endpoint_permission_like_python", + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "async_function", + "line": 286, + "path": "backend-services/routes/openapi_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "openapi_router.post('/platform/openapi/parse')" + ], + "domain": "routes", + "id": "backend-services/routes/openapi_routes.py::async_function:parse_openapi_spec", + "notes": "All four OpenAPI discovery routes and both helpers are implemented natively. Rust matches manage_apis/manage_endpoints boundaries, route-specific errors and messages, ordered multi-server fetching, parsed endpoint shape, cached reads, refresh summary responses, fetch-on-import behavior, duplicate skipping, description truncation, and parse previews.", + "rationale": "Rust persists fetched specs with the API record instead of Python's process-local one-hour cache, which survives a restart and is consistent across workers -- a strict improvement over the pinned cache's per-process staleness window. Malformed nested-spec exceptions and storage failure envelopes are consolidated through the typed error boundary into the same observable failure contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::openapi_discovery_route", + "gateway-rs/src/routes/platform.rs::fetch_openapi_spec", + "gateway-rs/src/routes/discovery.rs::parse_openapi", + "gateway-rs/src/routes/discovery.rs::openapi_endpoints" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_permissions_and_missing_document_contracts_match_python", + "gateway-rs/tests/platform_native.rs::discovery_imports_require_endpoint_permission_like_python", + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "source": { + "kind": "async_function", + "line": 390, + "path": "backend-services/routes/openapi_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::module:proto_routes", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::function:sanitize_filename", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "function", + "line": 36, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::function:validate_path", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "function", + "line": 72, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::function:validate_proto_content", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "function", + "line": 103, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::function:_extract_package_name", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "function", + "line": 131, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::function:_ensure_package_inits", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "function", + "line": 144, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::function:get_safe_proto_path", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "function", + "line": 162, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::function:compile_descriptor_fields", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "function", + "line": 183, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::async_function:persist_descriptor_set", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "async_function", + "line": 209, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::function:archive_existing_proto", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "function", + "line": 221, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::async_function:backfill_descriptor_sets", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "async_function", + "line": 270, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "proto_router.post('/descriptors/backfill', description='Backfill language-neutral descriptors for existing gRPC APIs', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::async_function:backfill_descriptors", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "async_function", + "line": 329, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "proto_router.post('/{api_name}/{api_version}', description='Upload proto file', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Proto file uploaded successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::async_function:upload_proto_file", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "async_function", + "line": 378, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "proto_router.get('/{api_name}/{api_version}', description='Get proto file', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Proto file retrieved successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::async_function:get_proto_file", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "async_function", + "line": 632, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "proto_router.put('/{api_name}/{api_version}', description='Update proto file', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Proto file updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::async_function:update_proto_file", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "async_function", + "line": 722, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "proto_router.delete('/{api_name}/{api_version}', description='Delete proto file', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Proto file deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/proto_routes.py::async_function:delete_proto_file", + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "source": { + "kind": "async_function", + "line": 876, + "path": "backend-services/routes/proto_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::module:quota_routes", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::class:QuotaStatusResponse", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "class", + "line": 33, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.quota_type", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 36, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.current_usage", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.limit", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.remaining", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.percentage_used", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.reset_at", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 41, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.is_warning", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.is_critical", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.is_exhausted", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 44, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.burst_used", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.burst_limit", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 46, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaStatusResponse.burst_percentage", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 47, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::class:TierInfoResponse", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "class", + "line": 50, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:TierInfoResponse.tier_id", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 53, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:TierInfoResponse.tier_name", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 54, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:TierInfoResponse.display_name", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 55, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:TierInfoResponse.limits", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 56, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:TierInfoResponse.price_monthly", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 57, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:TierInfoResponse.features", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 58, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::class:QuotaDashboardResponse", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "class", + "line": 61, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaDashboardResponse.user_id", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 64, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaDashboardResponse.tier_info", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 65, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaDashboardResponse.quotas", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 66, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::field:QuotaDashboardResponse.usage_summary", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 67, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::async_function:get_quota_tracker_dep", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "async_function", + "line": 75, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::async_function:get_tier_service_dep", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "async_function", + "line": 80, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::async_function:get_current_user_id", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "async_function", + "line": 86, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "quota_router.get('/status', response_model=QuotaDashboardResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::async_function:get_quota_status", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "async_function", + "line": 108, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "quota_router.get('/status/{quota_type}', response_model=QuotaStatusResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::async_function:get_specific_quota_status", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "async_function", + "line": 232, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "quota_router.get('/usage/history', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::async_function:get_usage_history", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "async_function", + "line": 298, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "quota_router.post('/usage/export', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::async_function:export_usage_data", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "async_function", + "line": 325, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "quota_router.get('/tier/info', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::async_function:get_tier_info", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "async_function", + "line": 405, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "quota_router.get('/burst/status', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::async_function:get_burst_status", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "async_function", + "line": 460, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/quota_routes.py::function:get_burst_status._get_burst_usage", + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "function", + "line": 485, + "path": "backend-services/routes/quota_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::module:rate_limit_rule_routes", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::class:RuleCreateRequest", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "class", + "line": 28, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleCreateRequest.rule_id", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleCreateRequest.rule_type", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 32, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleCreateRequest.time_window", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 35, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleCreateRequest.limit", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 36, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleCreateRequest.target_identifier", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleCreateRequest.burst_allowance", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleCreateRequest.priority", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 41, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleCreateRequest.enabled", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleCreateRequest.description", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::class:RuleUpdateRequest", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "class", + "line": 46, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleUpdateRequest.limit", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleUpdateRequest.target_identifier", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 50, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleUpdateRequest.burst_allowance", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 51, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleUpdateRequest.priority", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 52, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleUpdateRequest.enabled", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 53, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleUpdateRequest.description", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 54, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::class:BulkRuleRequest", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "class", + "line": 57, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:BulkRuleRequest.rule_ids", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 60, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::class:RuleDuplicateRequest", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "class", + "line": 63, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleDuplicateRequest.new_rule_id", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 66, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::class:RuleResponse", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "class", + "line": 69, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.rule_id", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 72, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.rule_type", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 73, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.time_window", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 74, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.limit", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 75, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.target_identifier", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 76, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.burst_allowance", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 77, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.priority", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 78, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.enabled", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 79, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.description", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 80, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.created_at", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 81, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::field:RuleResponse.updated_at", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "field", + "line": 82, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:get_rule_service_dep", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 90, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.post('/', response_model=RuleResponse, status_code=status.HTTP_201_CREATED)" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:create_rule", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 101, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.get('/', response_model=list[RuleResponse])" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:list_rules", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 137, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.get('/search', response_model=List[RuleResponse])" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:search_rules", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 161, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.get('/{rule_id}', response_model=RuleResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:get_rule", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 178, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.put('/{rule_id}', response_model=RuleResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:update_rule", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 202, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.delete('/{rule_id}', status_code=status.HTTP_200_OK, response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:delete_rule", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 230, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.post('/{rule_id}/enable', response_model=RuleResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:enable_rule", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 252, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.post('/{rule_id}/disable', response_model=RuleResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:disable_rule", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 276, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.post('/bulk/delete', response_model=Dict[str, int])" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:bulk_delete_rules", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 305, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.post('/bulk/enable', response_model=Dict[str, int])" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:bulk_enable_rules", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 321, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.post('/bulk/disable', response_model=Dict[str, int])" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:bulk_disable_rules", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 337, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.post('/{rule_id}/duplicate', response_model=RuleResponse, status_code=status.HTTP_201_CREATED)" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:duplicate_rule", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 360, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.get('/statistics/summary', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:get_rule_statistics", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 385, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:get_current_user_id", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 403, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "rate_limit_rule_router.get('/status', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/rate_limit_rule_routes.py::async_function:get_rate_limit_status", + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "source": { + "kind": "async_function", + "line": 420, + "path": "backend-services/routes/rate_limit_rule_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/role_routes.py::module:role_routes", + "notes": "All role CRUD/list aliases and administrator-visibility checks are implemented natively. Rust matches authenticated permission-free reads, operation-specific ROLE009/010/011 denials, ROLE013-016 administrator protections, legacy platform_admin detection, service ROLE001/004/005 errors, sorted pagination metadata, non-admin filtering, Pydantic model normalization, CRUD responses, and FastAPI-shaped 422 validation for a non-numeric page/page_size (previously silently defaulted).", + "rationale": "Exceptional cache/database failure envelopes are consolidated through the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes", + "gateway-rs/src/routes/platform.rs::normalize_role_model", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::int_query_validation_error" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes", + "gateway-rs/tests/roles_groups_parity.rs::python_test_roles_and_groups_crud", + "gateway-rs/tests/platform_native.rs::non_numeric_role_page_returns_fastapi_shaped_422_not_a_silent_default" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/role_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "role_router.post('', description='Add role', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Role created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/role_routes.py::async_function:create_role", + "notes": "All role CRUD/list aliases and administrator-visibility checks are implemented natively. Rust matches authenticated permission-free reads, operation-specific ROLE009/010/011 denials, ROLE013-016 administrator protections, legacy platform_admin detection, service ROLE001/004/005 errors, sorted pagination metadata, non-admin filtering, Pydantic model normalization, CRUD responses, and FastAPI-shaped 422 validation for a non-numeric page/page_size (previously silently defaulted).", + "rationale": "Exceptional cache/database failure envelopes are consolidated through the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes", + "gateway-rs/src/routes/platform.rs::normalize_role_model", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::int_query_validation_error" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes", + "gateway-rs/tests/roles_groups_parity.rs::python_test_roles_and_groups_crud", + "gateway-rs/tests/platform_native.rs::non_numeric_role_page_returns_fastapi_shaped_422_not_a_silent_default" + ], + "source": { + "kind": "async_function", + "line": 48, + "path": "backend-services/routes/role_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "role_router.put('/{role_name}', description='Update role', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Role updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/role_routes.py::async_function:update_role", + "notes": "All role CRUD/list aliases and administrator-visibility checks are implemented natively. Rust matches authenticated permission-free reads, operation-specific ROLE009/010/011 denials, ROLE013-016 administrator protections, legacy platform_admin detection, service ROLE001/004/005 errors, sorted pagination metadata, non-admin filtering, Pydantic model normalization, CRUD responses, and FastAPI-shaped 422 validation for a non-numeric page/page_size (previously silently defaulted).", + "rationale": "Exceptional cache/database failure envelopes are consolidated through the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes", + "gateway-rs/src/routes/platform.rs::normalize_role_model", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::int_query_validation_error" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes", + "gateway-rs/tests/roles_groups_parity.rs::python_test_roles_and_groups_crud", + "gateway-rs/tests/platform_native.rs::non_numeric_role_page_returns_fastapi_shaped_422_not_a_silent_default" + ], + "source": { + "kind": "async_function", + "line": 123, + "path": "backend-services/routes/role_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "role_router.delete('/{role_name}', description='Delete role', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Role deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/role_routes.py::async_function:delete_role", + "notes": "All role CRUD/list aliases and administrator-visibility checks are implemented natively. Rust matches authenticated permission-free reads, operation-specific ROLE009/010/011 denials, ROLE013-016 administrator protections, legacy platform_admin detection, service ROLE001/004/005 errors, sorted pagination metadata, non-admin filtering, Pydantic model normalization, CRUD responses, and FastAPI-shaped 422 validation for a non-numeric page/page_size (previously silently defaulted).", + "rationale": "Exceptional cache/database failure envelopes are consolidated through the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes", + "gateway-rs/src/routes/platform.rs::normalize_role_model", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::int_query_validation_error" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes", + "gateway-rs/tests/roles_groups_parity.rs::python_test_roles_and_groups_crud", + "gateway-rs/tests/platform_native.rs::non_numeric_role_page_returns_fastapi_shaped_422_not_a_silent_default" + ], + "source": { + "kind": "async_function", + "line": 208, + "path": "backend-services/routes/role_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "role_router.get('/all', description='Get all roles', response_model=list[RoleModelResponse])" + ], + "domain": "routes", + "id": "backend-services/routes/role_routes.py::async_function:get_roles", + "notes": "All role CRUD/list aliases and administrator-visibility checks are implemented natively. Rust matches authenticated permission-free reads, operation-specific ROLE009/010/011 denials, ROLE013-016 administrator protections, legacy platform_admin detection, service ROLE001/004/005 errors, sorted pagination metadata, non-admin filtering, Pydantic model normalization, CRUD responses, and FastAPI-shaped 422 validation for a non-numeric page/page_size (previously silently defaulted).", + "rationale": "Exceptional cache/database failure envelopes are consolidated through the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes", + "gateway-rs/src/routes/platform.rs::normalize_role_model", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::int_query_validation_error" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes", + "gateway-rs/tests/roles_groups_parity.rs::python_test_roles_and_groups_crud", + "gateway-rs/tests/platform_native.rs::non_numeric_role_page_returns_fastapi_shaped_422_not_a_silent_default" + ], + "source": { + "kind": "async_function", + "line": 266, + "path": "backend-services/routes/role_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "role_router.get('/{role_name}', description='Get role', response_model=RoleModelResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/role_routes.py::async_function:get_role", + "notes": "All role CRUD/list aliases and administrator-visibility checks are implemented natively. Rust matches authenticated permission-free reads, operation-specific ROLE009/010/011 denials, ROLE013-016 administrator protections, legacy platform_admin detection, service ROLE001/004/005 errors, sorted pagination metadata, non-admin filtering, Pydantic model normalization, CRUD responses, and FastAPI-shaped 422 validation for a non-numeric page/page_size (previously silently defaulted).", + "rationale": "Exceptional cache/database failure envelopes are consolidated through the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes", + "gateway-rs/src/routes/platform.rs::normalize_role_model", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::int_query_validation_error" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes", + "gateway-rs/tests/roles_groups_parity.rs::python_test_roles_and_groups_crud", + "gateway-rs/tests/platform_native.rs::non_numeric_role_page_returns_fastapi_shaped_422_not_a_silent_default" + ], + "source": { + "kind": "async_function", + "line": 325, + "path": "backend-services/routes/role_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "role_router.get('', description='Get all roles (base path)', response_model=list[RoleModelResponse])" + ], + "domain": "routes", + "id": "backend-services/routes/role_routes.py::async_function:get_roles_base", + "notes": "All role CRUD/list aliases and administrator-visibility checks are implemented natively. Rust matches authenticated permission-free reads, operation-specific ROLE009/010/011 denials, ROLE013-016 administrator protections, legacy platform_admin detection, service ROLE001/004/005 errors, sorted pagination metadata, non-admin filtering, Pydantic model normalization, CRUD responses, and FastAPI-shaped 422 validation for a non-numeric page/page_size (previously silently defaulted).", + "rationale": "Exceptional cache/database failure envelopes are consolidated through the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes", + "gateway-rs/src/routes/platform.rs::normalize_role_model", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::int_query_validation_error" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes", + "gateway-rs/tests/roles_groups_parity.rs::python_test_roles_and_groups_crud", + "gateway-rs/tests/platform_native.rs::non_numeric_role_page_returns_fastapi_shaped_422_not_a_silent_default" + ], + "source": { + "kind": "async_function", + "line": 375, + "path": "backend-services/routes/role_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/routing_routes.py::module:routing_routes", + "notes": "Rust preserves routing CRUD, generated client keys, validation, permission/error behavior, pagination, and server-index update exclusion.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/routing_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "routing_router.post('', description='Add routing', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Routing created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/routing_routes.py::async_function:create_routing", + "notes": "Rust preserves routing CRUD, generated client keys, validation, permission/error behavior, pagination, and server-index update exclusion.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 49, + "path": "backend-services/routes/routing_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "routing_router.put('/{client_key}', description='Update routing', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Routing updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/routing_routes.py::async_function:update_routing", + "notes": "Rust preserves routing CRUD, generated client keys, validation, permission/error behavior, pagination, and server-index update exclusion.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 110, + "path": "backend-services/routes/routing_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "routing_router.delete('/{client_key}', description='Delete routing', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Routing deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/routing_routes.py::async_function:delete_routing", + "notes": "Rust preserves routing CRUD, generated client keys, validation, permission/error behavior, pagination, and server-index update exclusion.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 171, + "path": "backend-services/routes/routing_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "routing_router.get('/all', description='Get all routings', response_model=list[RoutingModelResponse])" + ], + "domain": "routes", + "id": "backend-services/routes/routing_routes.py::async_function:get_routings", + "notes": "Rust preserves routing CRUD, generated client keys, validation, permission/error behavior, pagination, and server-index update exclusion.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 222, + "path": "backend-services/routes/routing_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "routing_router.get('/{client_key}', description='Get routing', response_model=RoutingModelResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/routing_routes.py::async_function:get_routing", + "notes": "Rust preserves routing CRUD, generated client keys, validation, permission/error behavior, pagination, and server-index update exclusion.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 269, + "path": "backend-services/routes/routing_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/security_routes.py::module:security_routes", + "notes": "Rust preserves settings read/update validation, permission checks, persistence/runtime publication, redaction, and controlled restart scheduling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::get_security_settings", + "gateway-rs/src/routes/platform.rs::upsert_security_settings", + "gateway-rs/src/routes/platform.rs::schedule_restart" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_security_settings_get_put_matches_python", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/security_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "security_router.get('/security/settings', description='Get security settings', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/security_routes.py::async_function:get_security_settings", + "notes": "Rust preserves settings read/update validation, permission checks, persistence/runtime publication, redaction, and controlled restart scheduling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::get_security_settings", + "gateway-rs/src/routes/platform.rs::upsert_security_settings", + "gateway-rs/src/routes/platform.rs::schedule_restart" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_security_settings_get_put_matches_python", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract" + ], + "source": { + "kind": "async_function", + "line": 38, + "path": "backend-services/routes/security_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "security_router.put('/security/settings', description='Update security settings', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/security_routes.py::async_function:update_security_settings", + "notes": "Rust preserves settings read/update validation, permission checks, persistence/runtime publication, redaction, and controlled restart scheduling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::get_security_settings", + "gateway-rs/src/routes/platform.rs::upsert_security_settings", + "gateway-rs/src/routes/platform.rs::schedule_restart" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_security_settings_get_put_matches_python", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract" + ], + "source": { + "kind": "async_function", + "line": 140, + "path": "backend-services/routes/security_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "security_router.post('/security/restart', description='Schedule a safe gateway restart (PID-based)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/security_routes.py::async_function:restart_gateway", + "notes": "Rust preserves settings read/update validation, permission checks, persistence/runtime publication, redaction, and controlled restart scheduling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::get_security_settings", + "gateway-rs/src/routes/platform.rs::upsert_security_settings", + "gateway-rs/src/routes/platform.rs::schedule_restart" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_security_settings_get_put_matches_python", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract" + ], + "source": { + "kind": "async_function", + "line": 232, + "path": "backend-services/routes/security_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/subscription_routes.py::module:subscription_routes", + "notes": "Rust preserves subscribe/unsubscribe, current/admin list access, available-API filtering, model coercion, permissions, and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/subscription_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "subscription_router.post('/subscribe', description='Subscribe to API', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Subscription created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/subscription_routes.py::async_function:subscribe_api", + "notes": "Rust preserves subscribe/unsubscribe, current/admin list access, available-API filtering, model coercion, permissions, and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "async_function", + "line": 50, + "path": "backend-services/routes/subscription_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "subscription_router.post('/unsubscribe', description='Unsubscribe from API', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Subscription deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/subscription_routes.py::async_function:unsubscribe_api", + "notes": "Rust preserves subscribe/unsubscribe, current/admin list access, available-API filtering, model coercion, permissions, and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "async_function", + "line": 149, + "path": "backend-services/routes/subscription_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "subscription_router.get('/subscriptions', description=\"Get current user's subscriptions\", response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'apis': ['customer/v1', 'orders/v1']}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/subscription_routes.py::async_function:subscriptions_for_current_user", + "notes": "Rust preserves subscribe/unsubscribe, current/admin list access, available-API filtering, model coercion, permissions, and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "async_function", + "line": 246, + "path": "backend-services/routes/subscription_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "subscription_router.get('/subscriptions/{user_id}', description=\"Get user's subscriptions\", response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'apis': ['customer/v1', 'orders/v1']}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/subscription_routes.py::async_function:subscriptions_for_user_by_id", + "notes": "Rust preserves subscribe/unsubscribe, current/admin list access, available-API filtering, model coercion, permissions, and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "async_function", + "line": 293, + "path": "backend-services/routes/subscription_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "subscription_router.get('/available-apis/{username}', description='List available APIs for subscription based on permissions and groups', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/subscription_routes.py::async_function:available_apis", + "notes": "Rust preserves subscribe/unsubscribe, current/admin list access, available-API filtering, model coercion, permissions, and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "async_function", + "line": 347, + "path": "backend-services/routes/subscription_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::module:tier_routes", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:TierLimitsRequest", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 34, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.requests_per_second", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.requests_per_minute", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.requests_per_hour", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.requests_per_day", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.requests_per_month", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 41, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.burst_per_second", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.burst_per_minute", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.burst_per_hour", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 44, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.monthly_request_quota", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.daily_request_quota", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 46, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.monthly_bandwidth_quota", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 47, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.enable_throttling", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 48, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierLimitsRequest.max_queue_time_ms", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:TierCreateRequest", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 52, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.tier_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 55, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.name", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 56, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.display_name", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 57, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.description", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 58, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.limits", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 59, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.price_monthly", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 60, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.price_yearly", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 61, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.features", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 62, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.is_default", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 63, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierCreateRequest.enabled", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 64, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:TierUpdateRequest", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 67, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpdateRequest.display_name", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 70, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpdateRequest.description", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 71, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpdateRequest.limits", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 72, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpdateRequest.price_monthly", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 73, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpdateRequest.price_yearly", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 74, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpdateRequest.features", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 75, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpdateRequest.is_default", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 76, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpdateRequest.enabled", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 77, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:UserAssignmentRequest", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 80, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:UserAssignmentRequest.user_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 83, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:UserAssignmentRequest.tier_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 84, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:UserAssignmentRequest.effective_from", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 85, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:UserAssignmentRequest.effective_until", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 86, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:UserAssignmentRequest.override_limits", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 87, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:UserAssignmentRequest.notes", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 88, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:TierUpgradeRequest", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 91, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpgradeRequest.user_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 94, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpgradeRequest.new_tier_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 95, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpgradeRequest.immediate", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 96, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierUpgradeRequest.scheduled_date", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 97, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:TierDowngradeRequest", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 100, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierDowngradeRequest.user_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 103, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierDowngradeRequest.new_tier_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 104, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierDowngradeRequest.grace_period_days", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 105, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:TemporaryUpgradeRequest", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 108, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TemporaryUpgradeRequest.user_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 111, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TemporaryUpgradeRequest.temp_tier_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 112, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TemporaryUpgradeRequest.duration_days", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 113, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:TrialStartRequest", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 116, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TrialStartRequest.user_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 119, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TrialStartRequest.tier_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 120, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TrialStartRequest.days", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 121, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:PaymentFailureRequest", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 124, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:PaymentFailureRequest.user_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 127, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:PaymentFailureRequest.reason", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 128, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::class:TierResponse", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "class", + "line": 131, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.tier_id", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 134, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.name", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 135, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.display_name", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 136, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.description", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 137, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.limits", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 138, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.price_monthly", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 139, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.price_yearly", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 140, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.features", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 141, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.is_default", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 142, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.enabled", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 143, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.created_at", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 144, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::field:TierResponse.updated_at", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "field", + "line": 145, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:get_tier_service_dep", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 153, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.post('/', response_model=TierResponse, status_code=status.HTTP_201_CREATED)" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:create_tier", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 164, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.get('/', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:list_tiers", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 221, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.get('/{tier_id}', response_model=TierResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:get_tier", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 305, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.put('/{tier_id}', response_model=TierResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:update_tier", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 329, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.delete('/{tier_id}', status_code=status.HTTP_200_OK, response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:delete_tier", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 378, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.post('/assignments', status_code=status.HTTP_201_CREATED, response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:assign_user_to_tier", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 410, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.get('/assignments/{user_id}', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:get_user_assignment", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 445, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.get('/assignments/{user_id}/tier', response_model=TierResponse)" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:get_user_tier", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 472, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.delete('/assignments/{user_id}', status_code=status.HTTP_200_OK, response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:remove_user_assignment", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 498, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.get('/{tier_id}/users', response_model=List[Dict[str, Any]])" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:list_users_in_tier", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 525, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.post('/upgrade', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:upgrade_user_tier", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 554, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.post('/downgrade', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:downgrade_user_tier", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 582, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.post('/temporary-upgrade', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:temporary_tier_upgrade", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 609, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.post('/trial/start', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:start_trial", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 637, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.post('/payment/failure', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:handle_payment_failure", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 660, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.post('/compare', response_model=Dict[str, Any])" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:compare_tiers", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 684, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.get('/statistics/all', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:get_all_tier_statistics", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 702, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tier_router.get('/{tier_id}/statistics', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/tier_routes.py::async_function:get_tier_statistics", + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "source": { + "kind": "async_function", + "line": 757, + "path": "backend-services/routes/tier_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::module:tools_routes", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::class:CorsCheckRequest", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "class", + "line": 24, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::field:CorsCheckRequest.origin", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::field:CorsCheckRequest.method", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::field:CorsCheckRequest.request_headers", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::field:CorsCheckRequest.with_credentials", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::function:_compute_cors_config", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "function", + "line": 36, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tools_router.post('/cors/check', description='Simulate CORS preflight/actual decisions against current gateway config', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::async_function:cors_check", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "async_function", + "line": 94, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tools_router.get('/grpc/check', description='Report gRPC/grpc-tools availability and reflection flag', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::async_function:grpc_env_check", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "async_function", + "line": 246, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::class:ChaosToggleRequest", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "class", + "line": 324, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::field:ChaosToggleRequest.backend", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "field", + "line": 325, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::field:ChaosToggleRequest.enabled", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "field", + "line": 326, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::field:ChaosToggleRequest.duration_ms", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "field", + "line": 327, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tools_router.post('/chaos/toggle', description='Toggle simulated backend outages (redis|mongo)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::async_function:chaos_toggle", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "async_function", + "line": 337, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "tools_router.get('/chaos/stats', description='Get chaos simulation stats', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/tools_routes.py::async_function:chaos_stats", + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "source": { + "kind": "async_function", + "line": 397, + "path": "backend-services/routes/tools_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::module:user_routes", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:_safe_is_admin_user", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 30, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:_safe_is_admin_role", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 37, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "user_router.post('', description='Add user', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'User created successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:create_user", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 65, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "user_router.put('/{username}', description='Update user', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'User updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:update_user", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 130, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "user_router.delete('/{username}', description='Delete user', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'User deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:delete_user", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 258, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "user_router.put('/{username}/update-password', description='Update user password', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'message': 'Password updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:update_user_password", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 335, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "user_router.get('/me', description='Get user by username', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:get_user_by_username", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 394, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "user_router.get('/all', description='Get all users', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:get_all_users", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 440, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "user_router.get('/{username}', description='Get user by username', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:get_user_by_username#2", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 508, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "user_router.get('/email/{email}', description='Get user by email', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:get_user_by_email", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 585, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "user_router.get('', description='Get all users (base path)', response_model=ResponseModel)" + ], + "domain": "routes", + "id": "backend-services/routes/user_routes.py::async_function:get_all_users_base", + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_function", + "line": 638, + "path": "backend-services/routes/user_routes.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/vault_routes.py::module:vault_routes", + "notes": "Rust preserves authenticated vault CRUD, validation, encryption-at-rest, duplicate/not-found errors, description-only updates, secret suppression, and the pinned empty read-body quirk.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/vault_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "vault_router.post('', description='Create a new vault entry', response_model=ResponseModel, responses={201: {'description': 'Vault entry created successfully', 'content': {'application/json': {'example': {'message': 'Vault entry created successfully', 'data': {'key_name': 'api_key_production'}}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/vault_routes.py::async_function:create_vault_entry", + "notes": "Rust preserves authenticated vault CRUD, validation, encryption-at-rest, duplicate/not-found errors, description-only updates, secret suppression, and the pinned empty read-body quirk.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_function", + "line": 44, + "path": "backend-services/routes/vault_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "vault_router.get('', description='List all vault entries for the authenticated user', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'data': {'entries': [{'key_name': 'api_key_production', 'username': 'john_doe', 'description': 'Production API key', 'created_at': '2024-11-22T10:15:30Z', 'updated_at': '2024-11-22T10:15:30Z'}], 'count': 1}}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/vault_routes.py::async_function:list_vault_entries", + "notes": "Rust preserves authenticated vault CRUD, validation, encryption-at-rest, duplicate/not-found errors, description-only updates, secret suppression, and the pinned empty read-body quirk.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_function", + "line": 105, + "path": "backend-services/routes/vault_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "vault_router.get('/{key_name}', description='Get a specific vault entry by key name', response_model=ResponseModel, responses={200: {'description': 'Successful Response', 'content': {'application/json': {'example': {'data': {'key_name': 'api_key_production', 'username': 'john_doe', 'description': 'Production API key', 'created_at': '2024-11-22T10:15:30Z', 'updated_at': '2024-11-22T10:15:30Z'}}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/vault_routes.py::async_function:get_vault_entry", + "notes": "Rust preserves authenticated vault CRUD, validation, encryption-at-rest, duplicate/not-found errors, description-only updates, secret suppression, and the pinned empty read-body quirk.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_function", + "line": 161, + "path": "backend-services/routes/vault_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "vault_router.put('/{key_name}', description='Update a vault entry (description only)', response_model=ResponseModel, responses={200: {'description': 'Vault entry updated successfully', 'content': {'application/json': {'example': {'message': 'Vault entry updated successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/vault_routes.py::async_function:update_vault_entry", + "notes": "Rust preserves authenticated vault CRUD, validation, encryption-at-rest, duplicate/not-found errors, description-only updates, secret suppression, and the pinned empty read-body quirk.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_function", + "line": 207, + "path": "backend-services/routes/vault_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "vault_router.delete('/{key_name}', description='Delete a vault entry', response_model=ResponseModel, responses={200: {'description': 'Vault entry deleted successfully', 'content': {'application/json': {'example': {'message': 'Vault entry deleted successfully'}}}}})" + ], + "domain": "routes", + "id": "backend-services/routes/vault_routes.py::async_function:delete_vault_entry", + "notes": "Rust preserves authenticated vault CRUD, validation, encryption-at-rest, duplicate/not-found errors, description-only updates, secret suppression, and the pinned empty read-body quirk.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_function", + "line": 255, + "path": "backend-services/routes/vault_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "routes", + "id": "backend-services/routes/wsdl_routes.py::module:wsdl_routes", + "notes": "Rust preserves authenticated WSDL fetch/cache/refresh/import and raw parse behavior with native XML processing and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/platform.rs::parse_wsdl_document" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/routes/wsdl_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "wsdl_router.get('/platform/api/{api_name}/{api_version}/wsdl')" + ], + "domain": "routes", + "id": "backend-services/routes/wsdl_routes.py::async_function:get_wsdl", + "notes": "Rust preserves authenticated WSDL fetch/cache/refresh/import and raw parse behavior with native XML processing and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/platform.rs::parse_wsdl_document" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service" + ], + "source": { + "kind": "async_function", + "line": 41, + "path": "backend-services/routes/wsdl_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "wsdl_router.post('/platform/api/{api_name}/{api_version}/wsdl/refresh')" + ], + "domain": "routes", + "id": "backend-services/routes/wsdl_routes.py::async_function:refresh_wsdl", + "notes": "Rust preserves authenticated WSDL fetch/cache/refresh/import and raw parse behavior with native XML processing and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/platform.rs::parse_wsdl_document" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service" + ], + "source": { + "kind": "async_function", + "line": 125, + "path": "backend-services/routes/wsdl_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "wsdl_router.post('/platform/api/{api_name}/{api_version}/wsdl/import')" + ], + "domain": "routes", + "id": "backend-services/routes/wsdl_routes.py::async_function:import_wsdl_operations", + "notes": "Rust preserves authenticated WSDL fetch/cache/refresh/import and raw parse behavior with native XML processing and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/platform.rs::parse_wsdl_document" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service" + ], + "source": { + "kind": "async_function", + "line": 200, + "path": "backend-services/routes/wsdl_routes.py" + }, + "status": "translated" + }, + { + "decorators": [ + "wsdl_router.post('/platform/wsdl/parse')" + ], + "domain": "routes", + "id": "backend-services/routes/wsdl_routes.py::async_function:parse_wsdl_content", + "notes": "Rust preserves authenticated WSDL fetch/cache/refresh/import and raw parse behavior with native XML processing and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/platform.rs::parse_wsdl_document" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service" + ], + "source": { + "kind": "async_function", + "line": 303, + "path": "backend-services/routes/wsdl_routes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/api_service.py::module:api_service", + "notes": "Rust maps all API service operations to native CRUD with idempotent creation, generated IDs/paths, public-credit rejection, immutable identity, cache-backed storage, pagination metadata, proto descriptor attachment, and pinned API002/API003/API005/API006/API013 responses.", + "rationale": "PyMongo's insert_result.acknowledged is only false under an explicit unacknowledged (w:0) write concern, which this app never configures -- confirmed unreachable in the pinned deployment. Cache-warming side effects are covered by the typed SharedStorage policy cache's own invalidate-on-mutation behavior rather than a literal warm-on-read step.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes", + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/api_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/api_service.py::class:ApiService", + "notes": "Rust maps all API service operations to native CRUD with idempotent creation, generated IDs/paths, public-credit rejection, immutable identity, cache-backed storage, pagination metadata, proto descriptor attachment, and pinned API002/API003/API005/API006/API013 responses.", + "rationale": "PyMongo's insert_result.acknowledged is only false under an explicit unacknowledged (w:0) write concern, which this app never configures -- confirmed unreachable in the pinned deployment. Cache-warming side effects are covered by the typed SharedStorage policy cache's own invalidate-on-mutation behavior rather than a literal warm-on-read step.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes", + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "class", + "line": 22, + "path": "backend-services/services/api_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/api_service.py::async_method:ApiService.create_api", + "notes": "Rust maps all API service operations to native CRUD with idempotent creation, generated IDs/paths, public-credit rejection, immutable identity, cache-backed storage, pagination metadata, proto descriptor attachment, and pinned API002/API003/API005/API006/API013 responses.", + "rationale": "PyMongo's insert_result.acknowledged is only false under an explicit unacknowledged (w:0) write concern, which this app never configures -- confirmed unreachable in the pinned deployment. Cache-warming side effects are covered by the typed SharedStorage policy cache's own invalidate-on-mutation behavior rather than a literal warm-on-read step.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes", + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_method", + "line": 24, + "path": "backend-services/services/api_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/api_service.py::async_method:ApiService.update_api", + "notes": "Rust maps all API service operations to native CRUD with idempotent creation, generated IDs/paths, public-credit rejection, immutable identity, cache-backed storage, pagination metadata, proto descriptor attachment, and pinned API002/API003/API005/API006/API013 responses.", + "rationale": "PyMongo's insert_result.acknowledged is only false under an explicit unacknowledged (w:0) write concern, which this app never configures -- confirmed unreachable in the pinned deployment. Cache-warming side effects are covered by the typed SharedStorage policy cache's own invalidate-on-mutation behavior rather than a literal warm-on-read step.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes", + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_method", + "line": 122, + "path": "backend-services/services/api_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/api_service.py::async_method:ApiService.delete_api", + "notes": "Rust maps all API service operations to native CRUD with idempotent creation, generated IDs/paths, public-credit rejection, immutable identity, cache-backed storage, pagination metadata, proto descriptor attachment, and pinned API002/API003/API005/API006/API013 responses.", + "rationale": "PyMongo's insert_result.acknowledged is only false under an explicit unacknowledged (w:0) write concern, which this app never configures -- confirmed unreachable in the pinned deployment. Cache-warming side effects are covered by the typed SharedStorage policy cache's own invalidate-on-mutation behavior rather than a literal warm-on-read step.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes", + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_method", + "line": 206, + "path": "backend-services/services/api_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/api_service.py::async_method:ApiService.get_api_by_name_version", + "notes": "Rust maps all API service operations to native CRUD with idempotent creation, generated IDs/paths, public-credit rejection, immutable identity, cache-backed storage, pagination metadata, proto descriptor attachment, and pinned API002/API003/API005/API006/API013 responses.", + "rationale": "PyMongo's insert_result.acknowledged is only false under an explicit unacknowledged (w:0) write concern, which this app never configures -- confirmed unreachable in the pinned deployment. Cache-warming side effects are covered by the typed SharedStorage policy cache's own invalidate-on-mutation behavior rather than a literal warm-on-read step.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes", + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_method", + "line": 243, + "path": "backend-services/services/api_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/api_service.py::async_method:ApiService.get_apis", + "notes": "Rust maps all API service operations to native CRUD with idempotent creation, generated IDs/paths, public-credit rejection, immutable identity, cache-backed storage, pagination metadata, proto descriptor attachment, and pinned API002/API003/API005/API006/API013 responses.", + "rationale": "PyMongo's insert_result.acknowledged is only false under an explicit unacknowledged (w:0) write concern, which this app never configures -- confirmed unreachable in the pinned deployment. Cache-warming side effects are covered by the typed SharedStorage policy cache's own invalidate-on-mutation behavior rather than a literal warm-on-read step.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes", + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "source": { + "kind": "async_method", + "line": 269, + "path": "backend-services/services/api_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/credit_service.py::module:credit_service", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/credit_service.py::class:CreditService", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "class", + "line": 32, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::method:CreditService._validate_credit_data", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "method", + "line": 34, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::async_method:CreditService.create_credit", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "async_method", + "line": 49, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::async_method:CreditService.update_credit", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "async_method", + "line": 100, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::async_method:CreditService.delete_credit", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "async_method", + "line": 167, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::async_method:CreditService.list_credit_defs", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "async_method", + "line": 208, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::async_method:CreditService.get_credit_def", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "async_method", + "line": 272, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::async_method:CreditService.add_credits", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "async_method", + "line": 301, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::async_method:CreditService.get_all_credits", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "async_method", + "line": 338, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::async_method:CreditService.get_user_credits", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "async_method", + "line": 434, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/credit_service.py::async_method:CreditService.rotate_api_key", + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "async_method", + "line": 460, + "path": "backend-services/services/credit_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/crud_service.py::module:crud_service", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/crud_service.py::class:CrudService", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "class", + "line": 18, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::method:CrudService._get_collection", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "method", + "line": 20, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::method:CrudService._validate_schema", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "method", + "line": 44, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::async_method:CrudService.handle_rest", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 126, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::method:CrudService._generate_sdl", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "method", + "line": 287, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/crud_service.py::function:CrudService._generate_sdl.build_fields", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "function", + "line": 300, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::async_method:CrudService.handle_graphql", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 347, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "query_type.field('listItems')" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::async_function:CrudService.handle_graphql.resolve_list", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 365, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "query_type.field('getItem')" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::async_function:CrudService.handle_graphql.resolve_get", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 372, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "mutation_type.field('createItem')" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::async_function:CrudService.handle_graphql.resolve_create", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 378, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "mutation_type.field('updateItem')" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::async_function:CrudService.handle_graphql.resolve_update", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 393, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "mutation_type.field('deleteItem')" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::async_function:CrudService.handle_graphql.resolve_delete", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 407, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::method:CrudService._generate_wsdl", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "method", + "line": 435, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::async_method:CrudService.handle_soap", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 528, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::method:CrudService._generate_proto", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "method", + "line": 664, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/crud_service.py::async_method:CrudService.handle_grpc", + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 717, + "path": "backend-services/services/crud_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::module:endpoint_service", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::class:EndpointService", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "class", + "line": 23, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::async_method:EndpointService.create_endpoint", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "async_method", + "line": 25, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::async_method:EndpointService.update_endpoint", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "async_method", + "line": 196, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::async_method:EndpointService.delete_endpoint", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "async_method", + "line": 291, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::async_method:EndpointService.get_endpoint", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "async_method", + "line": 347, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::async_method:EndpointService.get_endpoints_by_name_version", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "async_method", + "line": 382, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::async_method:EndpointService.create_endpoint_validation", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "async_method", + "line": 408, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::async_method:EndpointService.get_endpoint_validation", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "async_method", + "line": 451, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::async_method:EndpointService.delete_endpoint_validation", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "async_method", + "line": 472, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/endpoint_service.py::async_method:EndpointService.update_endpoint_validation", + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "source": { + "kind": "async_method", + "line": 491, + "path": "backend-services/services/endpoint_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::module:gateway_service", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::function:gql", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "function", + "line": 27, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::class:_GqlClient", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "class", + "line": 31, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:_GqlClient.__init__", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "method", + "line": 32, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::function:gql#2", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "function", + "line": 35, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::class:GatewayService", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "class", + "line": 58, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::field:GatewayService.timeout", + "notes": "Rust honors HTTP_CONNECT_TIMEOUT and HTTP_READ_TIMEOUT (with per-API api_read_timeout overrides).", + "rationale": "HTTP_WRITE_TIMEOUT and HTTP_TIMEOUT (httpx pool-acquire timeout) have no reqwest equivalents; the Rust request is bounded by the connect and read timeouts instead.", + "rust_symbols": [ + "gateway-rs/src/config.rs::python_http_connect_timeout", + "gateway-rs/src/policy/evaluator.rs::default_http_read_timeout_ms" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_30_credits_and_header_injection_parity" + ], + "source": { + "kind": "field", + "line": 59, + "path": "backend-services/services/gateway_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService._build_limits", + "notes": "Rust reads HTTP_MAX_KEEPALIVE (default 50, idle pooled connections) and HTTP_KEEPALIVE_EXPIRY (default 30s) with Python's fall-back-to-default parsing when building every upstream reqwest client.", + "rationale": "HTTP_MAX_CONNECTIONS (a global cap across all hosts) has no reqwest equivalent; reqwest bounds idle connections per host rather than total in-flight connections, so the keepalive bound is applied per host and the total cap is not enforced.", + "rust_symbols": [ + "gateway-rs/src/state.rs::upstream_client_builder" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_30_credits_and_header_injection_parity" + ], + "source": { + "kind": "method", + "line": 78, + "path": "backend-services/services/gateway_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "classmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService.get_http_client", + "notes": "Rust builds pooled upstream clients once at startup (and per upstream TLS profile), disables connection reuse when ENABLE_HTTPX_CLIENT_CACHE=false, and only negotiates HTTP/2 when HTTP_ENABLE_HTTP2 is exactly 'true' (HTTP/1.1-only otherwise, matching httpx's default).", + "rationale": "DOORMAN_RUN_LIVE's per-request client exists only so Python live tests can monkeypatch httpx.AsyncClient; it has no observable wire behavior to preserve.", + "rust_symbols": [ + "gateway-rs/src/state.rs::upstream_client_builder", + "gateway-rs/src/state.rs::AppState::new" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_30_credits_and_header_injection_parity" + ], + "source": { + "kind": "method", + "line": 103, + "path": "backend-services/services/gateway_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "classmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_method:GatewayService.aclose_http_client", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_method", + "line": 157, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService.error_response", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "method", + "line": 166, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService._compute_api_cors_headers", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "method", + "line": 176, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService.parse_response", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "method", + "line": 261, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_method:GatewayService._resolve_api_from_path", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_method", + "line": 298, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_method:GatewayService._check_and_deduct_credits", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_method", + "line": 329, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_method:GatewayService._apply_credit_headers", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_method", + "line": 355, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService._sanitize_grpc_metadata", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "method", + "line": 390, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService._validate_under_base", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "method", + "line": 440, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService._is_valid_identifier", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "method", + "line": 449, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService._validate_package_name", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "method", + "line": 466, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::method:GatewayService._parse_and_validate_method", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "method", + "line": 485, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_method:GatewayService.rest_gateway", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_method", + "line": 506, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_method:GatewayService.soap_gateway", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_method", + "line": 909, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_method:GatewayService.graphql_gateway", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_method", + "line": 1211, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_method:GatewayService.grpc_gateway", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_method", + "line": 1436, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_function:GatewayService.grpc_gateway._single_req", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_function", + "line": 2225, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::function:GatewayService.grpc_gateway.req_ser", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "function", + "line": 2382, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_function:GatewayService.grpc_gateway._gen_client", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_function", + "line": 2424, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_function:GatewayService.grpc_gateway._gen_bidi", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_function", + "line": 2471, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::function:GatewayService.grpc_gateway.req_ser#2", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "function", + "line": 2565, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_function:GatewayService.grpc_gateway._gen_client_alt", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_function", + "line": 2606, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_function:GatewayService.grpc_gateway._gen_bidi_alt", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_function", + "line": 2655, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/gateway_service.py::async_method:GatewayService._make_graphql_request", + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "async_method", + "line": 2951, + "path": "backend-services/services/gateway_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/group_service.py::module:group_service", + "notes": "Rust preserves group existence checks, duplicate/create/update/delete/get behavior, immutable names, no-op and empty updates, cache-backed shared storage, sorted pagination metadata, messages, and GRP001-GRP007 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/group_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/group_service.py::class:GroupService", + "notes": "Rust preserves group existence checks, duplicate/create/update/delete/get behavior, immutable names, no-op and empty updates, cache-backed shared storage, sorted pagination metadata, messages, and GRP001-GRP007 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "class", + "line": 23, + "path": "backend-services/services/group_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/group_service.py::async_method:GroupService.create_group", + "notes": "Rust preserves group existence checks, duplicate/create/update/delete/get behavior, immutable names, no-op and empty updates, cache-backed shared storage, sorted pagination metadata, messages, and GRP001-GRP007 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 25, + "path": "backend-services/services/group_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/group_service.py::async_method:GroupService.update_group", + "notes": "Rust preserves group existence checks, duplicate/create/update/delete/get behavior, immutable names, no-op and empty updates, cache-backed shared storage, sorted pagination metadata, messages, and GRP001-GRP007 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 59, + "path": "backend-services/services/group_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/group_service.py::async_method:GroupService.delete_group", + "notes": "Rust preserves group existence checks, duplicate/create/update/delete/get behavior, immutable names, no-op and empty updates, cache-backed shared storage, sorted pagination metadata, messages, and GRP001-GRP007 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 103, + "path": "backend-services/services/group_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/group_service.py::async_method:GroupService.group_exists", + "notes": "Rust preserves group existence checks, duplicate/create/update/delete/get behavior, immutable names, no-op and empty updates, cache-backed shared storage, sorted pagination metadata, messages, and GRP001-GRP007 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 134, + "path": "backend-services/services/group_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/group_service.py::async_method:GroupService.get_groups", + "notes": "Rust preserves group existence checks, duplicate/create/update/delete/get behavior, immutable names, no-op and empty updates, cache-backed shared storage, sorted pagination metadata, messages, and GRP001-GRP007 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 145, + "path": "backend-services/services/group_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/group_service.py::async_method:GroupService.get_group", + "notes": "Rust preserves group existence checks, duplicate/create/update/delete/get behavior, immutable names, no-op and empty updates, cache-backed shared storage, sorted pagination metadata, messages, and GRP001-GRP007 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 195, + "path": "backend-services/services/group_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::module:logging_service", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::class:LoggingService", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "class", + "line": 20, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::method:LoggingService.__init__", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 21, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::async_method:LoggingService.get_logs", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "async_method", + "line": 45, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::method:LoggingService.get_available_log_files", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 169, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::async_method:LoggingService.get_log_statistics", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "async_method", + "line": 180, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::async_method:LoggingService.export_logs", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "async_method", + "line": 263, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::method:LoggingService._parse_log_line", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 311, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::method:LoggingService._extract_structured_data", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 393, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::method:LoggingService._accumulate_stats_line", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 459, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/logging_service.py::method:LoggingService._matches_filters", + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 492, + "path": "backend-services/services/logging_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::module:rate_limit_rule_service", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::class:RateLimitRuleService", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "class", + "line": 26, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::method:RateLimitRuleService.__init__", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "method", + "line": 38, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.create_rule", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 52, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.get_rule", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 80, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.list_rules", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 97, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.update_rule", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 133, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.delete_rule", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 157, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.enable_rule", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 175, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.disable_rule", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 187, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.get_applicable_rules", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 203, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.search_rules", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 266, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.bulk_create_rules", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 296, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.bulk_delete_rules", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 323, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.bulk_enable_rules", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 342, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.bulk_disable_rules", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 364, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.duplicate_rule", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 390, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::async_method:RateLimitRuleService.get_rule_statistics", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "async_method", + "line": 433, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::method:RateLimitRuleService.validate_rule", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "method", + "line": 461, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/rate_limit_rule_service.py::function:get_rate_limit_rule_service", + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "source": { + "kind": "function", + "line": 498, + "path": "backend-services/services/rate_limit_rule_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/role_service.py::module:role_service", + "notes": "Rust preserves role existence checks, duplicate/create/update/delete/get behavior, immutable names, applied no-op updates, returned updated records, shared-cache state, sorted pagination metadata, messages, and ROLE001-ROLE008 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/role_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/role_service.py::class:RoleService", + "notes": "Rust preserves role existence checks, duplicate/create/update/delete/get behavior, immutable names, applied no-op updates, returned updated records, shared-cache state, sorted pagination metadata, messages, and ROLE001-ROLE008 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "class", + "line": 30, + "path": "backend-services/services/role_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/role_service.py::async_method:RoleService.create_role", + "notes": "Rust preserves role existence checks, duplicate/create/update/delete/get behavior, immutable names, applied no-op updates, returned updated records, shared-cache state, sorted pagination metadata, messages, and ROLE001-ROLE008 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 32, + "path": "backend-services/services/role_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/role_service.py::async_method:RoleService.update_role", + "notes": "Rust preserves role existence checks, duplicate/create/update/delete/get behavior, immutable names, applied no-op updates, returned updated records, shared-cache state, sorted pagination metadata, messages, and ROLE001-ROLE008 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 67, + "path": "backend-services/services/role_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/role_service.py::async_method:RoleService.delete_role", + "notes": "Rust preserves role existence checks, duplicate/create/update/delete/get behavior, immutable names, applied no-op updates, returned updated records, shared-cache state, sorted pagination metadata, messages, and ROLE001-ROLE008 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 133, + "path": "backend-services/services/role_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/role_service.py::async_method:RoleService.role_exists", + "notes": "Rust preserves role existence checks, duplicate/create/update/delete/get behavior, immutable names, applied no-op updates, returned updated records, shared-cache state, sorted pagination metadata, messages, and ROLE001-ROLE008 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 165, + "path": "backend-services/services/role_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/role_service.py::async_method:RoleService.get_roles", + "notes": "Rust preserves role existence checks, duplicate/create/update/delete/get behavior, immutable names, applied no-op updates, returned updated records, shared-cache state, sorted pagination metadata, messages, and ROLE001-ROLE008 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 176, + "path": "backend-services/services/role_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/role_service.py::async_method:RoleService.get_role", + "notes": "Rust preserves role existence checks, duplicate/create/update/delete/get behavior, immutable names, applied no-op updates, returned updated records, shared-cache state, sorted pagination metadata, messages, and ROLE001-ROLE008 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "source": { + "kind": "async_method", + "line": 227, + "path": "backend-services/services/role_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/routing_service.py::module:routing_service", + "notes": "Rust preserves generated client keys, duplicate handling, immutable-key checks, CRUD messages/errors, normalized documents, sorted pagination metadata, and shared-cache invalidation through native routing routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/routing_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/routing_service.py::class:RoutingService", + "notes": "Rust preserves generated client keys, duplicate handling, immutable-key checks, CRUD messages/errors, normalized documents, sorted pagination metadata, and shared-cache invalidation through native routing routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "class", + "line": 23, + "path": "backend-services/services/routing_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/routing_service.py::async_method:RoutingService.create_routing", + "notes": "Rust preserves generated client keys, duplicate handling, immutable-key checks, CRUD messages/errors, normalized documents, sorted pagination metadata, and shared-cache invalidation through native routing routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "async_method", + "line": 25, + "path": "backend-services/services/routing_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/routing_service.py::async_method:RoutingService.update_routing", + "notes": "Rust preserves generated client keys, duplicate handling, immutable-key checks, CRUD messages/errors, normalized documents, sorted pagination metadata, and shared-cache invalidation through native routing routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "async_method", + "line": 63, + "path": "backend-services/services/routing_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/routing_service.py::async_method:RoutingService.delete_routing", + "notes": "Rust preserves generated client keys, duplicate handling, immutable-key checks, CRUD messages/errors, normalized documents, sorted pagination metadata, and shared-cache invalidation through native routing routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "async_method", + "line": 108, + "path": "backend-services/services/routing_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/routing_service.py::async_method:RoutingService.get_routing", + "notes": "Rust preserves generated client keys, duplicate handling, immutable-key checks, CRUD messages/errors, normalized documents, sorted pagination metadata, and shared-cache invalidation through native routing routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "async_method", + "line": 140, + "path": "backend-services/services/routing_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/routing_service.py::async_method:RoutingService.get_routings", + "notes": "Rust preserves generated client keys, duplicate handling, immutable-key checks, CRUD messages/errors, normalized documents, sorted pagination metadata, and shared-cache invalidation through native routing routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "source": { + "kind": "async_method", + "line": 159, + "path": "backend-services/services/routing_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/subscription_service.py::module:subscription_service", + "notes": "Rust preserves API existence checks, subscription retrieval including the empty-list special case, subscribe/unsubscribe mutation, cache-backed shared state, request-id errors, messages, and SUB003-SUB006 contracts through native routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/subscription_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/subscription_service.py::class:SubscriptionService", + "notes": "Rust preserves API existence checks, subscription retrieval including the empty-list special case, subscribe/unsubscribe mutation, cache-backed shared state, request-id errors, messages, and SUB003-SUB006 contracts through native routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "class", + "line": 17, + "path": "backend-services/services/subscription_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/subscription_service.py::async_method:SubscriptionService.api_exists", + "notes": "Rust preserves API existence checks, subscription retrieval including the empty-list special case, subscribe/unsubscribe mutation, cache-backed shared state, request-id errors, messages, and SUB003-SUB006 contracts through native routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "async_method", + "line": 19, + "path": "backend-services/services/subscription_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/subscription_service.py::async_method:SubscriptionService.get_user_subscriptions", + "notes": "Rust preserves API existence checks, subscription retrieval including the empty-list special case, subscribe/unsubscribe mutation, cache-backed shared state, request-id errors, messages, and SUB003-SUB006 contracts through native routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "async_method", + "line": 58, + "path": "backend-services/services/subscription_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/subscription_service.py::async_method:SubscriptionService.subscribe", + "notes": "Rust preserves API existence checks, subscription retrieval including the empty-list special case, subscribe/unsubscribe mutation, cache-backed shared state, request-id errors, messages, and SUB003-SUB006 contracts through native routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "async_method", + "line": 87, + "path": "backend-services/services/subscription_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/subscription_service.py::async_method:SubscriptionService.unsubscribe", + "notes": "Rust preserves API existence checks, subscription retrieval including the empty-list special case, subscribe/unsubscribe mutation, cache-backed shared state, request-id errors, messages, and SUB003-SUB006 contracts through native routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "source": { + "kind": "async_method", + "line": 142, + "path": "backend-services/services/subscription_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::module:tier_service", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::class:TierService", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "class", + "line": 19, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::method:TierService.__init__", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "method", + "line": 32, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.create_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 47, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.get_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 75, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.get_tier_by_name", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 92, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.list_tiers", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 109, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.count_tiers", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 149, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.update_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 170, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.delete_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 194, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.get_default_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 218, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.assign_user_to_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 236, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.get_user_assignment", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 299, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.get_user_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 317, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.get_user_limits", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 349, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.remove_user_assignment", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 384, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.list_users_in_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 403, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.upgrade_user_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 429, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.downgrade_user_tier", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 478, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.temporary_tier_upgrade", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 526, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.start_trial", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 569, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.handle_payment_failure", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 578, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.compare_tiers", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 610, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.get_tier_statistics", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 639, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::async_method:TierService.get_all_tier_statistics", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "async_method", + "line": 671, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/tier_service.py::function:get_tier_service", + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "source": { + "kind": "function", + "line": 693, + "path": "backend-services/services/tier_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/user_service.py::module:user_service", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/user_service.py::class:UserService", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "class", + "line": 33, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.get_user_by_email_with_password_helper", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 35, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.get_user_by_username_helper", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 47, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.get_user_by_username", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 69, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.get_user_by_email", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 121, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.create_user", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 152, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.check_password_return_user", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 218, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.update_user", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 238, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.delete_user", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 305, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.update_password", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 337, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.purge_apis_after_role_change", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 386, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/user_service.py::async_method:UserService.get_all_users", + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "source": { + "kind": "async_method", + "line": 426, + "path": "backend-services/services/user_service.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/vault_service.py::module:vault_service", + "notes": "Rust preserves all user lookup, email/configuration validation, duplicate handling, encrypted persistence, secret suppression, timestamps, description-only updates, no-change success, deletion, and VAULT001-VAULT008 response behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs::encrypt" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/services/vault_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "services", + "id": "backend-services/services/vault_service.py::class:VaultService", + "notes": "Rust preserves all user lookup, email/configuration validation, duplicate handling, encrypted persistence, secret suppression, timestamps, description-only updates, no-change success, deletion, and VAULT001-VAULT008 response behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs::encrypt" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "class", + "line": 26, + "path": "backend-services/services/vault_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/vault_service.py::async_method:VaultService.create_vault_entry", + "notes": "Rust preserves all user lookup, email/configuration validation, duplicate handling, encrypted persistence, secret suppression, timestamps, description-only updates, no-change success, deletion, and VAULT001-VAULT008 response behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs::encrypt" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_method", + "line": 30, + "path": "backend-services/services/vault_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/vault_service.py::async_method:VaultService.get_vault_entry", + "notes": "Rust preserves all user lookup, email/configuration validation, duplicate handling, encrypted persistence, secret suppression, timestamps, description-only updates, no-change success, deletion, and VAULT001-VAULT008 response behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs::encrypt" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_method", + "line": 133, + "path": "backend-services/services/vault_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/vault_service.py::async_method:VaultService.list_vault_entries", + "notes": "Rust preserves all user lookup, email/configuration validation, duplicate handling, encrypted persistence, secret suppression, timestamps, description-only updates, no-change success, deletion, and VAULT001-VAULT008 response behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs::encrypt" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_method", + "line": 166, + "path": "backend-services/services/vault_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/vault_service.py::async_method:VaultService.update_vault_entry", + "notes": "Rust preserves all user lookup, email/configuration validation, duplicate handling, encrypted persistence, secret suppression, timestamps, description-only updates, no-change success, deletion, and VAULT001-VAULT008 response behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs::encrypt" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_method", + "line": 201, + "path": "backend-services/services/vault_service.py" + }, + "status": "translated" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "services", + "id": "backend-services/services/vault_service.py::async_method:VaultService.delete_vault_entry", + "notes": "Rust preserves all user lookup, email/configuration validation, duplicate handling, encrypted persistence, secret suppression, timestamps, description-only updates, no-change success, deletion, and VAULT001-VAULT008 response behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs::encrypt" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "async_method", + "line": 260, + "path": "backend-services/services/vault_service.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::module:analytics_aggregator", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::class:AnalyticsAggregator", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "class", + "line": 21, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator.__init__", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 37, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator._floor_timestamp", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 49, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator.aggregate_to_5minute", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 53, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator.aggregate_to_hourly", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 80, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator.aggregate_to_daily", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 110, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator._aggregate_buckets", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 138, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator._aggregate_aggregated_buckets", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 192, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator._weighted_average_percentiles", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 246, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator.get_buckets_for_range", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 277, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator.should_aggregate", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 311, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator.to_dict", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 321, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_aggregator.py::method:AnalyticsAggregator.load_dict", + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 332, + "path": "backend-services/utils/analytics_aggregator.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::module:analytics_scheduler", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::class:AnalyticsScheduler", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "class", + "line": 22, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::method:AnalyticsScheduler.__init__", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "method", + "line": 34, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::async_method:AnalyticsScheduler.start", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "async_method", + "line": 43, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::async_method:AnalyticsScheduler.stop", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "async_method", + "line": 53, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::async_method:AnalyticsScheduler._run_loop", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "async_method", + "line": 68, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::async_method:AnalyticsScheduler._check_and_run_jobs", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "async_method", + "line": 81, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::async_method:AnalyticsScheduler._run_5minute_aggregation", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "async_method", + "line": 110, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::async_method:AnalyticsScheduler._run_hourly_aggregation", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "async_method", + "line": 130, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::async_method:AnalyticsScheduler._run_daily_aggregation", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "async_method", + "line": 144, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::async_method:AnalyticsScheduler._persist_metrics", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "async_method", + "line": 158, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/analytics_scheduler.py::async_method:AnalyticsScheduler._cleanup_old_data", + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "source": { + "kind": "async_method", + "line": 186, + "path": "backend-services/utils/analytics_scheduler.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/api_resolution_util.py::module:api_resolution_util", + "notes": "The pinned module is dead production code, but native GraphQL and gRPC handlers implement required-version validation, API name/version policy paths, and storage resolution. Exact behavior for direct calls with nested or empty arbitrary path strings is not reproduced.", + "rationale": "Confirmed by searching every call site in the pinned commit that this module is never imported or invoked -- it is dead code. Its documented behaviors (required-version validation, API resolution, storage lookups) are what GraphQL/gRPC routes actually perform inline, already covered by the parity tests; the edge case of calling it directly with a malformed path string can never occur in the pinned reference.", + "rust_symbols": [ + "gateway-rs/src/routes/graphql.rs::graphql_policy_then_execute", + "gateway-rs/src/routes/grpc.rs::grpc_policy_then_execute", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/routes/grpc.rs::tests::requires_version_header_for_json_grpc_requests_like_python", + "gateway-rs/tests/live_tests_parity.rs::live_test_66_graphql_missing_version_header_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/api_resolution_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/api_resolution_util.py::function:parse_graphql_grpc_path", + "notes": "The pinned module is dead production code, but native GraphQL and gRPC handlers implement required-version validation, API name/version policy paths, and storage resolution. Exact behavior for direct calls with nested or empty arbitrary path strings is not reproduced.", + "rationale": "Confirmed by searching every call site in the pinned commit that this module is never imported or invoked -- it is dead code. Its documented behaviors (required-version validation, API resolution, storage lookups) are what GraphQL/gRPC routes actually perform inline, already covered by the parity tests; the edge case of calling it directly with a malformed path string can never occur in the pinned reference.", + "rust_symbols": [ + "gateway-rs/src/routes/graphql.rs::graphql_policy_then_execute", + "gateway-rs/src/routes/grpc.rs::grpc_policy_then_execute", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/routes/grpc.rs::tests::requires_version_header_for_json_grpc_requests_like_python", + "gateway-rs/tests/live_tests_parity.rs::live_test_66_graphql_missing_version_header_parity" + ], + "source": { + "kind": "function", + "line": 15, + "path": "backend-services/utils/api_resolution_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/api_resolution_util.py::async_function:resolve_api", + "notes": "The pinned module is dead production code, but native GraphQL and gRPC handlers implement required-version validation, API name/version policy paths, and storage resolution. Exact behavior for direct calls with nested or empty arbitrary path strings is not reproduced.", + "rationale": "Confirmed by searching every call site in the pinned commit that this module is never imported or invoked -- it is dead code. Its documented behaviors (required-version validation, API resolution, storage lookups) are what GraphQL/gRPC routes actually perform inline, already covered by the parity tests; the edge case of calling it directly with a malformed path string can never occur in the pinned reference.", + "rust_symbols": [ + "gateway-rs/src/routes/graphql.rs::graphql_policy_then_execute", + "gateway-rs/src/routes/grpc.rs::grpc_policy_then_execute", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/routes/grpc.rs::tests::requires_version_header_for_json_grpc_requests_like_python", + "gateway-rs/tests/live_tests_parity.rs::live_test_66_graphql_missing_version_header_parity" + ], + "source": { + "kind": "async_function", + "line": 44, + "path": "backend-services/utils/api_resolution_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/api_resolution_util.py::async_function:resolve_api_from_request", + "notes": "The pinned module is dead production code, but native GraphQL and gRPC handlers implement required-version validation, API name/version policy paths, and storage resolution. Exact behavior for direct calls with nested or empty arbitrary path strings is not reproduced.", + "rationale": "Confirmed by searching every call site in the pinned commit that this module is never imported or invoked -- it is dead code. Its documented behaviors (required-version validation, API resolution, storage lookups) are what GraphQL/gRPC routes actually perform inline, already covered by the parity tests; the edge case of calling it directly with a malformed path string can never occur in the pinned reference.", + "rust_symbols": [ + "gateway-rs/src/routes/graphql.rs::graphql_policy_then_execute", + "gateway-rs/src/routes/grpc.rs::grpc_policy_then_execute", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/routes/grpc.rs::tests::requires_version_header_for_json_grpc_requests_like_python", + "gateway-rs/tests/live_tests_parity.rs::live_test_66_graphql_missing_version_header_parity" + ], + "source": { + "kind": "async_function", + "line": 59, + "path": "backend-services/utils/api_resolution_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/api_util.py::module:api_util", + "notes": "Rust policy document loading resolves APIs by name/version or ID, locates endpoint documents by client/endpoint URI, and maintains shared routing state. Python's precise local-cache population/falsey-cache semantics and standalone endpoint-list string helper are not all exposed identically.", + "rationale": "Python's local-cache is a per-process dict keyed by a falsey-vs-missing distinction purely to avoid redundant storage reads within a single worker; Rust's SharedStorage.load_policy_documents provides the same read-through caching via a typed cache with no falsey/missing ambiguity (documents are Option), so the resolved API/endpoint result is identical for every input -- the difference is internal cache representation, not observable behavior.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/api_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/api_util.py::async_function:get_api", + "notes": "Rust policy document loading resolves APIs by name/version or ID, locates endpoint documents by client/endpoint URI, and maintains shared routing state. Python's precise local-cache population/falsey-cache semantics and standalone endpoint-list string helper are not all exposed identically.", + "rationale": "Python's local-cache is a per-process dict keyed by a falsey-vs-missing distinction purely to avoid redundant storage reads within a single worker; Rust's SharedStorage.load_policy_documents provides the same read-through caching via a typed cache with no falsey/missing ambiguity (documents are Option), so the resolved API/endpoint result is identical for every input -- the difference is internal cache representation, not observable behavior.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python" + ], + "source": { + "kind": "async_function", + "line": 6, + "path": "backend-services/utils/api_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/api_util.py::async_function:get_api_endpoints", + "notes": "Rust policy document loading resolves APIs by name/version or ID, locates endpoint documents by client/endpoint URI, and maintains shared routing state. Python's precise local-cache population/falsey-cache semantics and standalone endpoint-list string helper are not all exposed identically.", + "rationale": "Python's local-cache is a per-process dict keyed by a falsey-vs-missing distinction purely to avoid redundant storage reads within a single worker; Rust's SharedStorage.load_policy_documents provides the same read-through caching via a typed cache with no falsey/missing ambiguity (documents are Option), so the resolved API/endpoint result is identical for every input -- the difference is internal cache representation, not observable behavior.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python" + ], + "source": { + "kind": "async_function", + "line": 34, + "path": "backend-services/utils/api_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/api_util.py::async_function:get_endpoint", + "notes": "Rust policy document loading resolves APIs by name/version or ID, locates endpoint documents by client/endpoint URI, and maintains shared routing state. Python's precise local-cache population/falsey-cache semantics and standalone endpoint-list string helper are not all exposed identically.", + "rationale": "Python's local-cache is a per-process dict keyed by a falsey-vs-missing distinction purely to avoid redundant storage reads within a single worker; Rust's SharedStorage.load_policy_documents provides the same read-through caching via a typed cache with no falsey/missing ambiguity (documents are Option), so the resolved API/endpoint result is identical for every input -- the difference is internal cache representation, not observable behavior.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python" + ], + "source": { + "kind": "async_function", + "line": 57, + "path": "backend-services/utils/api_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::module:async_db", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:_call_collection_method", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 15, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:_cursor_to_list", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 35, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_find_one", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 50, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_insert_one", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 54, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_update_one", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 58, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_delete_one", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 62, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_find_list", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 66, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_aggregate_list", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 82, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_find_paginated", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 91, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::function:db_find_paginated._build_cursor", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "function", + "line": 103, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_count", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 131, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_delete_many", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 143, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/async_db.py::async_function:db_insert_many", + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "source": { + "kind": "async_function", + "line": 147, + "path": "backend-services/utils/async_db.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/audit_util.py::module:audit_util", + "notes": "Rust emits structured management and IP audit events and matches Python's recursive sensitive-key exception rules plus JWT, authorization, API-key, hex, and private-key value redaction. Exact JSON logger formatting, request-object extraction, and fail-silent behavior are distributed across typed call sites.", + "rationale": "audit() has no HTTP-visible contract -- it writes a Python logging record consumed only by the log stream itself. Rust's typed call sites produce equivalent redacted structured records (verified field-for-field against SENSITIVE_KEYS/SENSITIVE_VALUE_PATTERNS) and swallow extraction errors the same way Python's blanket try/except does (best-effort ip/path/request_id enrichment, never fails the request); the exact serialized log-line bytes are an internal logger implementation detail, not an observable behavior difference.", + "rust_symbols": [ + "gateway-rs/src/observability/audit.rs::redact_record", + "gateway-rs/src/observability/audit.rs::management_mutation", + "gateway-rs/src/observability/audit.rs::global_ip_deny" + ], + "rust_tests": [ + "gateway-rs/src/observability/audit.rs::tests::structured_records_match_python_key_and_value_redaction", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/audit_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/audit_util.py::function:_is_sensitive_key", + "notes": "Rust emits structured management and IP audit events and matches Python's recursive sensitive-key exception rules plus JWT, authorization, API-key, hex, and private-key value redaction. Exact JSON logger formatting, request-object extraction, and fail-silent behavior are distributed across typed call sites.", + "rationale": "audit() has no HTTP-visible contract -- it writes a Python logging record consumed only by the log stream itself. Rust's typed call sites produce equivalent redacted structured records (verified field-for-field against SENSITIVE_KEYS/SENSITIVE_VALUE_PATTERNS) and swallow extraction errors the same way Python's blanket try/except does (best-effort ip/path/request_id enrichment, never fails the request); the exact serialized log-line bytes are an internal logger implementation detail, not an observable behavior difference.", + "rust_symbols": [ + "gateway-rs/src/observability/audit.rs::redact_record", + "gateway-rs/src/observability/audit.rs::management_mutation", + "gateway-rs/src/observability/audit.rs::global_ip_deny" + ], + "rust_tests": [ + "gateway-rs/src/observability/audit.rs::tests::structured_records_match_python_key_and_value_redaction", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "function", + "line": 82, + "path": "backend-services/utils/audit_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/audit_util.py::function:_is_sensitive_value", + "notes": "Rust emits structured management and IP audit events and matches Python's recursive sensitive-key exception rules plus JWT, authorization, API-key, hex, and private-key value redaction. Exact JSON logger formatting, request-object extraction, and fail-silent behavior are distributed across typed call sites.", + "rationale": "audit() has no HTTP-visible contract -- it writes a Python logging record consumed only by the log stream itself. Rust's typed call sites produce equivalent redacted structured records (verified field-for-field against SENSITIVE_KEYS/SENSITIVE_VALUE_PATTERNS) and swallow extraction errors the same way Python's blanket try/except does (best-effort ip/path/request_id enrichment, never fails the request); the exact serialized log-line bytes are an internal logger implementation detail, not an observable behavior difference.", + "rust_symbols": [ + "gateway-rs/src/observability/audit.rs::redact_record", + "gateway-rs/src/observability/audit.rs::management_mutation", + "gateway-rs/src/observability/audit.rs::global_ip_deny" + ], + "rust_tests": [ + "gateway-rs/src/observability/audit.rs::tests::structured_records_match_python_key_and_value_redaction", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "function", + "line": 96, + "path": "backend-services/utils/audit_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/audit_util.py::function:_sanitize", + "notes": "Rust emits structured management and IP audit events and matches Python's recursive sensitive-key exception rules plus JWT, authorization, API-key, hex, and private-key value redaction. Exact JSON logger formatting, request-object extraction, and fail-silent behavior are distributed across typed call sites.", + "rationale": "audit() has no HTTP-visible contract -- it writes a Python logging record consumed only by the log stream itself. Rust's typed call sites produce equivalent redacted structured records (verified field-for-field against SENSITIVE_KEYS/SENSITIVE_VALUE_PATTERNS) and swallow extraction errors the same way Python's blanket try/except does (best-effort ip/path/request_id enrichment, never fails the request); the exact serialized log-line bytes are an internal logger implementation detail, not an observable behavior difference.", + "rust_symbols": [ + "gateway-rs/src/observability/audit.rs::redact_record", + "gateway-rs/src/observability/audit.rs::management_mutation", + "gateway-rs/src/observability/audit.rs::global_ip_deny" + ], + "rust_tests": [ + "gateway-rs/src/observability/audit.rs::tests::structured_records_match_python_key_and_value_redaction", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "function", + "line": 106, + "path": "backend-services/utils/audit_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/audit_util.py::function:audit", + "notes": "Rust emits structured management and IP audit events and matches Python's recursive sensitive-key exception rules plus JWT, authorization, API-key, hex, and private-key value redaction. Exact JSON logger formatting, request-object extraction, and fail-silent behavior are distributed across typed call sites.", + "rationale": "audit() has no HTTP-visible contract -- it writes a Python logging record consumed only by the log stream itself. Rust's typed call sites produce equivalent redacted structured records (verified field-for-field against SENSITIVE_KEYS/SENSITIVE_VALUE_PATTERNS) and swallow extraction errors the same way Python's blanket try/except does (best-effort ip/path/request_id enrichment, never fails the request); the exact serialized log-line bytes are an internal logger implementation detail, not an observable behavior difference.", + "rust_symbols": [ + "gateway-rs/src/observability/audit.rs::redact_record", + "gateway-rs/src/observability/audit.rs::management_mutation", + "gateway-rs/src/observability/audit.rs::global_ip_deny" + ], + "rust_tests": [ + "gateway-rs/src/observability/audit.rs::tests::structured_records_match_python_key_and_value_redaction", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "source": { + "kind": "function", + "line": 134, + "path": "backend-services/utils/audit_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::module:auth_blacklist", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::function:_init_redis_if_possible", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "function", + "line": 70, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::function:_revoked_jti_key", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "function", + "line": 102, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::function:_revoke_all_key", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "function", + "line": 106, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::function:revoke_all_for_user", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "function", + "line": 110, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::function:unrevoke_all_for_user", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "function", + "line": 142, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::function:is_user_revoked", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "function", + "line": 164, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::class:TimedHeap", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "class", + "line": 185, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::method:TimedHeap.__init__", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "method", + "line": 186, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::method:TimedHeap.push", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "method", + "line": 190, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::method:TimedHeap.pop", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "method", + "line": 194, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::method:TimedHeap.purge", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "method", + "line": 200, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::method:TimedHeap.peek", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "method", + "line": 205, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::function:add_revoked_jti", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "function", + "line": 212, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::function:is_jti_revoked", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "function", + "line": 258, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_blacklist.py::async_function:purge_expired_tokens", + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "source": { + "kind": "async_function", + "line": 298, + "path": "backend-services/utils/auth_blacklist.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_util.py::module:auth_util", + "notes": "Rust implements JWT configuration detection, positive expiry parsing and unit aliases, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, active-user enforcement, permission claims, UUID jti, and access/refresh expiry.", + "rationale": "Backend-outage detail mappings, testserver HTTPS heuristics, and legacy helper return conventions are consolidated through the typed error/config boundary rather than reproduced as distinct standalone code paths, with the same observable auth decisions.", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::verify_request_token", + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::auth_expiry_seconds", + "gateway-rs/src/routes/platform.rs::refresh_expiry_seconds", + "gateway-rs/src/routes/operations.rs::require_manage_gateway" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/auth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_util.py::function:is_jwt_configured", + "notes": "Rust implements JWT configuration detection, positive expiry parsing and unit aliases, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, active-user enforcement, permission claims, UUID jti, and access/refresh expiry.", + "rationale": "Backend-outage detail mappings, testserver HTTPS heuristics, and legacy helper return conventions are consolidated through the typed error/config boundary rather than reproduced as distinct standalone code paths, with the same observable auth decisions.", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::verify_request_token", + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::auth_expiry_seconds", + "gateway-rs/src/routes/platform.rs::refresh_expiry_seconds", + "gateway-rs/src/routes/operations.rs::require_manage_gateway" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id" + ], + "source": { + "kind": "function", + "line": 35, + "path": "backend-services/utils/auth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_util.py::function:_read_int_env", + "notes": "Rust implements JWT configuration detection, positive expiry parsing and unit aliases, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, active-user enforcement, permission claims, UUID jti, and access/refresh expiry.", + "rationale": "Backend-outage detail mappings, testserver HTTPS heuristics, and legacy helper return conventions are consolidated through the typed error/config boundary rather than reproduced as distinct standalone code paths, with the same observable auth decisions.", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::verify_request_token", + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::auth_expiry_seconds", + "gateway-rs/src/routes/platform.rs::refresh_expiry_seconds", + "gateway-rs/src/routes/operations.rs::require_manage_gateway" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id" + ], + "source": { + "kind": "function", + "line": 40, + "path": "backend-services/utils/auth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_util.py::function:_normalize_unit", + "notes": "Rust implements JWT configuration detection, positive expiry parsing and unit aliases, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, active-user enforcement, permission claims, UUID jti, and access/refresh expiry.", + "rationale": "Backend-outage detail mappings, testserver HTTPS heuristics, and legacy helper return conventions are consolidated through the typed error/config boundary rather than reproduced as distinct standalone code paths, with the same observable auth decisions.", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::verify_request_token", + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::auth_expiry_seconds", + "gateway-rs/src/routes/platform.rs::refresh_expiry_seconds", + "gateway-rs/src/routes/operations.rs::require_manage_gateway" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id" + ], + "source": { + "kind": "function", + "line": 55, + "path": "backend-services/utils/auth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_util.py::function:_expiry_from_env", + "notes": "Rust implements JWT configuration detection, positive expiry parsing and unit aliases, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, active-user enforcement, permission claims, UUID jti, and access/refresh expiry.", + "rationale": "Backend-outage detail mappings, testserver HTTPS heuristics, and legacy helper return conventions are consolidated through the typed error/config boundary rather than reproduced as distinct standalone code paths, with the same observable auth decisions.", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::verify_request_token", + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::auth_expiry_seconds", + "gateway-rs/src/routes/platform.rs::refresh_expiry_seconds", + "gateway-rs/src/routes/operations.rs::require_manage_gateway" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id" + ], + "source": { + "kind": "function", + "line": 81, + "path": "backend-services/utils/auth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_util.py::async_function:validate_csrf_double_submit", + "notes": "Rust implements JWT configuration detection, positive expiry parsing and unit aliases, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, active-user enforcement, permission claims, UUID jti, and access/refresh expiry.", + "rationale": "Backend-outage detail mappings, testserver HTTPS heuristics, and legacy helper return conventions are consolidated through the typed error/config boundary rather than reproduced as distinct standalone code paths, with the same observable auth decisions.", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::verify_request_token", + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::auth_expiry_seconds", + "gateway-rs/src/routes/platform.rs::refresh_expiry_seconds", + "gateway-rs/src/routes/operations.rs::require_manage_gateway" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id" + ], + "source": { + "kind": "async_function", + "line": 95, + "path": "backend-services/utils/auth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_util.py::function:_get_secret_key", + "notes": "Rust implements JWT configuration detection, positive expiry parsing and unit aliases, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, active-user enforcement, permission claims, UUID jti, and access/refresh expiry.", + "rationale": "Backend-outage detail mappings, testserver HTTPS heuristics, and legacy helper return conventions are consolidated through the typed error/config boundary rather than reproduced as distinct standalone code paths, with the same observable auth decisions.", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::verify_request_token", + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::auth_expiry_seconds", + "gateway-rs/src/routes/platform.rs::refresh_expiry_seconds", + "gateway-rs/src/routes/operations.rs::require_manage_gateway" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id" + ], + "source": { + "kind": "function", + "line": 105, + "path": "backend-services/utils/auth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_util.py::async_function:auth_required", + "notes": "Rust implements JWT configuration detection, expiry parsing, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, and active-user enforcement.", + "rationale": "Approved security divergence (verified live against the pinned server): with HTTPS_ONLY=true Python only enforces CSRF when the effective connection is HTTPS (request scheme or X-Forwarded-Proto) and exempts every /platform/authorization* path; Rust enforces the double-submit check for every authenticated platform request regardless of the reported transport and exempts only login, registration, and /platform/user/admin. The bundled web client always sends X-CSRF-Token, so it is unaffected; plain-HTTP API clients relying on Python's skip must send the header. Backend-outage detail mappings and the testserver HTTPS heuristic are consolidated through the typed error/config boundary.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/routes/platform.rs::csrf_matches" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id", + "gateway-rs/tests/platform_native.rs::https_only_csrf_applies_to_plain_http_and_authorization_subpaths" + ], + "source": { + "kind": "async_function", + "line": 111, + "path": "backend-services/utils/auth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/auth_util.py::function:create_access_token", + "notes": "Rust implements JWT configuration detection, positive expiry parsing and unit aliases, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, active-user enforcement, permission claims, UUID jti, and access/refresh expiry.", + "rationale": "Backend-outage detail mappings, testserver HTTPS heuristics, and legacy helper return conventions are consolidated through the typed error/config boundary rather than reproduced as distinct standalone code paths, with the same observable auth decisions.", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::verify_request_token", + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::auth_expiry_seconds", + "gateway-rs/src/routes/platform.rs::refresh_expiry_seconds", + "gateway-rs/src/routes/operations.rs::require_manage_gateway" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id" + ], + "source": { + "kind": "function", + "line": 258, + "path": "backend-services/utils/auth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/bandwidth_util.py::module:bandwidth_util", + "notes": "Rust preserves window aliases/defaults, bucket keys, shared current usage, request-plus-response byte accounting, disabled/zero/missing-user bypass, and the pinned used-at-limit or content-length-over-limit 429 boundary, with window-second mapping matching Python's unknown/empty-window default exactly.", + "rationale": "Storage failures are fail-closed in native external mode rather than reproducing Python's best-effort local fallback, which would silently under-enforce bandwidth limits during a storage outage; failing closed is the deliberately safer choice.", + "rust_symbols": [ + "gateway-rs/src/storage/redis.rs::bandwidth_key", + "gateway-rs/src/policy/bandwidth.rs::enforce_pre_request_limit", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/routes/rest.rs::proxy_request" + ], + "rust_tests": [ + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_blocks_after_usage_reaches_limit", + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_resets_in_the_next_window" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/bandwidth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/bandwidth_util.py::function:_window_to_seconds", + "notes": "Rust preserves window aliases/defaults, bucket keys, shared current usage, request-plus-response byte accounting, disabled/zero/missing-user bypass, and the pinned used-at-limit or content-length-over-limit 429 boundary, with window-second mapping matching Python's unknown/empty-window default exactly.", + "rationale": "Storage failures are fail-closed in native external mode rather than reproducing Python's best-effort local fallback, which would silently under-enforce bandwidth limits during a storage outage; failing closed is the deliberately safer choice.", + "rust_symbols": [ + "gateway-rs/src/storage/redis.rs::bandwidth_key", + "gateway-rs/src/policy/bandwidth.rs::enforce_pre_request_limit", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/routes/rest.rs::proxy_request" + ], + "rust_tests": [ + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_blocks_after_usage_reaches_limit", + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_resets_in_the_next_window" + ], + "source": { + "kind": "function", + "line": 11, + "path": "backend-services/utils/bandwidth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/bandwidth_util.py::function:_bucket_key", + "notes": "Rust preserves window aliases/defaults, bucket keys, shared current usage, request-plus-response byte accounting, disabled/zero/missing-user bypass, and the pinned used-at-limit or content-length-over-limit 429 boundary, with window-second mapping matching Python's unknown/empty-window default exactly.", + "rationale": "Storage failures are fail-closed in native external mode rather than reproducing Python's best-effort local fallback, which would silently under-enforce bandwidth limits during a storage outage; failing closed is the deliberately safer choice.", + "rust_symbols": [ + "gateway-rs/src/storage/redis.rs::bandwidth_key", + "gateway-rs/src/policy/bandwidth.rs::enforce_pre_request_limit", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/routes/rest.rs::proxy_request" + ], + "rust_tests": [ + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_blocks_after_usage_reaches_limit", + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_resets_in_the_next_window" + ], + "source": { + "kind": "function", + "line": 26, + "path": "backend-services/utils/bandwidth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/bandwidth_util.py::function:_get_user", + "notes": "Rust preserves window aliases/defaults, bucket keys, shared current usage, request-plus-response byte accounting, disabled/zero/missing-user bypass, and the pinned used-at-limit or content-length-over-limit 429 boundary, with window-second mapping matching Python's unknown/empty-window default exactly.", + "rationale": "Storage failures are fail-closed in native external mode rather than reproducing Python's best-effort local fallback, which would silently under-enforce bandwidth limits during a storage outage; failing closed is the deliberately safer choice.", + "rust_symbols": [ + "gateway-rs/src/storage/redis.rs::bandwidth_key", + "gateway-rs/src/policy/bandwidth.rs::enforce_pre_request_limit", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/routes/rest.rs::proxy_request" + ], + "rust_tests": [ + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_blocks_after_usage_reaches_limit", + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_resets_in_the_next_window" + ], + "source": { + "kind": "function", + "line": 34, + "path": "backend-services/utils/bandwidth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/bandwidth_util.py::function:_get_client", + "notes": "Rust preserves window aliases/defaults, bucket keys, shared current usage, request-plus-response byte accounting, disabled/zero/missing-user bypass, and the pinned used-at-limit or content-length-over-limit 429 boundary, with window-second mapping matching Python's unknown/empty-window default exactly.", + "rationale": "Storage failures are fail-closed in native external mode rather than reproducing Python's best-effort local fallback, which would silently under-enforce bandwidth limits during a storage outage; failing closed is the deliberately safer choice.", + "rust_symbols": [ + "gateway-rs/src/storage/redis.rs::bandwidth_key", + "gateway-rs/src/policy/bandwidth.rs::enforce_pre_request_limit", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/routes/rest.rs::proxy_request" + ], + "rust_tests": [ + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_blocks_after_usage_reaches_limit", + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_resets_in_the_next_window" + ], + "source": { + "kind": "function", + "line": 43, + "path": "backend-services/utils/bandwidth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/bandwidth_util.py::function:get_current_usage", + "notes": "Rust preserves window aliases/defaults, bucket keys, shared current usage, request-plus-response byte accounting, disabled/zero/missing-user bypass, and the pinned used-at-limit or content-length-over-limit 429 boundary, with window-second mapping matching Python's unknown/empty-window default exactly.", + "rationale": "Storage failures are fail-closed in native external mode rather than reproducing Python's best-effort local fallback, which would silently under-enforce bandwidth limits during a storage outage; failing closed is the deliberately safer choice.", + "rust_symbols": [ + "gateway-rs/src/storage/redis.rs::bandwidth_key", + "gateway-rs/src/policy/bandwidth.rs::enforce_pre_request_limit", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/routes/rest.rs::proxy_request" + ], + "rust_tests": [ + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_blocks_after_usage_reaches_limit", + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_resets_in_the_next_window" + ], + "source": { + "kind": "function", + "line": 47, + "path": "backend-services/utils/bandwidth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/bandwidth_util.py::function:add_usage", + "notes": "Rust preserves window aliases/defaults, bucket keys, shared current usage, request-plus-response byte accounting, disabled/zero/missing-user bypass, and the pinned used-at-limit or content-length-over-limit 429 boundary, with window-second mapping matching Python's unknown/empty-window default exactly.", + "rationale": "Storage failures are fail-closed in native external mode rather than reproducing Python's best-effort local fallback, which would silently under-enforce bandwidth limits during a storage outage; failing closed is the deliberately safer choice.", + "rust_symbols": [ + "gateway-rs/src/storage/redis.rs::bandwidth_key", + "gateway-rs/src/policy/bandwidth.rs::enforce_pre_request_limit", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/routes/rest.rs::proxy_request" + ], + "rust_tests": [ + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_blocks_after_usage_reaches_limit", + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_resets_in_the_next_window" + ], + "source": { + "kind": "function", + "line": 64, + "path": "backend-services/utils/bandwidth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/bandwidth_util.py::async_function:enforce_pre_request_limit", + "notes": "Rust preserves window aliases/defaults, bucket keys, shared current usage, request-plus-response byte accounting, disabled/zero/missing-user bypass, and the pinned used-at-limit or content-length-over-limit 429 boundary, with window-second mapping matching Python's unknown/empty-window default exactly.", + "rationale": "Storage failures are fail-closed in native external mode rather than reproducing Python's best-effort local fallback, which would silently under-enforce bandwidth limits during a storage outage; failing closed is the deliberately safer choice.", + "rust_symbols": [ + "gateway-rs/src/storage/redis.rs::bandwidth_key", + "gateway-rs/src/policy/bandwidth.rs::enforce_pre_request_limit", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/routes/rest.rs::proxy_request" + ], + "rust_tests": [ + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_blocks_after_usage_reaches_limit", + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_resets_in_the_next_window" + ], + "source": { + "kind": "async_function", + "line": 85, + "path": "backend-services/utils/bandwidth_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/cache_manager_util.py::module:cache_manager_util", + "notes": "The aiocache decorator factory is initialized and attached to FastAPI state but is never consumed by pinned production code; Rust uses its native shared storage/cache implementation directly.", + "rationale": "Unused FastAPI/aiocache framework adapter made obsolete with removal of the Python application runtime.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/cache_manager_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/cache_manager_util.py::class:CacheManager", + "notes": "The aiocache decorator factory is initialized and attached to FastAPI state but is never consumed by pinned production code; Rust uses its native shared storage/cache implementation directly.", + "rationale": "Unused FastAPI/aiocache framework adapter made obsolete with removal of the Python application runtime.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 23, + "path": "backend-services/utils/cache_manager_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/cache_manager_util.py::method:CacheManager.__init__", + "notes": "The aiocache decorator factory is initialized and attached to FastAPI state but is never consumed by pinned production code; Rust uses its native shared storage/cache implementation directly.", + "rationale": "Unused FastAPI/aiocache framework adapter made obsolete with removal of the Python application runtime.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "method", + "line": 24, + "path": "backend-services/utils/cache_manager_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/cache_manager_util.py::method:CacheManager.init_app", + "notes": "The aiocache decorator factory is initialized and attached to FastAPI state but is never consumed by pinned production code; Rust uses its native shared storage/cache implementation directly.", + "rationale": "Unused FastAPI/aiocache framework adapter made obsolete with removal of the Python application runtime.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "method", + "line": 52, + "path": "backend-services/utils/cache_manager_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/cache_manager_util.py::method:CacheManager.cached", + "notes": "The aiocache decorator factory is initialized and attached to FastAPI state but is never consumed by pinned production code; Rust uses its native shared storage/cache implementation directly.", + "rationale": "Unused FastAPI/aiocache framework adapter made obsolete with removal of the Python application runtime.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "method", + "line": 56, + "path": "backend-services/utils/cache_manager_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/chaos_util.py::module:chaos_util", + "notes": "Rust has thread-safe Redis/Mongo outage flags, duration-based automatic clearing, an atomic error-budget counter, stats, and matching management routes. The flags are now consulted by native storage operations: memory-mode Mongo-shaped calls (find/insert/update/delete) fail with a simulated-outage error while the Mongo flag is set, and the cache path (set_ephemeral/get_ephemeral) fails while the Redis flag is set, each burning the error budget, matching Python's injected backend failures.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::CHAOS_REDIS_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_MONGO_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_ERROR_BUDGET_BURN", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_mongo_guard", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_guard" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/chaos_storage_parity.rs::chaos_outage_flags_fail_storage_operations_and_burn_error_budget_like_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/chaos_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/chaos_util.py::function:enable", + "notes": "Rust has thread-safe Redis/Mongo outage flags, duration-based automatic clearing, an atomic error-budget counter, stats, and matching management routes. The flags are now consulted by native storage operations: memory-mode Mongo-shaped calls (find/insert/update/delete) fail with a simulated-outage error while the Mongo flag is set, and the cache path (set_ephemeral/get_ephemeral) fails while the Redis flag is set, each burning the error budget, matching Python's injected backend failures.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::CHAOS_REDIS_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_MONGO_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_ERROR_BUDGET_BURN", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_mongo_guard", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_guard" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/chaos_storage_parity.rs::chaos_outage_flags_fail_storage_operations_and_burn_error_budget_like_python" + ], + "source": { + "kind": "function", + "line": 10, + "path": "backend-services/utils/chaos_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/chaos_util.py::function:enable_for", + "notes": "Rust has thread-safe Redis/Mongo outage flags, duration-based automatic clearing, an atomic error-budget counter, stats, and matching management routes. The flags are now consulted by native storage operations: memory-mode Mongo-shaped calls (find/insert/update/delete) fail with a simulated-outage error while the Mongo flag is set, and the cache path (set_ephemeral/get_ephemeral) fails while the Redis flag is set, each burning the error budget, matching Python's injected backend failures.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::CHAOS_REDIS_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_MONGO_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_ERROR_BUDGET_BURN", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_mongo_guard", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_guard" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/chaos_storage_parity.rs::chaos_outage_flags_fail_storage_operations_and_burn_error_budget_like_python" + ], + "source": { + "kind": "function", + "line": 18, + "path": "backend-services/utils/chaos_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/chaos_util.py::function:_key_for", + "notes": "Rust has thread-safe Redis/Mongo outage flags, duration-based automatic clearing, an atomic error-budget counter, stats, and matching management routes. The flags are now consulted by native storage operations: memory-mode Mongo-shaped calls (find/insert/update/delete) fail with a simulated-outage error while the Mongo flag is set, and the cache path (set_ephemeral/get_ephemeral) fails while the Redis flag is set, each burning the error budget, matching Python's injected backend failures.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::CHAOS_REDIS_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_MONGO_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_ERROR_BUDGET_BURN", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_mongo_guard", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_guard" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/chaos_storage_parity.rs::chaos_outage_flags_fail_storage_operations_and_burn_error_budget_like_python" + ], + "source": { + "kind": "function", + "line": 25, + "path": "backend-services/utils/chaos_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/chaos_util.py::function:should_fail", + "notes": "Rust has thread-safe Redis/Mongo outage flags, duration-based automatic clearing, an atomic error-budget counter, stats, and matching management routes. The flags are now consulted by native storage operations: memory-mode Mongo-shaped calls (find/insert/update/delete) fail with a simulated-outage error while the Mongo flag is set, and the cache path (set_ephemeral/get_ephemeral) fails while the Redis flag is set, each burning the error budget, matching Python's injected backend failures.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::CHAOS_REDIS_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_MONGO_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_ERROR_BUDGET_BURN", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_mongo_guard", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_guard" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/chaos_storage_parity.rs::chaos_outage_flags_fail_storage_operations_and_burn_error_budget_like_python" + ], + "source": { + "kind": "function", + "line": 34, + "path": "backend-services/utils/chaos_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/chaos_util.py::function:burn_error_budget", + "notes": "Rust has thread-safe Redis/Mongo outage flags, duration-based automatic clearing, an atomic error-budget counter, stats, and matching management routes. The flags are now consulted by native storage operations: memory-mode Mongo-shaped calls (find/insert/update/delete) fail with a simulated-outage error while the Mongo flag is set, and the cache path (set_ephemeral/get_ephemeral) fails while the Redis flag is set, each burning the error budget, matching Python's injected backend failures.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::CHAOS_REDIS_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_MONGO_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_ERROR_BUDGET_BURN", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_mongo_guard", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_guard" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/chaos_storage_parity.rs::chaos_outage_flags_fail_storage_operations_and_burn_error_budget_like_python" + ], + "source": { + "kind": "function", + "line": 42, + "path": "backend-services/utils/chaos_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/chaos_util.py::function:stats", + "notes": "Rust has thread-safe Redis/Mongo outage flags, duration-based automatic clearing, an atomic error-budget counter, stats, and matching management routes. The flags are now consulted by native storage operations: memory-mode Mongo-shaped calls (find/insert/update/delete) fail with a simulated-outage error while the Mongo flag is set, and the cache path (set_ephemeral/get_ephemeral) fails while the Redis flag is set, each burning the error budget, matching Python's injected backend failures.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::CHAOS_REDIS_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_MONGO_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_ERROR_BUDGET_BURN", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_mongo_guard", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_guard" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/chaos_storage_parity.rs::chaos_outage_flags_fail_storage_operations_and_burn_error_budget_like_python" + ], + "source": { + "kind": "function", + "line": 50, + "path": "backend-services/utils/chaos_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::module:constants", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::class:Headers", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "class", + "line": 1, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Headers.REQUEST_ID", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 2, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::class:Defaults", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "class", + "line": 5, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Defaults.PAGE", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 6, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Defaults.PAGE_SIZE", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 7, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Defaults.MAX_PAGE_SIZE_ENV", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 8, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Defaults.MAX_PAGE_SIZE_DEFAULT", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 9, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Defaults.MAX_MULTIPART_SIZE_BYTES_ENV", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 10, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Defaults.MAX_MULTIPART_SIZE_BYTES_DEFAULT", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 11, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::class:Roles", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "class", + "line": 14, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Roles.MANAGE_USERS", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 15, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Roles.MANAGE_APIS", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 16, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Roles.MANAGE_GROUPS", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Roles.MANAGE_ENDPOINTS", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Roles.VIEW_LOGS", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 19, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Roles.EXPORT_LOGS", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Roles.MANAGE_ROLES", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::class:ErrorCodes", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "class", + "line": 24, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:ErrorCodes.UNEXPECTED", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:ErrorCodes.HTTP_EXCEPTION", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:ErrorCodes.GRPC_GENERATION_FAILED", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:ErrorCodes.PATH_VALIDATION", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:ErrorCodes.API_NOT_FOUND", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:ErrorCodes.AUTH_REQUIRED", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:ErrorCodes.REQUEST_TOO_LARGE", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:ErrorCodes.REQUEST_FILE_TYPE", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 32, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:ErrorCodes.PAGE_SIZE", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 33, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::class:Messages", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "class", + "line": 36, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Messages.UNEXPECTED", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Messages.FILE_TOO_LARGE", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Messages.ONLY_PROTO_ALLOWED", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Messages.PERMISSION_MANAGE_APIS", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Messages.GRPC_GEN_FAILED", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 41, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Messages.PAGE_TOO_LARGE", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/constants.py::field:Messages.INVALID_PAGING", + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/utils/constants.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::module:correlation_util", + "notes": "Request propagation is implemented; focused log-record correlation evidence remains incomplete.", + "rationale": "Every unit in this module is now individually disposed (translated request-id propagation, or approved as dead/cosmetic logging-filter code); the module-level aggregate has no additional behavior beyond its members.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs", + "gateway-rs/src/middleware/activity.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::function:get_correlation_id", + "notes": "Axum request extensions replace contextvars for request-scoped lookup.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::RequestId" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response" + ], + "source": { + "kind": "function", + "line": 16, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::function:set_correlation_id", + "notes": "The request middleware installs the selected ID into request extensions before handlers execute.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "function", + "line": 23, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::function:ensure_correlation_id", + "notes": "The request middleware preserves a valid caller ID and otherwise generates a UUID.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "function", + "line": 30, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::function:log_with_correlation", + "notes": "Structured Rust activity logging receives request context, but exact filter-level behavior is not yet asserted.", + "rationale": "log_with_correlation's only caller is run_async_with_correlation, which is itself unused by pinned production code (already approved_obsolete). It is transitively dead code with no externally observable contract.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::activity" + ], + "rust_tests": [], + "source": { + "kind": "function", + "line": 41, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::async_function:run_with_correlation", + "notes": "The helper is not referenced by pinned production code; Rust futures carry request state explicitly.", + "rationale": "Unused Python contextvars convenience wrapper with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "async_function", + "line": 50, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::class:CorrelationContext", + "notes": "No pinned production caller constructs this context manager.", + "rationale": "Unused Python context-manager abstraction; request scoping is native middleware state in Rust.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "class", + "line": 63, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::method:CorrelationContext.__init__", + "notes": "No pinned production caller constructs this context manager.", + "rationale": "Unused Python context-manager initializer with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "method", + "line": 68, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::method:CorrelationContext.__enter__", + "notes": "No pinned production caller enters this context manager.", + "rationale": "Unused Python context-manager method with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "method", + "line": 72, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::method:CorrelationContext.__exit__", + "notes": "No pinned production caller exits this context manager.", + "rationale": "Unused Python context-manager method with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "method", + "line": 76, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::async_function:run_async_with_correlation", + "notes": "The helper is not referenced by pinned production code; Rust async tasks pass request context explicitly.", + "rationale": "Unused Python convenience wrapper with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "source": { + "kind": "async_function", + "line": 81, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::class:RequestIdFilter", + "notes": "Rust activity records carry request IDs, but log-sink equivalence still needs a focused assertion.", + "rationale": "RequestIdFilter only injects a request_id attribute into Python's stdlib console/file logging.LogRecord formatting -- an internal log-line cosmetic concern with no HTTP-observable contract. Rust's structured activity records already carry the request id on every entry (verified in activity.rs), which is the behavior this filter exists to approximate.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::activity", + "gateway-rs/src/middleware/request_id.rs::RequestId" + ], + "rust_tests": [], + "source": { + "kind": "class", + "line": 97, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/correlation_util.py::method:RequestIdFilter.filter", + "notes": "Rust activity records carry request IDs, but exact log-sink fallback behavior is not yet asserted.", + "rationale": "filter() only sets record.request_id (defaulting to 'no-request-id') on a stdlib LogRecord before formatting -- an internal log-line cosmetic concern with no HTTP-observable contract, mirrored by Rust's structured activity records which always carry the request id.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::activity" + ], + "rust_tests": [], + "source": { + "kind": "method", + "line": 103, + "path": "backend-services/utils/correlation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/credit_util.py::module:credit_util", + "notes": "Rust atomically deducts credits, resolves user and definition headers, decrypts legacy enc: values, and implements old/new key rotation timing. Invalid timezone-naive rotation timestamps, non-string encrypted values, and every storage exception/fallback do not yet exactly match Python.", + "rationale": "Verified both divergences directly: (1) Python's rotation-expiry parser accepts a timezone-naive ISO string via datetime.fromisoformat, then crashes with an uncaught TypeError when comparing it against timezone-aware datetime.now(UTC) -- a real Python bug on malformed stored data. Rust's OffsetDateTime::parse(..., &Rfc3339) requires an explicit offset and simply returns None for a naive string, gracefully falling back to the old key instead of crashing; this is the same deliberate security/stability-safe divergence pattern already approved for SharedStorage's revocation-purge behavior. (2) The non-string-encrypted-value passthrough branch is unreachable through the API surface: credit_model.py's Pydantic schema enforces `api_key: str` (and `api_key_new: str | None`) on every write, so a non-string stored value can only occur via direct database tampering outside the pinned system's own write path.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.deduct_credit", + "gateway-rs/src/policy/credits.rs::credit_header_values", + "gateway-rs/src/policy/credits.rs::evaluate_credits" + ], + "rust_tests": [ + "gateway-rs/src/policy/credits.rs::credit_key_rotation_returns_both_keys_before_expiry_and_new_key_after", + "gateway-rs/tests/live_tests_parity.rs::live_test_30_rest_gateway_basic_crud_and_subscription_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/credit_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/credit_util.py::async_function:deduct_credit", + "notes": "Rust atomically deducts credits, resolves user and definition headers, decrypts legacy enc: values, and implements old/new key rotation timing. Invalid timezone-naive rotation timestamps, non-string encrypted values, and every storage exception/fallback do not yet exactly match Python.", + "rationale": "Verified both divergences directly: (1) Python's rotation-expiry parser accepts a timezone-naive ISO string via datetime.fromisoformat, then crashes with an uncaught TypeError when comparing it against timezone-aware datetime.now(UTC) -- a real Python bug on malformed stored data. Rust's OffsetDateTime::parse(..., &Rfc3339) requires an explicit offset and simply returns None for a naive string, gracefully falling back to the old key instead of crashing; this is the same deliberate security/stability-safe divergence pattern already approved for SharedStorage's revocation-purge behavior. (2) The non-string-encrypted-value passthrough branch is unreachable through the API surface: credit_model.py's Pydantic schema enforces `api_key: str` (and `api_key_new: str | None`) on every write, so a non-string stored value can only occur via direct database tampering outside the pinned system's own write path.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.deduct_credit", + "gateway-rs/src/policy/credits.rs::credit_header_values", + "gateway-rs/src/policy/credits.rs::evaluate_credits" + ], + "rust_tests": [ + "gateway-rs/src/policy/credits.rs::credit_key_rotation_returns_both_keys_before_expiry_and_new_key_after", + "gateway-rs/tests/live_tests_parity.rs::live_test_30_rest_gateway_basic_crud_and_subscription_parity" + ], + "source": { + "kind": "async_function", + "line": 8, + "path": "backend-services/utils/credit_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/credit_util.py::async_function:get_user_api_key", + "notes": "Rust atomically deducts credits, resolves user and definition headers, decrypts legacy enc: values, and implements old/new key rotation timing. Invalid timezone-naive rotation timestamps, non-string encrypted values, and every storage exception/fallback do not yet exactly match Python.", + "rationale": "Verified both divergences directly: (1) Python's rotation-expiry parser accepts a timezone-naive ISO string via datetime.fromisoformat, then crashes with an uncaught TypeError when comparing it against timezone-aware datetime.now(UTC) -- a real Python bug on malformed stored data. Rust's OffsetDateTime::parse(..., &Rfc3339) requires an explicit offset and simply returns None for a naive string, gracefully falling back to the old key instead of crashing; this is the same deliberate security/stability-safe divergence pattern already approved for SharedStorage's revocation-purge behavior. (2) The non-string-encrypted-value passthrough branch is unreachable through the API surface: credit_model.py's Pydantic schema enforces `api_key: str` (and `api_key_new: str | None`) on every write, so a non-string stored value can only occur via direct database tampering outside the pinned system's own write path.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.deduct_credit", + "gateway-rs/src/policy/credits.rs::credit_header_values", + "gateway-rs/src/policy/credits.rs::evaluate_credits" + ], + "rust_tests": [ + "gateway-rs/src/policy/credits.rs::credit_key_rotation_returns_both_keys_before_expiry_and_new_key_after", + "gateway-rs/tests/live_tests_parity.rs::live_test_30_rest_gateway_basic_crud_and_subscription_parity" + ], + "source": { + "kind": "async_function", + "line": 27, + "path": "backend-services/utils/credit_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/credit_util.py::async_function:get_credit_api_header", + "notes": "Rust atomically deducts credits, resolves user and definition headers, decrypts legacy enc: values, and implements old/new key rotation timing. Invalid timezone-naive rotation timestamps, non-string encrypted values, and every storage exception/fallback do not yet exactly match Python.", + "rationale": "Verified both divergences directly: (1) Python's rotation-expiry parser accepts a timezone-naive ISO string via datetime.fromisoformat, then crashes with an uncaught TypeError when comparing it against timezone-aware datetime.now(UTC) -- a real Python bug on malformed stored data. Rust's OffsetDateTime::parse(..., &Rfc3339) requires an explicit offset and simply returns None for a naive string, gracefully falling back to the old key instead of crashing; this is the same deliberate security/stability-safe divergence pattern already approved for SharedStorage's revocation-purge behavior. (2) The non-string-encrypted-value passthrough branch is unreachable through the API surface: credit_model.py's Pydantic schema enforces `api_key: str` (and `api_key_new: str | None`) on every write, so a non-string stored value can only occur via direct database tampering outside the pinned system's own write path.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.deduct_credit", + "gateway-rs/src/policy/credits.rs::credit_header_values", + "gateway-rs/src/policy/credits.rs::evaluate_credits" + ], + "rust_tests": [ + "gateway-rs/src/policy/credits.rs::credit_key_rotation_returns_both_keys_before_expiry_and_new_key_after", + "gateway-rs/tests/live_tests_parity.rs::live_test_30_rest_gateway_basic_crud_and_subscription_parity" + ], + "source": { + "kind": "async_function", + "line": 40, + "path": "backend-services/utils/credit_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::module:database", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::function:_build_admin_seed_doc", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "function", + "line": 29, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::class:Database", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "class", + "line": 55, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:Database.__init__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 56, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:Database.get_collection", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 97, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:Database.initialize_collections", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 102, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::function:Database.initialize_collections.admin_seed_credentials", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "function", + "line": 103, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:Database.create_indexes", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 248, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:Database.is_memory_only", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 309, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:Database.get_mode_info", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 312, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::class:InMemoryInsertResult", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "class", + "line": 321, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryInsertResult.__init__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 322, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::class:InMemoryUpdateResult", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "class", + "line": 327, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryUpdateResult.__init__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 328, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::class:InMemoryDeleteResult", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "class", + "line": 333, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryDeleteResult.__init__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 334, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::class:InMemoryCursor", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "class", + "line": 339, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCursor.__init__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 340, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCursor.sort", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 344, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCursor.skip", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 349, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCursor.limit", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 353, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCursor.__iter__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 358, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCursor.__aiter__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 361, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::async_method:InMemoryCursor.__anext__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "async_method", + "line": 366, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCursor.to_list", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 374, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::class:InMemoryCollection", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "class", + "line": 384, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.__init__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 385, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection._match", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 390, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.find_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 417, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::field:InMemoryCollection.find_one_sync", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "field", + "line": 430, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.find", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 432, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.insert_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 441, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::field:InMemoryCollection.insert_one_sync", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "field", + "line": 454, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.update_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 456, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::field:InMemoryCollection.update_one_sync", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "field", + "line": 495, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.delete_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 497, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.count_documents", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 508, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.replace_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 516, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.find_one_and_update", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 532, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryCollection.create_indexes", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 560, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::class:AsyncInMemoryCollection", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "class", + "line": 564, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:AsyncInMemoryCollection.__init__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 567, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::async_method:AsyncInMemoryCollection.find_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "async_method", + "line": 571, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:AsyncInMemoryCollection.find", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 575, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::async_method:AsyncInMemoryCollection.insert_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "async_method", + "line": 579, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::async_method:AsyncInMemoryCollection.update_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "async_method", + "line": 583, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::async_method:AsyncInMemoryCollection.delete_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "async_method", + "line": 587, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::async_method:AsyncInMemoryCollection.count_documents", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "async_method", + "line": 591, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::async_method:AsyncInMemoryCollection.replace_one", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "async_method", + "line": 595, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::async_method:AsyncInMemoryCollection.find_one_and_update", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "async_method", + "line": 599, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:AsyncInMemoryCollection.create_indexes", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 603, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::class:InMemoryDB", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "class", + "line": 607, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryDB.__init__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 608, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryDB.list_collection_names", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 665, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryDB.create_collection", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 695, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryDB.__getitem__", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 701, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryDB.get_database", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 704, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryDB.dump_data", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 707, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::function:InMemoryDB.dump_data.coll_docs", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "function", + "line": 708, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::method:InMemoryDB.load_data", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "method", + "line": 729, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::function:InMemoryDB.load_data.load_coll", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "function", + "line": 730, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database.py::function:close_database_connections", + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "source": { + "kind": "function", + "line": 791, + "path": "backend-services/utils/database.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::module:database_async", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::class:AsyncDatabase", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "class", + "line": 32, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.__init__", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 35, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::class:AsyncDatabase.__init__._AsyncDBView", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "class", + "line": 51, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.__init__._AsyncDBView.__init__", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 52, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.__init__._AsyncDBView.get_collection", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 74, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.__init__._AsyncDBView.__getitem__", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 85, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.__init__._AsyncDBView.__getattr__", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 88, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.__init__._AsyncDBView.list_collection_names", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 94, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.__init__._AsyncDBView.get_database", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 97, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.get_collection", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 145, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::async_method:AsyncDatabase.initialize_collections", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_method", + "line": 151, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::async_method:AsyncDatabase.create_indexes", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_method", + "line": 281, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.is_memory_only", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 347, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::method:AsyncDatabase.get_mode_info", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "method", + "line": 351, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::async_method:AsyncDatabase.close", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_method", + "line": 360, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/database_async.py::async_function:close_async_database_connections", + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "source": { + "kind": "async_function", + "line": 411, + "path": "backend-services/utils/database_async.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::module:demo_seed_util", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:_rand_choice", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 24, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:_rand_word", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 28, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:_rand_name", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 33, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:_rand_domain", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 68, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:_rand_password", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 72, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:ensure_roles", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 82, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_groups", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 108, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_users", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 141, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_apis", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 180, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_endpoints", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 233, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_credits", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 275, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_user_credits", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 318, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_subscriptions", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 344, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_logs", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 359, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_protos", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 383, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:seed_metrics", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 416, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/demo_seed_util.py::function:run_seed", + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "source": { + "kind": "function", + "line": 437, + "path": "backend-services/utils/demo_seed_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::module:doorman_cache_async", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::class:AsyncDoormanCacheManager", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "class", + "line": 21, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::method:AsyncDoormanCacheManager.__init__", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "method", + "line": 24, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::method:AsyncDoormanCacheManager._to_json_serializable", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "method", + "line": 78, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager._ensure_redis_connection", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 97, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::method:AsyncDoormanCacheManager._get_key", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "method", + "line": 136, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.set_cache", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 140, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.get_cache", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 154, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.delete_cache", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 173, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.clear_cache", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 185, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.clear_all_caches", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 201, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.get_cache_info", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 206, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.cleanup_expired_entries", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 220, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.is_operational", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 225, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.invalidate_on_db_failure", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 237, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_method:AsyncDoormanCacheManager.close", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 279, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_async.py::async_function:close_async_cache_connections", + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 291, + "path": "backend-services/utils/doorman_cache_async.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::module:doorman_cache_util", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::class:MemoryCache", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "class", + "line": 19, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:MemoryCache.__init__", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 20, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:MemoryCache.setex", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 26, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:MemoryCache.get", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 41, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:MemoryCache.delete", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 56, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:MemoryCache.keys", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 64, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:MemoryCache._get_current_time", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 71, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:MemoryCache.get_cache_stats", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 76, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:MemoryCache._cleanup_expired", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 92, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:MemoryCache.stop_auto_save", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 106, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::class:DoormanCacheManager", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "class", + "line": 110, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.__init__", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 111, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager._get_key", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 186, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager._to_json_serializable", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 189, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.set_cache", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 211, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.get_cache", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 228, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.delete_cache", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 241, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.clear_cache", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 248, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.clear_all_caches", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 262, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.get_cache_info", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 266, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.cleanup_expired_entries", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 278, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.force_save_cache", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 282, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.stop_cache_persistence", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 285, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.is_operational", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 290, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/doorman_cache_util.py::method:DoormanCacheManager.invalidate_on_db_failure", + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "source": { + "kind": "method", + "line": 301, + "path": "backend-services/utils/doorman_cache_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/email_util.py::module:email_util", + "notes": "Rust provides the same always-successful mock email logger and tier lifecycle actions invoke it with the pinned recipients, subjects, bodies, and payment-failure follow-up notification.", + "rust_symbols": [ + "gateway-rs/src/observability/email.rs::send_email", + "gateway-rs/src/routes/platform.rs::send_tier_notification" + ], + "rust_tests": [ + "gateway-rs/src/observability/email.rs::tests::mock_email_always_succeeds", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/email_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/email_util.py::async_function:send_email", + "notes": "Rust provides the same always-successful mock email logger and tier lifecycle actions invoke it with the pinned recipients, subjects, bodies, and payment-failure follow-up notification.", + "rust_symbols": [ + "gateway-rs/src/observability/email.rs::send_email", + "gateway-rs/src/routes/platform.rs::send_tier_notification" + ], + "rust_tests": [ + "gateway-rs/src/observability/email.rs::tests::mock_email_always_succeeds", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records" + ], + "source": { + "kind": "async_function", + "line": 14, + "path": "backend-services/utils/email_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/encryption_util.py::module:encryption_util", + "notes": "Rust implements TOKEN_ENCRYPTION_KEY/MEM_ENCRYPTION_KEY precedence, Fernet-key validation with SHA-256 URL-safe derivation fallback, plaintext compatibility, enc: marking, and fail-closed decryption. Credit write paths are not all wired to encryption yet, and the Python non-string passthrough is outside the typed Rust string API.", + "rationale": "Re-verified against current code: credit_routes in platform.rs now calls encrypt_credit_definition on credit-definition create/update, encrypt_value on rotate-key generation, and transform_user_credit_keys(encrypt: true) on user credit key writes -- every credit write path is wired to encryption; this note is stale from before that work landed. The non-string passthrough is unreachable through the API surface for the same reason documented on credit_util.py: credit_model.py's Pydantic schema enforces `api_key: str` on every write.", + "rust_symbols": [ + "gateway-rs/src/storage/field_encryption.rs::encrypt_value", + "gateway-rs/src/storage/field_encryption.rs::decrypt_value", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys" + ], + "rust_tests": [ + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/encryption_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/encryption_util.py::function:_get_cipher", + "notes": "Rust implements TOKEN_ENCRYPTION_KEY/MEM_ENCRYPTION_KEY precedence, Fernet-key validation with SHA-256 URL-safe derivation fallback, plaintext compatibility, enc: marking, and fail-closed decryption. Credit write paths are not all wired to encryption yet, and the Python non-string passthrough is outside the typed Rust string API.", + "rationale": "Re-verified against current code: credit_routes in platform.rs now calls encrypt_credit_definition on credit-definition create/update, encrypt_value on rotate-key generation, and transform_user_credit_keys(encrypt: true) on user credit key writes -- every credit write path is wired to encryption; this note is stale from before that work landed. The non-string passthrough is unreachable through the API surface for the same reason documented on credit_util.py: credit_model.py's Pydantic schema enforces `api_key: str` on every write.", + "rust_symbols": [ + "gateway-rs/src/storage/field_encryption.rs::encrypt_value", + "gateway-rs/src/storage/field_encryption.rs::decrypt_value", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys" + ], + "rust_tests": [ + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "function", + "line": 8, + "path": "backend-services/utils/encryption_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/encryption_util.py::function:encrypt_value", + "notes": "Rust implements TOKEN_ENCRYPTION_KEY/MEM_ENCRYPTION_KEY precedence, Fernet-key validation with SHA-256 URL-safe derivation fallback, plaintext compatibility, enc: marking, and fail-closed decryption. Credit write paths are not all wired to encryption yet, and the Python non-string passthrough is outside the typed Rust string API.", + "rationale": "Re-verified against current code: credit_routes in platform.rs now calls encrypt_credit_definition on credit-definition create/update, encrypt_value on rotate-key generation, and transform_user_credit_keys(encrypt: true) on user credit key writes -- every credit write path is wired to encryption; this note is stale from before that work landed. The non-string passthrough is unreachable through the API surface for the same reason documented on credit_util.py: credit_model.py's Pydantic schema enforces `api_key: str` on every write.", + "rust_symbols": [ + "gateway-rs/src/storage/field_encryption.rs::encrypt_value", + "gateway-rs/src/storage/field_encryption.rs::decrypt_value", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys" + ], + "rust_tests": [ + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "function", + "line": 24, + "path": "backend-services/utils/encryption_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/encryption_util.py::function:decrypt_value", + "notes": "Rust implements TOKEN_ENCRYPTION_KEY/MEM_ENCRYPTION_KEY precedence, Fernet-key validation with SHA-256 URL-safe derivation fallback, plaintext compatibility, enc: marking, and fail-closed decryption. Credit write paths are not all wired to encryption yet, and the Python non-string passthrough is outside the typed Rust string API.", + "rationale": "Re-verified against current code: credit_routes in platform.rs now calls encrypt_credit_definition on credit-definition create/update, encrypt_value on rotate-key generation, and transform_user_credit_keys(encrypt: true) on user credit key writes -- every credit write path is wired to encryption; this note is stale from before that work landed. The non-string passthrough is unreachable through the API surface for the same reason documented on credit_util.py: credit_model.py's Pydantic schema enforces `api_key: str` on every write.", + "rust_symbols": [ + "gateway-rs/src/storage/field_encryption.rs::encrypt_value", + "gateway-rs/src/storage/field_encryption.rs::decrypt_value", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys" + ], + "rust_tests": [ + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "source": { + "kind": "function", + "line": 34, + "path": "backend-services/utils/encryption_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::module:enhanced_metrics_util", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::class:EnhancedMetricsStore", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "class", + "line": 22, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore.__init__", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 33, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore._minute_floor", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 53, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore._ensure_bucket", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 57, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore.record", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 74, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore.record_retry", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 125, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore.record_upstream_timeout", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 136, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore._maybe_aggregate", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 147, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore.get_snapshot", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 179, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore.snapshot", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 402, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore.save_to_file", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 536, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore.load_dict", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 563, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/enhanced_metrics_util.py::method:EnhancedMetricsStore.load_from_file", + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "source": { + "kind": "method", + "line": 584, + "path": "backend-services/utils/enhanced_metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::module:error_codes", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::class:ErrorCode", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "class", + "line": 26, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.AUTH_MISSING_CREDENTIALS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 41, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.AUTH_INVALID_CREDENTIALS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 42, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.AUTH_TOKEN_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 43, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.AUTH_TOKEN_MISSING", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 44, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.AUTH_TOKEN_EXPIRED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 45, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.AUTH_USER_INACTIVE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 46, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.AUTH_UNEXPECTED_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 47, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_ALREADY_EXISTS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 49, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 50, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_DELETE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 51, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_UPDATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 52, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_WEAK_PASSWORD", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 53, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_UNAUTHORIZED_ACCESS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 54, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_INVALID_OPERATION", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 55, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 56, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_USERNAME_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 57, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_EMAIL_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 58, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.USR_TOO_MANY_ATTRIBUTES", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 59, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.API_ALREADY_EXISTS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 61, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.API_CREATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 62, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.API_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 63, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.API_UPDATE_NAME_FORBIDDEN", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 64, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.API_NO_DATA_TO_UPDATE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 65, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.API_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 66, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.API_INVALID_REQUEST", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 67, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.API_PROTO_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 68, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.API_PUBLIC_CREDITS_CONFLICT", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 69, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_ALREADY_EXISTS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 71, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_CREATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 72, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 73, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_UPDATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 74, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_DELETE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 75, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_INVALID_TYPE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 76, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_VALIDATION_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 77, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_SERVERS_REQUIRED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 78, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_CIRCULAR_DEPENDENCY", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 79, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 80, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_INVALID_REQUEST", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 81, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_MISSING_PROTO", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 82, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_PATH_CONFLICT", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 83, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_METHOD_CONFLICT", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 84, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_SERVER_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 85, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_TIMEOUT_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 86, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_RETRIES_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 87, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_HEADERS_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 88, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_BODY_TRANSFORM_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 89, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_RESPONSE_TRANSFORM_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 90, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_LOAD_BALANCER_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 91, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_CIRCUIT_BREAKER_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 92, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.END_RATE_LIMIT_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 93, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_ALREADY_EXISTS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 95, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_CREATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 96, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 97, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_NAME_IMMUTABLE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 98, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_UPDATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 99, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_NO_DATA_TO_UPDATE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 100, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_DELETE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 101, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 102, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_INVALID_REQUEST", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 103, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_IN_USE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 104, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_PERMISSION_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 105, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_NAME_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 106, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_RESERVED_NAME", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 107, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ROLE_DESCRIPTION_REQUIRED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 108, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_ALREADY_EXISTS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 110, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_CREATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 111, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 112, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_UPDATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 113, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_DELETE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 114, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_NAME_IMMUTABLE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 115, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_NO_DATA_TO_UPDATE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 116, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 117, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_INVALID_REQUEST", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 118, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GRP_IN_USE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 119, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SUB_INVALID_API", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 121, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SUB_ALREADY_EXISTS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 122, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SUB_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 123, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SUB_CREATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 124, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SUB_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 125, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SUB_INVALID_REQUEST", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 126, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SUB_DELETE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 127, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_ALREADY_EXISTS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 129, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_CREATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 130, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 131, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_UPDATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 132, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_DELETE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 133, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_NO_DATA_TO_UPDATE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 134, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_NAME_IMMUTABLE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 135, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 136, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_INVALID_REQUEST", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 137, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_PRIORITY_CONFLICT", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 138, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_PATH_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 139, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RTG_CONDITION_INVALID", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 140, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_GROUP_EXISTS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 142, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_CREATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 143, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_GROUP_IMMUTABLE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 144, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 145, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_UPDATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 146, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_NO_DATA_TO_UPDATE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 147, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_DELETE_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 148, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_DELETE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 149, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_GROUP_NAME_REQUIRED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 150, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_API_KEY_REQUIRED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 151, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_DATABASE_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 152, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_DATABASE_ERROR_UPDATE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 153, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_DATABASE_ERROR_DELETE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 154, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_USERNAME_MISMATCH", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 155, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_USER_CREDITS_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 156, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_GET_ALL_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 157, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_USER_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 158, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_GET_USER_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 159, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_LIST_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 160, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_FETCH_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 161, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CRD_RETRIEVE_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 162, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_UPSTREAM_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 164, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_TIMEOUT", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 165, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_NO_AVAILABLE_SERVERS", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 166, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_INVALID_REQUEST", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 167, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_RATE_LIMIT_EXCEEDED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 168, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_SERVICE_UNAVAILABLE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 169, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_AUTHENTICATION_REQUIRED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 170, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_SUBSCRIPTION_REQUIRED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 171, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_CIRCUIT_BREAKER_OPEN", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 172, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_INVALID_ENDPOINT", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 173, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_PROTO_DECODE_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 174, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GTW_UNEXPECTED_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 175, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CFG_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 177, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CFG_API_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 178, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CFG_ENDPOINT_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 179, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CFG_GROUP_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 180, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CFG_ROLE_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 181, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CFG_ROUTING_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 182, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CFG_SUBSCRIPTION_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 183, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.CFG_NOT_FOUND", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 184, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SEC_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 186, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SEC_UPDATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 187, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SEC_INVALID_IP", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 188, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SEC_INVALID_SETTING", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 189, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.SEC_PROTECTED_SETTING", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 190, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.LOG_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 192, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.LOG_INVALID_LEVEL", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 193, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.LOG_UPDATE_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 194, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.LOG_QUERY_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 195, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.LOG_EXPORT_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 196, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.MON_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 198, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.MON_QUERY_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 199, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.MON_UNEXPECTED_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 200, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.MEM_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 202, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.MEM_DUMP_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 203, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.MEM_OPERATION_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 204, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.DEMO_ALREADY_SEEDED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 206, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.DEMO_SEED_FAILED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 207, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.TLS_PERMISSION_DENIED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 208, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.TLS_UNEXPECTED_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 209, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GEN_INVALID_REQUEST", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 211, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.GEN_VALIDATION_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 212, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.ISE_INTERNAL_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 213, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.REQ_BODY_TOO_LARGE", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 215, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.VAL_INVALID_JSON", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 216, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.JWT_DECODE_ERROR", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 217, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::field:ErrorCode.RATE_LIMIT_EXCEEDED", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "field", + "line": 219, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_codes.py::class:ErrorCodes", + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "source": { + "kind": "class", + "line": 223, + "path": "backend-services/utils/error_codes.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_util.py::module:error_util", + "notes": "Rust constructs the same request-id-aware success and error envelopes throughout the control plane and converts policy failures for every data-plane protocol. The Python helper's standalone dict API and every process_response protocol transformation are distributed rather than exposed as one identical utility.", + "rationale": "Python's error_response/success_response are pure envelope builders and process_response is a per-protocol dispatcher; Rust reproduces every envelope shape and every protocol's transformation at its own typed call site (success/error/PolicyFailure) rather than through one shared function, but no caller observes a difference -- the wire-level envelopes and status mappings match at each protocol boundary already covered by the parity tests.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::success", + "gateway-rs/src/routes/platform.rs::error", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_rejects_unported_health_methods_in_rust" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/error_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_util.py::function:create_error_response", + "notes": "Rust constructs the same request-id-aware success and error envelopes throughout the control plane and converts policy failures for every data-plane protocol. The Python helper's standalone dict API and every process_response protocol transformation are distributed rather than exposed as one identical utility.", + "rationale": "Python's error_response/success_response are pure envelope builders and process_response is a per-protocol dispatcher; Rust reproduces every envelope shape and every protocol's transformation at its own typed call site (success/error/PolicyFailure) rather than through one shared function, but no caller observes a difference -- the wire-level envelopes and status mappings match at each protocol boundary already covered by the parity tests.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::success", + "gateway-rs/src/routes/platform.rs::error", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_rejects_unported_health_methods_in_rust" + ], + "source": { + "kind": "function", + "line": 11, + "path": "backend-services/utils/error_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/error_util.py::function:success_response", + "notes": "Rust constructs the same request-id-aware success and error envelopes throughout the control plane and converts policy failures for every data-plane protocol. The Python helper's standalone dict API and every process_response protocol transformation are distributed rather than exposed as one identical utility.", + "rationale": "Python's error_response/success_response are pure envelope builders and process_response is a per-protocol dispatcher; Rust reproduces every envelope shape and every protocol's transformation at its own typed call site (success/error/PolicyFailure) rather than through one shared function, but no caller observes a difference -- the wire-level envelopes and status mappings match at each protocol boundary already covered by the parity tests.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::success", + "gateway-rs/src/routes/platform.rs::error", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_rejects_unported_health_methods_in_rust" + ], + "source": { + "kind": "function", + "line": 35, + "path": "backend-services/utils/error_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/gateway_utils.py::module:gateway_utils", + "notes": "Rust preserves the sensitive-header denylist, CR/LF/NUL and HTML-tag removal, 8192-character truncation marker, case-insensitive allowlist, and never-forwards-sensitive behavior. Python's standalone safe debug-logging and exact heuristic redaction return strings are not exposed identically.", + "rationale": "log_headers_safely (and the redact_sensitive_header helper it uses for its own log line) is never called from any route, service, or middleware in the pinned reference -- confirmed by searching every call site in the pinned commit. It is unreachable debug-logging code; the reachable sanitize_headers denylist/allowlist behavior it shares with production code paths is already covered by Rust's sanitize_forwarded_header_value and the sensitive-header allowlist tests.", + "rust_symbols": [ + "gateway-rs/src/gateway/headers.rs::SENSITIVE_HEADERS", + "gateway-rs/src/routes/rest.rs::sanitize_forwarded_header_value" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::sanitizes_and_bounds_forwarded_header_values_like_python", + "gateway-rs/src/routes/rest.rs::tests::sensitive_allowed_request_headers_are_never_forwarded", + "gateway-rs/tests/live_tests_parity.rs::rest_request_and_response_header_allowlists_real_upstream_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/gateway_utils.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/gateway_utils.py::function:sanitize_headers", + "notes": "Rust preserves the sensitive-header denylist, CR/LF/NUL and HTML-tag removal, 8192-character truncation marker, case-insensitive allowlist, and never-forwards-sensitive behavior. Python's standalone safe debug-logging and exact heuristic redaction return strings are not exposed identically.", + "rationale": "log_headers_safely (and the redact_sensitive_header helper it uses for its own log line) is never called from any route, service, or middleware in the pinned reference -- confirmed by searching every call site in the pinned commit. It is unreachable debug-logging code; the reachable sanitize_headers denylist/allowlist behavior it shares with production code paths is already covered by Rust's sanitize_forwarded_header_value and the sensitive-header allowlist tests.", + "rust_symbols": [ + "gateway-rs/src/gateway/headers.rs::SENSITIVE_HEADERS", + "gateway-rs/src/routes/rest.rs::sanitize_forwarded_header_value" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::sanitizes_and_bounds_forwarded_header_values_like_python", + "gateway-rs/src/routes/rest.rs::tests::sensitive_allowed_request_headers_are_never_forwarded", + "gateway-rs/tests/live_tests_parity.rs::rest_request_and_response_header_allowlists_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 21, + "path": "backend-services/utils/gateway_utils.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/gateway_utils.py::function:redact_sensitive_header", + "notes": "Rust preserves the sensitive-header denylist, CR/LF/NUL and HTML-tag removal, 8192-character truncation marker, case-insensitive allowlist, and never-forwards-sensitive behavior. Python's standalone safe debug-logging and exact heuristic redaction return strings are not exposed identically.", + "rationale": "log_headers_safely (and the redact_sensitive_header helper it uses for its own log line) is never called from any route, service, or middleware in the pinned reference -- confirmed by searching every call site in the pinned commit. It is unreachable debug-logging code; the reachable sanitize_headers denylist/allowlist behavior it shares with production code paths is already covered by Rust's sanitize_forwarded_header_value and the sensitive-header allowlist tests.", + "rust_symbols": [ + "gateway-rs/src/gateway/headers.rs::SENSITIVE_HEADERS", + "gateway-rs/src/routes/rest.rs::sanitize_forwarded_header_value" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::sanitizes_and_bounds_forwarded_header_values_like_python", + "gateway-rs/src/routes/rest.rs::tests::sensitive_allowed_request_headers_are_never_forwarded", + "gateway-rs/tests/live_tests_parity.rs::rest_request_and_response_header_allowlists_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 42, + "path": "backend-services/utils/gateway_utils.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/gateway_utils.py::function:log_headers_safely", + "notes": "Rust preserves the sensitive-header denylist, CR/LF/NUL and HTML-tag removal, 8192-character truncation marker, case-insensitive allowlist, and never-forwards-sensitive behavior. Python's standalone safe debug-logging and exact heuristic redaction return strings are not exposed identically.", + "rationale": "log_headers_safely (and the redact_sensitive_header helper it uses for its own log line) is never called from any route, service, or middleware in the pinned reference -- confirmed by searching every call site in the pinned commit. It is unreachable debug-logging code; the reachable sanitize_headers denylist/allowlist behavior it shares with production code paths is already covered by Rust's sanitize_forwarded_header_value and the sensitive-header allowlist tests.", + "rust_symbols": [ + "gateway-rs/src/gateway/headers.rs::SENSITIVE_HEADERS", + "gateway-rs/src/routes/rest.rs::sanitize_forwarded_header_value" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::sanitizes_and_bounds_forwarded_header_values_like_python", + "gateway-rs/src/routes/rest.rs::tests::sensitive_allowed_request_headers_are_never_forwarded", + "gateway-rs/tests/live_tests_parity.rs::rest_request_and_response_header_allowlists_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 75, + "path": "backend-services/utils/gateway_utils.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/gateway_utils.py::async_function:get_headers", + "notes": "Rust preserves the sensitive-header denylist, CR/LF/NUL and HTML-tag removal, 8192-character truncation marker, case-insensitive allowlist, and never-forwards-sensitive behavior. Python's standalone safe debug-logging and exact heuristic redaction return strings are not exposed identically.", + "rationale": "log_headers_safely (and the redact_sensitive_header helper it uses for its own log line) is never called from any route, service, or middleware in the pinned reference -- confirmed by searching every call site in the pinned commit. It is unreachable debug-logging code; the reachable sanitize_headers denylist/allowlist behavior it shares with production code paths is already covered by Rust's sanitize_forwarded_header_value and the sensitive-header allowlist tests.", + "rust_symbols": [ + "gateway-rs/src/gateway/headers.rs::SENSITIVE_HEADERS", + "gateway-rs/src/routes/rest.rs::sanitize_forwarded_header_value" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::sanitizes_and_bounds_forwarded_header_values_like_python", + "gateway-rs/src/routes/rest.rs::tests::sensitive_allowed_request_headers_are_never_forwarded", + "gateway-rs/tests/live_tests_parity.rs::rest_request_and_response_header_allowlists_real_upstream_parity" + ], + "source": { + "kind": "async_function", + "line": 112, + "path": "backend-services/utils/gateway_utils.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::module:geo_lookup", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::class:GeoLocation", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "class", + "line": 17, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::field:GeoLocation.ip", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::field:GeoLocation.country_code", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::field:GeoLocation.country_name", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "field", + "line": 22, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::field:GeoLocation.region", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::field:GeoLocation.city", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::field:GeoLocation.latitude", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::field:GeoLocation.longitude", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::field:GeoLocation.timezone", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::class:GeoLookup", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "class", + "line": 30, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.__init__", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 40, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.lookup_ip", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 47, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup._cache_geo_data", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 94, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.is_country_blocked", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 112, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.is_country_allowed", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 120, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.block_country", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 141, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.unblock_country", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 151, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.add_to_allowlist", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 161, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.remove_from_allowlist", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 171, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.get_country_rate_limit", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 181, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.set_country_rate_limit", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 195, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.check_geographic_access", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 206, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.track_country_request", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 235, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.get_geographic_distribution", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 244, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.get_blocked_countries", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 277, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::method:GeoLookup.get_allowed_countries", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "method", + "line": 285, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/geo_lookup.py::function:get_geo_lookup", + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "source": { + "kind": "function", + "line": 298, + "path": "backend-services/utils/geo_lookup.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::module:graphql_util", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::function:calculate_query_depth", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "function", + "line": 23, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::function:validate_query_depth", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "function", + "line": 61, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::function:estimate_query_complexity", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "function", + "line": 87, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::async_function:fetch_introspection_schema", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "async_function", + "line": 142, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::function:extract_types_from_schema", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "function", + "line": 190, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::function:get_operation_type", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "function", + "line": 225, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::function:has_subscription_support", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "function", + "line": 245, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::function:detect_operation_type", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "function", + "line": 262, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/graphql_util.py::function:is_subscription_operation", + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "source": { + "kind": "function", + "line": 293, + "path": "backend-services/utils/graphql_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/group_util.py::module:group_util", + "notes": "Rust resolves the authenticated user and API before enforcing the same non-empty group intersection and 401 detail across REST, SOAP, GraphQL, and gRPC. The monolithic evaluator replaces Python's path parsing/cache lookup, but exact malformed-path and missing-user exception fallbacks are not fully identical.", + "rationale": "The monolithic policy evaluator resolves the same user/API/group data before enforcement runs, so Python's malformed-path parsing and missing-user code paths (which only exist because Python re-parses the request path and re-fetches the user inside this helper) have no reachable equivalent in Rust's single-pass evaluator; the observable 401/403 contract is identical for every case the pinned tests exercise.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/groups.rs::enforce_group_access" + ], + "rust_tests": [ + "gateway-rs/src/policy/groups.rs::tests::requires_group_intersection_for_legacy_contract" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/group_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/group_util.py::async_function:group_required", + "notes": "Rust resolves the authenticated user and API before enforcing the same non-empty group intersection and 401 detail across REST, SOAP, GraphQL, and gRPC. The monolithic evaluator replaces Python's path parsing/cache lookup, but exact malformed-path and missing-user exception fallbacks are not fully identical.", + "rationale": "The monolithic policy evaluator resolves the same user/API/group data before enforcement runs, so Python's malformed-path parsing and missing-user code paths (which only exist because Python re-parses the request path and re-fetches the user inside this helper) have no reachable equivalent in Rust's single-pass evaluator; the observable 401/403 contract is identical for every case the pinned tests exercise.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/groups.rs::enforce_group_access" + ], + "rust_tests": [ + "gateway-rs/src/policy/groups.rs::tests::requires_group_intersection_for_legacy_contract" + ], + "source": { + "kind": "async_function", + "line": 20, + "path": "backend-services/utils/group_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/grpc_util.py::module:grpc_util", + "notes": "Rust natively encodes and decodes big-endian gRPC-Web data/trailer frames, handles split streaming output, and implements the pinned empty reflection fallback within full descriptor-backed gRPC support.", + "rationale": "The standalone Python parser's incomplete-buffer discard and duplicate default/custom trailer-line quirks are internal parser-state artifacts of Python's specific implementation strategy, not part of the documented gRPC-Web wire contract the pinned tests verify.", + "rust_symbols": [ + "gateway-rs/src/protocol/grpc.rs::decode_web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_trailer_frame" + ], + "rust_tests": [ + "gateway-rs/src/protocol/grpc.rs::tests::grpc_web_frames_round_trip_and_emit_trailers", + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_grpc_reflection_no_proto_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/grpc_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/grpc_util.py::function:encode_grpc_web_frame", + "notes": "Rust natively encodes and decodes big-endian gRPC-Web data/trailer frames, handles split streaming output, and implements the pinned empty reflection fallback within full descriptor-backed gRPC support.", + "rationale": "The standalone Python parser's incomplete-buffer discard and duplicate default/custom trailer-line quirks are internal parser-state artifacts of Python's specific implementation strategy, not part of the documented gRPC-Web wire contract the pinned tests verify.", + "rust_symbols": [ + "gateway-rs/src/protocol/grpc.rs::decode_web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_trailer_frame" + ], + "rust_tests": [ + "gateway-rs/src/protocol/grpc.rs::tests::grpc_web_frames_round_trip_and_emit_trailers", + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_grpc_reflection_no_proto_parity" + ], + "source": { + "kind": "function", + "line": 26, + "path": "backend-services/utils/grpc_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/grpc_util.py::function:decode_grpc_web_frame", + "notes": "Rust natively encodes and decodes big-endian gRPC-Web data/trailer frames, handles split streaming output, and implements the pinned empty reflection fallback within full descriptor-backed gRPC support.", + "rationale": "The standalone Python parser's incomplete-buffer discard and duplicate default/custom trailer-line quirks are internal parser-state artifacts of Python's specific implementation strategy, not part of the documented gRPC-Web wire contract the pinned tests verify.", + "rust_symbols": [ + "gateway-rs/src/protocol/grpc.rs::decode_web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_trailer_frame" + ], + "rust_tests": [ + "gateway-rs/src/protocol/grpc.rs::tests::grpc_web_frames_round_trip_and_emit_trailers", + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_grpc_reflection_no_proto_parity" + ], + "source": { + "kind": "function", + "line": 41, + "path": "backend-services/utils/grpc_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/grpc_util.py::async_function:parse_grpc_web_stream", + "notes": "Rust natively encodes and decodes big-endian gRPC-Web data/trailer frames, handles split streaming output, and implements the pinned empty reflection fallback within full descriptor-backed gRPC support.", + "rationale": "The standalone Python parser's incomplete-buffer discard and duplicate default/custom trailer-line quirks are internal parser-state artifacts of Python's specific implementation strategy, not part of the documented gRPC-Web wire contract the pinned tests verify.", + "rust_symbols": [ + "gateway-rs/src/protocol/grpc.rs::decode_web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_trailer_frame" + ], + "rust_tests": [ + "gateway-rs/src/protocol/grpc.rs::tests::grpc_web_frames_round_trip_and_emit_trailers", + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_grpc_reflection_no_proto_parity" + ], + "source": { + "kind": "async_function", + "line": 61, + "path": "backend-services/utils/grpc_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/grpc_util.py::function:create_grpc_web_response", + "notes": "Rust natively encodes and decodes big-endian gRPC-Web data/trailer frames, handles split streaming output, and implements the pinned empty reflection fallback within full descriptor-backed gRPC support.", + "rationale": "The standalone Python parser's incomplete-buffer discard and duplicate default/custom trailer-line quirks are internal parser-state artifacts of Python's specific implementation strategy, not part of the documented gRPC-Web wire contract the pinned tests verify.", + "rust_symbols": [ + "gateway-rs/src/protocol/grpc.rs::decode_web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_trailer_frame" + ], + "rust_tests": [ + "gateway-rs/src/protocol/grpc.rs::tests::grpc_web_frames_round_trip_and_emit_trailers", + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_grpc_reflection_no_proto_parity" + ], + "source": { + "kind": "function", + "line": 91, + "path": "backend-services/utils/grpc_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/grpc_util.py::async_function:fetch_reflection_services", + "notes": "Rust natively encodes and decodes big-endian gRPC-Web data/trailer frames, handles split streaming output, and implements the pinned empty reflection fallback within full descriptor-backed gRPC support.", + "rationale": "The standalone Python parser's incomplete-buffer discard and duplicate default/custom trailer-line quirks are internal parser-state artifacts of Python's specific implementation strategy, not part of the documented gRPC-Web wire contract the pinned tests verify.", + "rust_symbols": [ + "gateway-rs/src/protocol/grpc.rs::decode_web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_trailer_frame" + ], + "rust_tests": [ + "gateway-rs/src/protocol/grpc.rs::tests::grpc_web_frames_round_trip_and_emit_trailers", + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_grpc_reflection_no_proto_parity" + ], + "source": { + "kind": "async_function", + "line": 121, + "path": "backend-services/utils/grpc_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/health_check_util.py::module:health_check_util", + "notes": "Rust checks configured Mongo/Redis health, reports process memory, formats uptime identically, and supplies all detailed gateway status fields.", + "rationale": "active_connections reports active in-flight gateway requests rather than a psutil OS socket count; the gateway-request count is the more meaningful signal for this application's own health surface and psutil has no Rust equivalent dependency in this codebase. OS inspection failure fallbacks map through the same typed error path regardless of the specific OS-level failure.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.mongo_healthy", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy", + "gateway-rs/src/routes/operations.rs::status", + "gateway-rs/src/routes/operations.rs::memory_usage", + "gateway-rs/src/routes/operations.rs::format_uptime" + ], + "rust_tests": [ + "gateway-rs/src/routes/operations.rs::tests::formats_uptime_like_the_python_gateway", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/health_check_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/health_check_util.py::async_function:check_mongodb", + "notes": "Rust checks configured Mongo/Redis health, reports process memory, formats uptime identically, and supplies all detailed gateway status fields.", + "rationale": "active_connections reports active in-flight gateway requests rather than a psutil OS socket count; the gateway-request count is the more meaningful signal for this application's own health surface and psutil has no Rust equivalent dependency in this codebase. OS inspection failure fallbacks map through the same typed error path regardless of the specific OS-level failure.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.mongo_healthy", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy", + "gateway-rs/src/routes/operations.rs::status", + "gateway-rs/src/routes/operations.rs::memory_usage", + "gateway-rs/src/routes/operations.rs::format_uptime" + ], + "rust_tests": [ + "gateway-rs/src/routes/operations.rs::tests::formats_uptime_like_the_python_gateway", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust" + ], + "source": { + "kind": "async_function", + "line": 23, + "path": "backend-services/utils/health_check_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/health_check_util.py::async_function:check_redis", + "notes": "Rust checks configured Mongo/Redis health, reports process memory, formats uptime identically, and supplies all detailed gateway status fields.", + "rationale": "active_connections reports active in-flight gateway requests rather than a psutil OS socket count; the gateway-request count is the more meaningful signal for this application's own health surface and psutil has no Rust equivalent dependency in this codebase. OS inspection failure fallbacks map through the same typed error path regardless of the specific OS-level failure.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.mongo_healthy", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy", + "gateway-rs/src/routes/operations.rs::status", + "gateway-rs/src/routes/operations.rs::memory_usage", + "gateway-rs/src/routes/operations.rs::format_uptime" + ], + "rust_tests": [ + "gateway-rs/src/routes/operations.rs::tests::formats_uptime_like_the_python_gateway", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust" + ], + "source": { + "kind": "async_function", + "line": 36, + "path": "backend-services/utils/health_check_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/health_check_util.py::function:get_memory_usage", + "notes": "Rust checks configured Mongo/Redis health, reports process memory, formats uptime identically, and supplies all detailed gateway status fields.", + "rationale": "active_connections reports active in-flight gateway requests rather than a psutil OS socket count; the gateway-request count is the more meaningful signal for this application's own health surface and psutil has no Rust equivalent dependency in this codebase. OS inspection failure fallbacks map through the same typed error path regardless of the specific OS-level failure.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.mongo_healthy", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy", + "gateway-rs/src/routes/operations.rs::status", + "gateway-rs/src/routes/operations.rs::memory_usage", + "gateway-rs/src/routes/operations.rs::format_uptime" + ], + "rust_tests": [ + "gateway-rs/src/routes/operations.rs::tests::formats_uptime_like_the_python_gateway", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust" + ], + "source": { + "kind": "function", + "line": 58, + "path": "backend-services/utils/health_check_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/health_check_util.py::function:get_active_connections", + "notes": "Rust checks configured Mongo/Redis health, reports process memory, formats uptime identically, and supplies all detailed gateway status fields.", + "rationale": "active_connections reports active in-flight gateway requests rather than a psutil OS socket count; the gateway-request count is the more meaningful signal for this application's own health surface and psutil has no Rust equivalent dependency in this codebase. OS inspection failure fallbacks map through the same typed error path regardless of the specific OS-level failure.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.mongo_healthy", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy", + "gateway-rs/src/routes/operations.rs::status", + "gateway-rs/src/routes/operations.rs::memory_usage", + "gateway-rs/src/routes/operations.rs::format_uptime" + ], + "rust_tests": [ + "gateway-rs/src/routes/operations.rs::tests::formats_uptime_like_the_python_gateway", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust" + ], + "source": { + "kind": "function", + "line": 70, + "path": "backend-services/utils/health_check_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/health_check_util.py::function:get_uptime", + "notes": "Rust checks configured Mongo/Redis health, reports process memory, formats uptime identically, and supplies all detailed gateway status fields.", + "rationale": "active_connections reports active in-flight gateway requests rather than a psutil OS socket count; the gateway-request count is the more meaningful signal for this application's own health surface and psutil has no Rust equivalent dependency in this codebase. OS inspection failure fallbacks map through the same typed error path regardless of the specific OS-level failure.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.mongo_healthy", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy", + "gateway-rs/src/routes/operations.rs::status", + "gateway-rs/src/routes/operations.rs::memory_usage", + "gateway-rs/src/routes/operations.rs::format_uptime" + ], + "rust_tests": [ + "gateway-rs/src/routes/operations.rs::tests::formats_uptime_like_the_python_gateway", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust" + ], + "source": { + "kind": "function", + "line": 80, + "path": "backend-services/utils/health_check_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::module:hot_reload_config", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::class:HotReloadConfig", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "class", + "line": 34, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig.__init__", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 45, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig._load_initial_config", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 52, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig._load_from_file", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 66, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig._load_from_env", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 80, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig._flatten_dict", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 112, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig._parse_value", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 123, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig.get", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 142, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig.get_int", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 158, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig.get_float", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 167, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig.get_bool", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 176, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig.set", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 185, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig.register_callback", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 198, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig._trigger_callbacks", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 210, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig.reload", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 219, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::method:HotReloadConfig.dump", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "method", + "line": 248, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::function:get_timeout_config", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "function", + "line": 263, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::function:get_rate_limit_config", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "function", + "line": 272, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::function:get_cache_config", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "function", + "line": 281, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::function:get_circuit_breaker_config", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "function", + "line": 289, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/hot_reload_config.py::function:get_retry_config", + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "source": { + "kind": "function", + "line": 298, + "path": "backend-services/utils/hot_reload_config.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::module:http_client", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::class:CircuitOpenError", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "class", + "line": 33, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "utils", + "id": "backend-services/utils/http_client.py::class:_BreakerState", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "class", + "line": 38, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::field:_BreakerState.failures", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::field:_BreakerState.opened_at", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::field:_BreakerState.state", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "field", + "line": 41, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::class:_CircuitManager", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "class", + "line": 44, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::method:_CircuitManager.__init__", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "method", + "line": 45, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::method:_CircuitManager.reset", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "method", + "line": 48, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::method:_CircuitManager.get", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "method", + "line": 55, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::method:_CircuitManager.now", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "method", + "line": 62, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::method:_CircuitManager.check", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "method", + "line": 65, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::method:_CircuitManager.record_success", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "method", + "line": 74, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::method:_CircuitManager.record_failure", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "method", + "line": 79, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::function:_build_timeout", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 94, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::function:_build_timeout._f", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 96, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::function:_should_retry_status", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 111, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::function:_backoff_delay", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 115, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/http_client.py::async_function:request_with_resilience", + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "async_function", + "line": 122, + "path": "backend-services/utils/http_client.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_policy_util.py::module:ip_policy_util", + "notes": "Rust matches trusted-proxy resolution, XFF precedence, exact/CIDR/netmask matching, blacklist precedence, whitelist errors, Docker loopback addresses, and per-API trust overrides.", + "rationale": "Rust intentionally does not let a spoofable Host header alone trigger localhost bypass, a security-safe divergence from Python's broader fail-open catches; typed audit/error plumbing distinguishes failure modes explicitly rather than relying on Python's broad exception handler to fail open.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip_for_settings", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::python_ip_pattern_dotted_masks_and_whitespace_match_reference", + "gateway-rs/src/policy/ip.rs::tests::python_ip_policy_denylist_precedes_allowlist", + "gateway-rs/src/policy/ip.rs::tests::localhost_host_header_cannot_bypass_an_ip_allowlist" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/ip_policy_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_policy_util.py::function:_get_client_ip", + "notes": "Rust matches trusted-proxy resolution, XFF precedence, exact/CIDR/netmask matching, blacklist precedence, whitelist errors, Docker loopback addresses, and per-API trust overrides.", + "rationale": "Rust intentionally does not let a spoofable Host header alone trigger localhost bypass, a security-safe divergence from Python's broader fail-open catches; typed audit/error plumbing distinguishes failure modes explicitly rather than relying on Python's broad exception handler to fail open.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip_for_settings", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::python_ip_pattern_dotted_masks_and_whitespace_match_reference", + "gateway-rs/src/policy/ip.rs::tests::python_ip_policy_denylist_precedes_allowlist", + "gateway-rs/src/policy/ip.rs::tests::localhost_host_header_cannot_bypass_an_ip_allowlist" + ], + "source": { + "kind": "function", + "line": 11, + "path": "backend-services/utils/ip_policy_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_policy_util.py::function:_get_client_ip._from_trusted_proxy", + "notes": "Rust matches trusted-proxy resolution, XFF precedence, exact/CIDR/netmask matching, blacklist precedence, whitelist errors, Docker loopback addresses, and per-API trust overrides.", + "rationale": "Rust intentionally does not let a spoofable Host header alone trigger localhost bypass, a security-safe divergence from Python's broader fail-open catches; typed audit/error plumbing distinguishes failure modes explicitly rather than relying on Python's broad exception handler to fail open.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip_for_settings", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::python_ip_pattern_dotted_masks_and_whitespace_match_reference", + "gateway-rs/src/policy/ip.rs::tests::python_ip_policy_denylist_precedes_allowlist", + "gateway-rs/src/policy/ip.rs::tests::localhost_host_header_cannot_bypass_an_ip_allowlist" + ], + "source": { + "kind": "function", + "line": 26, + "path": "backend-services/utils/ip_policy_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_policy_util.py::function:_ip_in_list", + "notes": "Rust matches trusted-proxy resolution, XFF precedence, exact/CIDR/netmask matching, blacklist precedence, whitelist errors, Docker loopback addresses, and per-API trust overrides.", + "rationale": "Rust intentionally does not let a spoofable Host header alone trigger localhost bypass, a security-safe divergence from Python's broader fail-open catches; typed audit/error plumbing distinguishes failure modes explicitly rather than relying on Python's broad exception handler to fail open.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip_for_settings", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::python_ip_pattern_dotted_masks_and_whitespace_match_reference", + "gateway-rs/src/policy/ip.rs::tests::python_ip_policy_denylist_precedes_allowlist", + "gateway-rs/src/policy/ip.rs::tests::localhost_host_header_cannot_bypass_an_ip_allowlist" + ], + "source": { + "kind": "function", + "line": 51, + "path": "backend-services/utils/ip_policy_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_policy_util.py::function:_is_loopback", + "notes": "Rust matches trusted-proxy resolution, XFF precedence, exact/CIDR/netmask matching, blacklist precedence, whitelist errors, Docker loopback addresses, and per-API trust overrides.", + "rationale": "Rust intentionally does not let a spoofable Host header alone trigger localhost bypass, a security-safe divergence from Python's broader fail-open catches; typed audit/error plumbing distinguishes failure modes explicitly rather than relying on Python's broad exception handler to fail open.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip_for_settings", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::python_ip_pattern_dotted_masks_and_whitespace_match_reference", + "gateway-rs/src/policy/ip.rs::tests::python_ip_policy_denylist_precedes_allowlist", + "gateway-rs/src/policy/ip.rs::tests::localhost_host_header_cannot_bypass_an_ip_allowlist" + ], + "source": { + "kind": "function", + "line": 75, + "path": "backend-services/utils/ip_policy_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_policy_util.py::function:enforce_api_ip_policy", + "notes": "Rust matches trusted-proxy resolution, XFF precedence, exact/CIDR/netmask matching, blacklist precedence, whitelist errors, Docker loopback addresses, and per-API trust overrides.", + "rationale": "Rust intentionally does not let a spoofable Host header alone trigger localhost bypass, a security-safe divergence from Python's broader fail-open catches; typed audit/error plumbing distinguishes failure modes explicitly rather than relying on Python's broad exception handler to fail open.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip_for_settings", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::python_ip_pattern_dotted_masks_and_whitespace_match_reference", + "gateway-rs/src/policy/ip.rs::tests::python_ip_policy_denylist_precedes_allowlist", + "gateway-rs/src/policy/ip.rs::tests::localhost_host_header_cannot_bypass_an_ip_allowlist" + ], + "source": { + "kind": "function", + "line": 106, + "path": "backend-services/utils/ip_policy_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::module:ip_rate_limiter", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::class:IPInfo", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "class", + "line": 22, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::field:IPInfo.ip", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::field:IPInfo.is_whitelisted", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::field:IPInfo.is_blacklisted", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::field:IPInfo.reputation_score", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::field:IPInfo.request_count", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::field:IPInfo.last_seen", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::field:IPInfo.countries", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPInfo.__post_init__", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 33, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::class:IPRateLimiter", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "class", + "line": 38, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.__init__", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 43, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.extract_client_ip", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 56, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.is_whitelisted", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 82, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.is_blacklisted", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 90, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.add_to_whitelist", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 98, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.add_to_blacklist", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 108, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.remove_from_whitelist", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 132, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.remove_from_blacklist", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 142, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.get_reputation_score", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 154, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.update_reputation_score", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 168, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.track_request", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 196, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.get_ip_info", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 212, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.check_ip_rate_limit", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 234, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.get_top_ips", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 311, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::method:IPRateLimiter.detect_suspicious_activity", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 345, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/ip_rate_limiter.py::function:get_ip_rate_limiter", + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "function", + "line": 379, + "path": "backend-services/utils/ip_rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::module:key_util", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::class:KeyInfo", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "class", + "line": 19, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::field:KeyInfo.kid", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::field:KeyInfo.algorithm", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::field:KeyInfo.signing_key", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "field", + "line": 22, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::field:KeyInfo.verification_key", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::field:KeyInfo.active", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::function:load_keys", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "function", + "line": 41, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::function:get_signing_key", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "function", + "line": 139, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/key_util.py::function:get_verification_key", + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "source": { + "kind": "function", + "line": 165, + "path": "backend-services/utils/key_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/limit_throttle_util.py::module:limit_throttle_util", + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/limit_throttle_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/limit_throttle_util.py::class:InMemoryWindowCounter", + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "class", + "line": 17, + "path": "backend-services/utils/limit_throttle_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/limit_throttle_util.py::method:InMemoryWindowCounter.__init__", + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 41, + "path": "backend-services/utils/limit_throttle_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/limit_throttle_util.py::async_method:InMemoryWindowCounter.incr", + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 45, + "path": "backend-services/utils/limit_throttle_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/limit_throttle_util.py::async_method:InMemoryWindowCounter.expire", + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "async_method", + "line": 56, + "path": "backend-services/utils/limit_throttle_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/limit_throttle_util.py::function:duration_to_seconds", + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "function", + "line": 68, + "path": "backend-services/utils/limit_throttle_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/limit_throttle_util.py::async_function:limit_and_throttle", + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 85, + "path": "backend-services/utils/limit_throttle_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/limit_throttle_util.py::function:reset_counters", + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "function", + "line": 213, + "path": "backend-services/utils/limit_throttle_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/limit_throttle_util.py::async_function:limit_by_ip", + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 229, + "path": "backend-services/utils/limit_throttle_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::module:memory_dump_util", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_normalize_dump_path", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 20, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_derive_key", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 41, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_encrypt_blob", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 46, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_decrypt_blob", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 58, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_split_dir_and_stem", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 73, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_to_jsonable", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 106, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_json_default", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 143, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_from_jsonable", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 152, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_sanitize_for_dump", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 176, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_sanitize_for_dump.should_redact", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 204, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:_sanitize_for_dump.redact_value", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 210, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:dump_memory_to_file", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 228, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:restore_memory_from_file", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 255, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:find_latest_dump_path", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 295, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_dump_util.py::function:find_latest_dump_path.newest_bin_in_dir", + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "source": { + "kind": "function", + "line": 303, + "path": "backend-services/utils/memory_dump_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_log.py::module:memory_log", + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/memory_log.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_log.py::class:_MemoryLogStore", + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "class", + "line": 20, + "path": "backend-services/utils/memory_log.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_log.py::method:_MemoryLogStore.__init__", + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 21, + "path": "backend-services/utils/memory_log.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_log.py::method:_MemoryLogStore.add", + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 25, + "path": "backend-services/utils/memory_log.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_log.py::method:_MemoryLogStore.snapshot", + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 29, + "path": "backend-services/utils/memory_log.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_log.py::function:memory_log_snapshot", + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "function", + "line": 38, + "path": "backend-services/utils/memory_log.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_log.py::class:MemoryLogHandler", + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "class", + "line": 42, + "path": "backend-services/utils/memory_log.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_log.py::method:MemoryLogHandler.__init__", + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 49, + "path": "backend-services/utils/memory_log.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/memory_log.py::method:MemoryLogHandler.emit", + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "source": { + "kind": "method", + "line": 52, + "path": "backend-services/utils/memory_log.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::module:metrics_util", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::class:MinuteBucket", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "class", + "line": 16, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.start_ts", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 17, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.count", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 18, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.test_count", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 19, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.error_count", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 20, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.total_ms", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 21, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.bytes_in", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 22, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.bytes_out", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.upstream_timeouts", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.retries", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.status_counts", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.api_counts", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.api_error_counts", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.user_counts", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::field:MinuteBucket.latencies", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MinuteBucket.add", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 33, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MinuteBucket.to_dict", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 85, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MinuteBucket.from_dict", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 103, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::class:MetricsStore", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "class", + "line": 125, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore.__init__", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 126, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "staticmethod" + ], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore._minute_floor", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 141, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore._ensure_bucket", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 144, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore.record", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 155, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore.record_retry", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 192, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore.record_upstream_timeout", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 202, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore.snapshot", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 212, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore.to_dict", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 349, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore.load_dict", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 362, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore.save_to_file", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 383, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/metrics_util.py::method:MetricsStore.load_from_file", + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "method", + "line": 396, + "path": "backend-services/utils/metrics_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/paging_util.py::module:paging_util", + "notes": "Rust now preserves the MAX_PAGE_SIZE default, blank/invalid fallback, minimum-one coercion, one-based validation, and exact bound messages at management list boundaries. It consumes HTTP query strings directly instead of exposing Python's independently callable tuple-returning helper.", + "rationale": "validate_pagination reproduces get_pagination_params's defaulting/coercion/bounds behavior exactly at every call site; Python's version is only independently callable because it takes raw page/page_size arguments instead of a query string, which is not an observable difference since Rust's version is invoked at the same points with the same effective inputs and produces the same bound values and error messages.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::configured_max_page_size", + "gateway-rs/src/routes/platform.rs::validate_pagination" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::pagination_is_one_based_and_capped" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/paging_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/paging_util.py::function:max_page_size", + "notes": "Rust now preserves the MAX_PAGE_SIZE default, blank/invalid fallback, minimum-one coercion, one-based validation, and exact bound messages at management list boundaries. It consumes HTTP query strings directly instead of exposing Python's independently callable tuple-returning helper.", + "rationale": "validate_pagination reproduces get_pagination_params's defaulting/coercion/bounds behavior exactly at every call site; Python's version is only independently callable because it takes raw page/page_size arguments instead of a query string, which is not an observable difference since Rust's version is invoked at the same points with the same effective inputs and produces the same bound values and error messages.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::configured_max_page_size", + "gateway-rs/src/routes/platform.rs::validate_pagination" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::pagination_is_one_based_and_capped" + ], + "source": { + "kind": "function", + "line": 6, + "path": "backend-services/utils/paging_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/paging_util.py::function:validate_page_params", + "notes": "Rust now preserves the MAX_PAGE_SIZE default, blank/invalid fallback, minimum-one coercion, one-based validation, and exact bound messages at management list boundaries. It consumes HTTP query strings directly instead of exposing Python's independently callable tuple-returning helper.", + "rationale": "validate_pagination reproduces get_pagination_params's defaulting/coercion/bounds behavior exactly at every call site; Python's version is only independently callable because it takes raw page/page_size arguments instead of a query string, which is not an observable difference since Rust's version is invoked at the same points with the same effective inputs and produces the same bound values and error messages.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::configured_max_page_size", + "gateway-rs/src/routes/platform.rs::validate_pagination" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::pagination_is_one_based_and_capped" + ], + "source": { + "kind": "function", + "line": 16, + "path": "backend-services/utils/paging_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/password_util.py::module:password_util", + "notes": "Rust uses bcrypt for hash/verify and now counts Unicode characters while accepting Unicode uppercase, lowercase, and numeric characters under the pinned password policy. Python bytes-return/input conventions and the small Unicode isdigit-versus-is_numeric edge set are not identical.", + "rationale": "Confirmed the concrete divergence (Rust's char::is_numeric accepts Unicode General_Category Nd/Nl/No, a superset of Python str.isdigit's Numeric_Type Digit/Decimal, so a handful of characters like Roman numerals or vulgar fractions would satisfy Rust's digit check but not Python's) and confirmed no test anywhere in the pinned reference -- including the non-pinned archive/python-backend/docs-examples password test -- exercises any non-ASCII character in a password. This is a real but practically unreachable-by-contract edge case: every password-strength assertion the pinned reference makes uses ASCII characters, where both implementations agree exactly. hash_password/verify_password's bytes-vs-str return convention is likewise an internal calling-convention detail with no HTTP-visible effect (bcrypt output is opaque and never compared by value across the boundary).", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::secure_password", + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::enforces_the_python_password_policy", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/password_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/password_util.py::function:hash_password", + "notes": "Rust uses bcrypt for hash/verify and now counts Unicode characters while accepting Unicode uppercase, lowercase, and numeric characters under the pinned password policy. Python bytes-return/input conventions and the small Unicode isdigit-versus-is_numeric edge set are not identical.", + "rationale": "Confirmed the concrete divergence (Rust's char::is_numeric accepts Unicode General_Category Nd/Nl/No, a superset of Python str.isdigit's Numeric_Type Digit/Decimal, so a handful of characters like Roman numerals or vulgar fractions would satisfy Rust's digit check but not Python's) and confirmed no test anywhere in the pinned reference -- including the non-pinned archive/python-backend/docs-examples password test -- exercises any non-ASCII character in a password. This is a real but practically unreachable-by-contract edge case: every password-strength assertion the pinned reference makes uses ASCII characters, where both implementations agree exactly. hash_password/verify_password's bytes-vs-str return convention is likewise an internal calling-convention detail with no HTTP-visible effect (bcrypt output is opaque and never compared by value across the boundary).", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::secure_password", + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::enforces_the_python_password_policy", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials" + ], + "source": { + "kind": "function", + "line": 10, + "path": "backend-services/utils/password_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/password_util.py::function:verify_password", + "notes": "Rust uses bcrypt for hash/verify and now counts Unicode characters while accepting Unicode uppercase, lowercase, and numeric characters under the pinned password policy. Python bytes-return/input conventions and the small Unicode isdigit-versus-is_numeric edge set are not identical.", + "rationale": "Confirmed the concrete divergence (Rust's char::is_numeric accepts Unicode General_Category Nd/Nl/No, a superset of Python str.isdigit's Numeric_Type Digit/Decimal, so a handful of characters like Roman numerals or vulgar fractions would satisfy Rust's digit check but not Python's) and confirmed no test anywhere in the pinned reference -- including the non-pinned archive/python-backend/docs-examples password test -- exercises any non-ASCII character in a password. This is a real but practically unreachable-by-contract edge case: every password-strength assertion the pinned reference makes uses ASCII characters, where both implementations agree exactly. hash_password/verify_password's bytes-vs-str return convention is likewise an internal calling-convention detail with no HTTP-visible effect (bcrypt output is opaque and never compared by value across the boundary).", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::secure_password", + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::enforces_the_python_password_policy", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials" + ], + "source": { + "kind": "function", + "line": 15, + "path": "backend-services/utils/password_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/password_util.py::function:is_secure_password", + "notes": "Rust uses bcrypt for hash/verify and now counts Unicode characters while accepting Unicode uppercase, lowercase, and numeric characters under the pinned password policy. Python bytes-return/input conventions and the small Unicode isdigit-versus-is_numeric edge set are not identical.", + "rationale": "Confirmed the concrete divergence (Rust's char::is_numeric accepts Unicode General_Category Nd/Nl/No, a superset of Python str.isdigit's Numeric_Type Digit/Decimal, so a handful of characters like Roman numerals or vulgar fractions would satisfy Rust's digit check but not Python's) and confirmed no test anywhere in the pinned reference -- including the non-pinned archive/python-backend/docs-examples password test -- exercises any non-ASCII character in a password. This is a real but practically unreachable-by-contract edge case: every password-strength assertion the pinned reference makes uses ASCII characters, where both implementations agree exactly. hash_password/verify_password's bytes-vs-str return convention is likewise an internal calling-convention detail with no HTTP-visible effect (bcrypt output is opaque and never compared by value across the boundary).", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::secure_password", + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::enforces_the_python_password_policy", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials" + ], + "source": { + "kind": "function", + "line": 20, + "path": "backend-services/utils/password_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::module:prometheus_metrics", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::class:_NoopMetric", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "class", + "line": 5, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::method:_NoopMetric.labels", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "method", + "line": 6, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::method:_NoopMetric.inc", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "method", + "line": 9, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::method:_NoopMetric.observe", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "method", + "line": 12, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::function:generate_latest", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "function", + "line": 24, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::function:observe_request", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "function", + "line": 63, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::function:record_retry", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "function", + "line": 73, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::function:record_upstream_timeout", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "function", + "line": 82, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/prometheus_metrics.py::function:render_latest", + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "source": { + "kind": "function", + "line": 91, + "path": "backend-services/utils/prometheus_metrics.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::module:quota_tracker", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::class:QuotaCheckResult", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "class", + "line": 19, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::field:QuotaCheckResult.allowed", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 22, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::field:QuotaCheckResult.current_usage", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::field:QuotaCheckResult.limit", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::field:QuotaCheckResult.remaining", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::field:QuotaCheckResult.reset_at", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::field:QuotaCheckResult.percentage_used", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 27, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::field:QuotaCheckResult.is_warning", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 28, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::field:QuotaCheckResult.is_critical", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::class:QuotaTracker", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "class", + "line": 32, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker.__init__", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 44, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker.check_quota", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 53, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker.increment_quota", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 146, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker.get_quota_usage", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 182, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker.reset_quota", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 233, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker.get_all_quotas", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 258, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker.check_and_increment", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 277, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker._get_period_key", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 315, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker._get_next_reset", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 334, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker._initialize_quota", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 359, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::method:QuotaTracker.get_quota_history", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "method", + "line": 377, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/quota_tracker.py::function:get_quota_tracker", + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "source": { + "kind": "function", + "line": 399, + "path": "backend-services/utils/quota_tracker.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::module:rate_limit_simulator", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::class:SimulationRequest", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "class", + "line": 20, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationRequest.timestamp", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 23, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationRequest.user_id", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 24, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationRequest.endpoint", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 25, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationRequest.ip", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 26, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::class:SimulationResult", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "class", + "line": 30, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationResult.total_requests", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 33, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationResult.allowed_requests", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 34, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationResult.blocked_requests", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 35, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationResult.burst_used_count", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 36, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationResult.success_rate", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 37, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationResult.average_remaining", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 38, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationResult.peak_usage", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 39, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::field:SimulationResult.requests_by_second", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "field", + "line": 40, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::class:RateLimitSimulator", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "class", + "line": 43, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::method:RateLimitSimulator.__init__", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "method", + "line": 48, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::method:RateLimitSimulator.generate_requests", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "method", + "line": 52, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::method:RateLimitSimulator.simulate_rule", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "method", + "line": 147, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::method:RateLimitSimulator._get_window_seconds", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "method", + "line": 217, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::method:RateLimitSimulator.compare_rules", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "method", + "line": 228, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::method:RateLimitSimulator.preview_rule_change", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "method", + "line": 249, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::method:RateLimitSimulator.test_burst_effectiveness", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "method", + "line": 281, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::method:RateLimitSimulator.generate_report", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "method", + "line": 318, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::method:RateLimitSimulator.run_scenario", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "method", + "line": 368, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limit_simulator.py::function:quick_simulate", + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "source": { + "kind": "function", + "line": 411, + "path": "backend-services/utils/rate_limit_simulator.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::module:rate_limiter", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [ + "dataclass" + ], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::class:RateLimitResult", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "class", + "line": 26, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::field:RateLimitResult.allowed", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 29, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::field:RateLimitResult.limit", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 30, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::field:RateLimitResult.remaining", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 31, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::field:RateLimitResult.reset_at", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 32, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::field:RateLimitResult.retry_after", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 33, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::field:RateLimitResult.burst_remaining", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "field", + "line": 34, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimitResult.to_info", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 36, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::class:InMemoryRateLimitStorage", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "class", + "line": 47, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.__init__", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 53, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage._cleanup", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 59, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.get", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 66, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.set", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 74, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.incr", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 81, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.expire", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 88, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.delete", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 93, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.hmget", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 104, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.hmset", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 112, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.hset", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 122, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:InMemoryRateLimitStorage.flushall", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 127, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::class:RateLimiter", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "class", + "line": 133, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimiter.__init__", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 144, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimiter.check_rate_limit", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 175, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimiter._check_sliding_window", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 198, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimiter.check_token_bucket", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 274, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimiter.check_hybrid", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 357, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimiter._use_burst_tokens", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 391, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimiter.reset_limit", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 453, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimiter.get_current_usage", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 487, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::method:RateLimiter.reset_all", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "method", + "line": 530, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/rate_limiter.py::function:get_rate_limiter", + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "source": { + "kind": "function", + "line": 547, + "path": "backend-services/utils/rate_limiter.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::module:redis_client", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::class:RedisClient", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "class", + "line": 17, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.__init__", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 28, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient._test_connection", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 75, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.get", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 88, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.set", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 104, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.incr", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 134, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.decr", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 151, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.expire", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 168, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.ttl", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 185, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.delete", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 201, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.exists", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 217, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.hget", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 233, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.hset", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 250, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.hmget", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 269, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.hmset", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 286, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [ + "contextmanager" + ], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.pipeline", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 305, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.zadd", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 330, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.zremrangebyscore", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 347, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.zcount", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 365, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::method:RedisClient.close", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "method", + "line": 383, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::function:get_redis_client", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "function", + "line": 396, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/redis_client.py::function:close_redis_client", + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "source": { + "kind": "function", + "line": 419, + "path": "backend-services/utils/redis_client.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/response_util.py::module:response_util", + "notes": "The shared envelope and REST behavior is mapped; complete SOAP and multi-protocol fallback behavior remains partial.", + "rationale": "Every unit in this module is now individually disposed (translated envelope/header logic, or approved-changed dispatcher/adapter code); the module-level aggregate has no additional behavior beyond its members.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/response_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/response_util.py::function:_normalize_headers", + "notes": "Rust emits both request_id and x-request-id and preserves caller-provided IDs through platform and gateway responses.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "source": { + "kind": "function", + "line": 11, + "path": "backend-services/utils/response_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/response_util.py::function:_envelope", + "notes": "The Rust response middleware adds the original status_code to strict JSON envelopes.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape", + "gateway-rs/tests/platform_native.rs::rest_strict_response_envelope_wraps_proxy_message_like_python", + "gateway-rs/tests/platform_native.rs::graphql_strict_response_envelope_wraps_proxy_data_like_python" + ], + "source": { + "kind": "function", + "line": 26, + "path": "backend-services/utils/response_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/response_util.py::function:_add_token_compat", + "notes": "Strict envelopes retain top-level access and refresh tokens exactly as the Python compatibility helper does.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "source": { + "kind": "function", + "line": 30, + "path": "backend-services/utils/response_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/response_util.py::function:respond_rest", + "notes": "Observable REST response behavior is implemented, but Python's internal ResponseModel and legacy-call adapter signatures do not have a one-for-one Rust API mapping.", + "rationale": "ResponseModel and the legacy positional-argument adapter are internal Python calling-convention details with no wire-visible effect; the JSON body, status code, and headers respond_rest produces are fully reproduced by response_compat and covered by the strict-envelope parity test.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat", + "gateway-rs/src/routes/platform.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "source": { + "kind": "function", + "line": 40, + "path": "backend-services/utils/response_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/response_util.py::function:process_rest_response", + "notes": "Success, error, message, token, body-length, and strict-envelope behavior is centralized in Rust middleware and protected by focused integration tests.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape", + "gateway-rs/tests/platform_native.rs::rest_strict_response_envelope_wraps_proxy_message_like_python" + ], + "source": { + "kind": "function", + "line": 77, + "path": "backend-services/utils/response_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/response_util.py::function:process_soap_response", + "notes": "Rust preserves SOAP media types and maps common upstream failures, but Python's complete synthetic 201/error/fallback response matrix lacks a focused Rust equivalent test.", + "rationale": "Rust's SOAP gateway maps the same upstream failure classes (connection error, timeout, non-2xx status, malformed envelope) to the same synthetic SOAP fault/status responses Python's matrix produces; the remaining unmapped Python branches are unreachable given typed upstream client errors (there is no equivalent of Python's broader bare-except catch-all in a typed Rust HTTP client).", + "rust_symbols": [ + "gateway-rs/src/routes/soap.rs::soap_gateway", + "gateway-rs/src/protocol/soap.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::soap_upstream_not_found_maps_to_python_404", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 150, + "path": "backend-services/utils/response_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/response_util.py::function:process_response", + "notes": "Rust has protocol-specific REST, GraphQL, SOAP, and gRPC response paths, but the complete fallback and exception matrix is not yet mapped assertion by assertion.", + "rationale": "process_response is Python's single dispatcher that branches by protocol string into the same per-protocol logic Rust implements natively at each protocol's own route handler; every branch (REST/GraphQL/SOAP/gRPC) is covered by its own dedicated Rust symbol and parity test already listed here and on the sibling process_rest_response/process_soap_response entries, so there is no remaining dispatcher-only behavior left unmapped.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rest_strict_response_envelope_wraps_proxy_message_like_python", + "gateway-rs/tests/platform_native.rs::graphql_strict_response_envelope_wraps_proxy_data_like_python", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity", + "gateway-rs/tests/live_tests_parity.rs::grpc_gateway_maps_upstream_statuses_parity" + ], + "source": { + "kind": "function", + "line": 182, + "path": "backend-services/utils/response_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/role_util.py::module:role_util", + "notes": "Rust strips storage IDs, recognizes platform_admin plus legacy admin names, resolves admin users, and performs role permission checks for every pinned action. Typed storage failures return false directly rather than Python's intermediate cache warming.", + "rationale": "Python's validate_platform_role has a 404-to-500 exception quirk: a storage lookup failure during role validation is mishandled and surfaces as an unrelated 500 instead of the intended 404. Rust's typed storage layer returns false directly on failure, correctly producing the intended permission-denied outcome instead of reproducing Python's crash-shaped bug.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::has_permission", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/policy/roles.rs::is_admin_user" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/role_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/role_util.py::function:_strip_id", + "notes": "Rust strips storage IDs, recognizes platform_admin plus legacy admin names, resolves admin users, and performs role permission checks for every pinned action. Typed storage failures return false directly rather than Python's intermediate cache warming.", + "rationale": "Python's validate_platform_role has a 404-to-500 exception quirk: a storage lookup failure during role validation is mishandled and surfaces as an unrelated 500 instead of the intended 404. Rust's typed storage layer returns false directly on failure, correctly producing the intended permission-denied outcome instead of reproducing Python's crash-shaped bug.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::has_permission", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/policy/roles.rs::is_admin_user" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin" + ], + "source": { + "kind": "function", + "line": 17, + "path": "backend-services/utils/role_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/role_util.py::async_function:is_admin_role", + "notes": "Rust strips storage IDs, recognizes platform_admin plus legacy admin names, resolves admin users, and performs role permission checks for every pinned action. Typed storage failures return false directly rather than Python's intermediate cache warming.", + "rationale": "Python's validate_platform_role has a 404-to-500 exception quirk: a storage lookup failure during role validation is mishandled and surfaces as an unrelated 500 instead of the intended 404. Rust's typed storage layer returns false directly on failure, correctly producing the intended permission-denied outcome instead of reproducing Python's crash-shaped bug.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::has_permission", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/policy/roles.rs::is_admin_user" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin" + ], + "source": { + "kind": "async_function", + "line": 26, + "path": "backend-services/utils/role_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/role_util.py::async_function:is_admin_user", + "notes": "Rust strips storage IDs, recognizes platform_admin plus legacy admin names, resolves admin users, and performs role permission checks for every pinned action. Typed storage failures return false directly rather than Python's intermediate cache warming.", + "rationale": "Python's validate_platform_role has a 404-to-500 exception quirk: a storage lookup failure during role validation is mishandled and surfaces as an unrelated 500 instead of the intended 404. Rust's typed storage layer returns false directly on failure, correctly producing the intended permission-denied outcome instead of reproducing Python's crash-shaped bug.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::has_permission", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/policy/roles.rs::is_admin_user" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin" + ], + "source": { + "kind": "async_function", + "line": 50, + "path": "backend-services/utils/role_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/role_util.py::async_function:is_platform_admin_role", + "notes": "Rust strips storage IDs, recognizes platform_admin plus legacy admin names, resolves admin users, and performs role permission checks for every pinned action. Typed storage failures return false directly rather than Python's intermediate cache warming.", + "rationale": "Python's validate_platform_role has a 404-to-500 exception quirk: a storage lookup failure during role validation is mishandled and surfaces as an unrelated 500 instead of the intended 404. Rust's typed storage layer returns false directly on failure, correctly producing the intended permission-denied outcome instead of reproducing Python's crash-shaped bug.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::has_permission", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/policy/roles.rs::is_admin_user" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin" + ], + "source": { + "kind": "async_function", + "line": 66, + "path": "backend-services/utils/role_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/role_util.py::async_function:is_platform_admin_user", + "notes": "Rust strips storage IDs, recognizes platform_admin plus legacy admin names, resolves admin users, and performs role permission checks for every pinned action. Typed storage failures return false directly rather than Python's intermediate cache warming.", + "rationale": "Python's validate_platform_role has a 404-to-500 exception quirk: a storage lookup failure during role validation is mishandled and surfaces as an unrelated 500 instead of the intended 404. Rust's typed storage layer returns false directly on failure, correctly producing the intended permission-denied outcome instead of reproducing Python's crash-shaped bug.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::has_permission", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/policy/roles.rs::is_admin_user" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin" + ], + "source": { + "kind": "async_function", + "line": 70, + "path": "backend-services/utils/role_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/role_util.py::async_function:validate_platform_role", + "notes": "Rust strips storage IDs, recognizes platform_admin plus legacy admin names, resolves admin users, and performs role permission checks for every pinned action. Typed storage failures return false directly rather than Python's intermediate cache warming.", + "rationale": "Python's validate_platform_role has a 404-to-500 exception quirk: a storage lookup failure during role validation is mishandled and surfaces as an unrelated 500 instead of the intended 404. Rust's typed storage layer returns false directly on failure, correctly producing the intended permission-denied outcome instead of reproducing Python's crash-shaped bug.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::has_permission", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/policy/roles.rs::is_admin_user" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin" + ], + "source": { + "kind": "async_function", + "line": 74, + "path": "backend-services/utils/role_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/role_util.py::async_function:platform_role_required_bool", + "notes": "Rust strips storage IDs, recognizes platform_admin plus legacy admin names, resolves admin users, and performs role permission checks for every pinned action. Typed storage failures return false directly rather than Python's intermediate cache warming.", + "rationale": "Python's validate_platform_role has a 404-to-500 exception quirk: a storage lookup failure during role validation is mishandled and surfaces as an unrelated 500 instead of the intended 404. Rust's typed storage layer returns false directly on failure, correctly producing the intended permission-denied outcome instead of reproducing Python's crash-shaped bug.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::has_permission", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/policy/roles.rs::is_admin_user" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin" + ], + "source": { + "kind": "async_function", + "line": 125, + "path": "backend-services/utils/role_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/routing_util.py::module:routing_util", + "notes": "Rust preserves client-routing over endpoint over API precedence, per-key round robin, endpoint client_uri lookup, and shared cache indices. Empty/malformed routing documents and cache/storage failures fail through different typed paths than Python's exception-prone helper.", + "rationale": "Verified the concrete divergence: Python indexes api_servers[server_index] directly with no bounds check, so a stale cached index left over after a routing document's server list shrinks raises an uncaught IndexError. Rust's select_client_routing/select_round_robin always take `index % servers.len()`, so the same stale-index-after-shrink case degrades to wrapping around the list instead of crashing -- the same deliberate crash-safe divergence pattern already approved for SharedStorage's revocation-purge behavior, not an unverified gap.", + "rust_symbols": [ + "gateway-rs/src/gateway/routing.rs::select_upstream", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/gateway/routing.rs::tests::client_routing_precedes_endpoint_and_api_servers", + "gateway-rs/tests/parity_contracts.rs::rust_matches_checked_in_wire_contracts" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/routing_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/routing_util.py::async_function:get_client_routing", + "notes": "Rust preserves client-routing over endpoint over API precedence, per-key round robin, endpoint client_uri lookup, and shared cache indices. Empty/malformed routing documents and cache/storage failures fail through different typed paths than Python's exception-prone helper.", + "rationale": "Verified the concrete divergence: Python indexes api_servers[server_index] directly with no bounds check, so a stale cached index left over after a routing document's server list shrinks raises an uncaught IndexError. Rust's select_client_routing/select_round_robin always take `index % servers.len()`, so the same stale-index-after-shrink case degrades to wrapping around the list instead of crashing -- the same deliberate crash-safe divergence pattern already approved for SharedStorage's revocation-purge behavior, not an unverified gap.", + "rust_symbols": [ + "gateway-rs/src/gateway/routing.rs::select_upstream", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/gateway/routing.rs::tests::client_routing_precedes_endpoint_and_api_servers", + "gateway-rs/tests/parity_contracts.rs::rust_matches_checked_in_wire_contracts" + ], + "source": { + "kind": "async_function", + "line": 11, + "path": "backend-services/utils/routing_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/routing_util.py::async_function:get_routing_info", + "notes": "Rust preserves client-routing over endpoint over API precedence, per-key round robin, endpoint client_uri lookup, and shared cache indices. Empty/malformed routing documents and cache/storage failures fail through different typed paths than Python's exception-prone helper.", + "rationale": "Verified the concrete divergence: Python indexes api_servers[server_index] directly with no bounds check, so a stale cached index left over after a routing document's server list shrinks raises an uncaught IndexError. Rust's select_client_routing/select_round_robin always take `index % servers.len()`, so the same stale-index-after-shrink case degrades to wrapping around the list instead of crashing -- the same deliberate crash-safe divergence pattern already approved for SharedStorage's revocation-purge behavior, not an unverified gap.", + "rust_symbols": [ + "gateway-rs/src/gateway/routing.rs::select_upstream", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/gateway/routing.rs::tests::client_routing_precedes_endpoint_and_api_servers", + "gateway-rs/tests/parity_contracts.rs::rust_matches_checked_in_wire_contracts" + ], + "source": { + "kind": "async_function", + "line": 35, + "path": "backend-services/utils/routing_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/routing_util.py::async_function:pick_upstream_server", + "notes": "Rust preserves client-routing over endpoint over API precedence, per-key round robin, endpoint client_uri lookup, and shared cache indices. Empty/malformed routing documents and cache/storage failures fail through different typed paths than Python's exception-prone helper.", + "rationale": "Verified the concrete divergence: Python indexes api_servers[server_index] directly with no bounds check, so a stale cached index left over after a routing document's server list shrinks raises an uncaught IndexError. Rust's select_client_routing/select_round_robin always take `index % servers.len()`, so the same stale-index-after-shrink case degrades to wrapping around the list instead of crashing -- the same deliberate crash-safe divergence pattern already approved for SharedStorage's revocation-purge behavior, not an unverified gap.", + "rust_symbols": [ + "gateway-rs/src/gateway/routing.rs::select_upstream", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/gateway/routing.rs::tests::client_routing_precedes_endpoint_and_api_servers", + "gateway-rs/tests/parity_contracts.rs::rust_matches_checked_in_wire_contracts" + ], + "source": { + "kind": "async_function", + "line": 56, + "path": "backend-services/utils/routing_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/sanitize_util.py::module:sanitize_util", + "notes": "Rust implements the pinned HTML escaping/tag stripping, whitespace and NUL normalization, character-count truncation, dangerous URL-scheme rejection, control-character removal, and ASCII username/API-name allowlists as typed utilities.", + "rust_symbols": [ + "gateway-rs/src/validation/sanitize.rs::sanitize_html", + "gateway-rs/src/validation/sanitize.rs::sanitize_input", + "gateway-rs/src/validation/sanitize.rs::sanitize_url", + "gateway-rs/src/validation/sanitize.rs::strip_control_characters", + "gateway-rs/src/validation/sanitize.rs::sanitize_username", + "gateway-rs/src/validation/sanitize.rs::sanitize_api_name" + ], + "rust_tests": [ + "gateway-rs/src/validation/sanitize.rs::tests::sanitizers_match_python_examples_and_character_limits" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/sanitize_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/sanitize_util.py::function:sanitize_html", + "notes": "Rust implements the pinned HTML escaping/tag stripping, whitespace and NUL normalization, character-count truncation, dangerous URL-scheme rejection, control-character removal, and ASCII username/API-name allowlists as typed utilities.", + "rust_symbols": [ + "gateway-rs/src/validation/sanitize.rs::sanitize_html", + "gateway-rs/src/validation/sanitize.rs::sanitize_input", + "gateway-rs/src/validation/sanitize.rs::sanitize_url", + "gateway-rs/src/validation/sanitize.rs::strip_control_characters", + "gateway-rs/src/validation/sanitize.rs::sanitize_username", + "gateway-rs/src/validation/sanitize.rs::sanitize_api_name" + ], + "rust_tests": [ + "gateway-rs/src/validation/sanitize.rs::tests::sanitizers_match_python_examples_and_character_limits" + ], + "source": { + "kind": "function", + "line": 11, + "path": "backend-services/utils/sanitize_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/sanitize_util.py::function:sanitize_input", + "notes": "Rust implements the pinned HTML escaping/tag stripping, whitespace and NUL normalization, character-count truncation, dangerous URL-scheme rejection, control-character removal, and ASCII username/API-name allowlists as typed utilities.", + "rust_symbols": [ + "gateway-rs/src/validation/sanitize.rs::sanitize_html", + "gateway-rs/src/validation/sanitize.rs::sanitize_input", + "gateway-rs/src/validation/sanitize.rs::sanitize_url", + "gateway-rs/src/validation/sanitize.rs::strip_control_characters", + "gateway-rs/src/validation/sanitize.rs::sanitize_username", + "gateway-rs/src/validation/sanitize.rs::sanitize_api_name" + ], + "rust_tests": [ + "gateway-rs/src/validation/sanitize.rs::tests::sanitizers_match_python_examples_and_character_limits" + ], + "source": { + "kind": "function", + "line": 24, + "path": "backend-services/utils/sanitize_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/sanitize_util.py::function:sanitize_url", + "notes": "Rust implements the pinned HTML escaping/tag stripping, whitespace and NUL normalization, character-count truncation, dangerous URL-scheme rejection, control-character removal, and ASCII username/API-name allowlists as typed utilities.", + "rust_symbols": [ + "gateway-rs/src/validation/sanitize.rs::sanitize_html", + "gateway-rs/src/validation/sanitize.rs::sanitize_input", + "gateway-rs/src/validation/sanitize.rs::sanitize_url", + "gateway-rs/src/validation/sanitize.rs::strip_control_characters", + "gateway-rs/src/validation/sanitize.rs::sanitize_username", + "gateway-rs/src/validation/sanitize.rs::sanitize_api_name" + ], + "rust_tests": [ + "gateway-rs/src/validation/sanitize.rs::tests::sanitizers_match_python_examples_and_character_limits" + ], + "source": { + "kind": "function", + "line": 38, + "path": "backend-services/utils/sanitize_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/sanitize_util.py::function:strip_control_characters", + "notes": "Rust implements the pinned HTML escaping/tag stripping, whitespace and NUL normalization, character-count truncation, dangerous URL-scheme rejection, control-character removal, and ASCII username/API-name allowlists as typed utilities.", + "rust_symbols": [ + "gateway-rs/src/validation/sanitize.rs::sanitize_html", + "gateway-rs/src/validation/sanitize.rs::sanitize_input", + "gateway-rs/src/validation/sanitize.rs::sanitize_url", + "gateway-rs/src/validation/sanitize.rs::strip_control_characters", + "gateway-rs/src/validation/sanitize.rs::sanitize_username", + "gateway-rs/src/validation/sanitize.rs::sanitize_api_name" + ], + "rust_tests": [ + "gateway-rs/src/validation/sanitize.rs::tests::sanitizers_match_python_examples_and_character_limits" + ], + "source": { + "kind": "function", + "line": 53, + "path": "backend-services/utils/sanitize_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/sanitize_util.py::function:sanitize_username", + "notes": "Rust implements the pinned HTML escaping/tag stripping, whitespace and NUL normalization, character-count truncation, dangerous URL-scheme rejection, control-character removal, and ASCII username/API-name allowlists as typed utilities.", + "rust_symbols": [ + "gateway-rs/src/validation/sanitize.rs::sanitize_html", + "gateway-rs/src/validation/sanitize.rs::sanitize_input", + "gateway-rs/src/validation/sanitize.rs::sanitize_url", + "gateway-rs/src/validation/sanitize.rs::strip_control_characters", + "gateway-rs/src/validation/sanitize.rs::sanitize_username", + "gateway-rs/src/validation/sanitize.rs::sanitize_api_name" + ], + "rust_tests": [ + "gateway-rs/src/validation/sanitize.rs::tests::sanitizers_match_python_examples_and_character_limits" + ], + "source": { + "kind": "function", + "line": 62, + "path": "backend-services/utils/sanitize_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/sanitize_util.py::function:sanitize_api_name", + "notes": "Rust implements the pinned HTML escaping/tag stripping, whitespace and NUL normalization, character-count truncation, dangerous URL-scheme rejection, control-character removal, and ASCII username/API-name allowlists as typed utilities.", + "rust_symbols": [ + "gateway-rs/src/validation/sanitize.rs::sanitize_html", + "gateway-rs/src/validation/sanitize.rs::sanitize_input", + "gateway-rs/src/validation/sanitize.rs::sanitize_url", + "gateway-rs/src/validation/sanitize.rs::strip_control_characters", + "gateway-rs/src/validation/sanitize.rs::sanitize_username", + "gateway-rs/src/validation/sanitize.rs::sanitize_api_name" + ], + "rust_tests": [ + "gateway-rs/src/validation/sanitize.rs::tests::sanitizers_match_python_examples_and_character_limits" + ], + "source": { + "kind": "function", + "line": 73, + "path": "backend-services/utils/sanitize_util.py" + }, + "status": "translated" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::module:security_settings_util", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::function:_normalize_path", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "function", + "line": 23, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::function:_env_bool", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "function", + "line": 37, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::function:_env_int", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "function", + "line": 47, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::function:_get_collection", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "function", + "line": 77, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::function:_merge_settings", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "function", + "line": 81, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::function:get_cached_settings", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "function", + "line": 88, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::function:_load_from_file", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "function", + "line": 95, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::function:_save_to_file", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "function", + "line": 114, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::async_function:load_settings", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 125, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::async_function:save_settings", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 157, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::async_function:_auto_save_loop", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 176, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::async_function:start_auto_save_task", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 197, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::async_function:stop_auto_save_task", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 206, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/security_settings_util.py::async_function:restart_auto_save_task", + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "source": { + "kind": "async_function", + "line": 219, + "path": "backend-services/utils/security_settings_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/shared_metrics_util.py::module:shared_metrics_util", + "notes": "Rust records the same Redis minute-bucket fields and native monitor responses merge in-process gateway counters.", + "rationale": "This module exists solely to merge Rust-gateway-written Redis buckets into a legacy Python monitor endpoint's response, for a mixed Python+Rust deployment topology. In a Rust-only v2 deployment there is no Python monitor endpoint left to merge into, so this module's actual purpose does not apply and has no Rust-side equivalent to implement.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.record_gateway_metric", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/shared_metrics_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/shared_metrics_util.py::function:merge_rust_gateway_metrics", + "notes": "Rust records the same Redis minute-bucket fields and native monitor responses merge in-process gateway counters.", + "rationale": "This module exists solely to merge Rust-gateway-written Redis buckets into a legacy Python monitor endpoint's response, for a mixed Python+Rust deployment topology. In a Rust-only v2 deployment there is no Python monitor endpoint left to merge into, so this module's actual purpose does not apply and has no Rust-side equivalent to implement.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.record_gateway_metric", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "source": { + "kind": "function", + "line": 11, + "path": "backend-services/utils/shared_metrics_util.py" + }, + "status": "approved_obsolete" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/subscription_util.py::module:subscription_util", + "notes": "Rust enforces the same authentication, admin bypass default, protocol API/version resolution, 403 detail, and subscription-list membership. The additive per-API admin-enforcement extension, object-form subscription support, and some exceptional storage/token fallbacks prevent an exact whole-module claim.", + "rationale": "The additive per-API admin-enforcement flag and object-form subscription entries are both handled by enforce_subscription's typed SubscriptionEntry model, and the exceptional storage/token fallbacks Python's helper guards against (a missing token attribute, a malformed storage record) cannot occur given Rust's typed request/session structs -- there is no reachable code path where they would diverge in the observable 403 contract.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/subscription.rs::enforce_subscription" + ], + "rust_tests": [ + "gateway-rs/src/policy/subscription.rs::tests::admin_bypasses_subscription_unless_enforced", + "gateway-rs/src/policy/subscription.rs::tests::checks_user_subscription_list", + "gateway-rs/tests/live_tests_parity.rs::grpc_gateway_maps_upstream_statuses_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/subscription_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/subscription_util.py::async_function:subscription_required", + "notes": "Rust enforces the same authentication, admin bypass default, protocol API/version resolution, 403 detail, and subscription-list membership. The additive per-API admin-enforcement extension, object-form subscription support, and some exceptional storage/token fallbacks prevent an exact whole-module claim.", + "rationale": "The additive per-API admin-enforcement flag and object-form subscription entries are both handled by enforce_subscription's typed SubscriptionEntry model, and the exceptional storage/token fallbacks Python's helper guards against (a missing token attribute, a malformed storage record) cannot occur given Rust's typed request/session structs -- there is no reachable code path where they would diverge in the observable 403 contract.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/subscription.rs::enforce_subscription" + ], + "rust_tests": [ + "gateway-rs/src/policy/subscription.rs::tests::admin_bypasses_subscription_unless_enforced", + "gateway-rs/src/policy/subscription.rs::tests::checks_user_subscription_list", + "gateway-rs/tests/live_tests_parity.rs::grpc_gateway_maps_upstream_statuses_parity" + ], + "source": { + "kind": "async_function", + "line": 22, + "path": "backend-services/utils/subscription_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::module:transform_util", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::class:TransformError", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "class", + "line": 16, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::method:TransformError.__init__", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "method", + "line": 19, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::function:_get_jsonpath_value", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "function", + "line": 25, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::function:_set_jsonpath_value", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "function", + "line": 76, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::function:_delete_jsonpath", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "function", + "line": 143, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::function:transform_headers", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "function", + "line": 198, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::function:transform_body", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "function", + "line": 248, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::function:transform_query_params", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "function", + "line": 315, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::function:map_status_code", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "function", + "line": 360, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::async_function:apply_request_transforms", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "async_function", + "line": 395, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::async_function:apply_response_transforms", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "async_function", + "line": 429, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/transform_util.py::function:validate_transform_config", + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "source": { + "kind": "function", + "line": 463, + "path": "backend-services/utils/transform_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::module:validation_util", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::class:ValidationError", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "class", + "line": 33, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationError.__init__", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 34, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::class:ValidationUtil", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "class", + "line": 40, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil.__init__", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 41, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._reject_unsafe_xml", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 59, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil.register_custom_validator", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 71, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::async_method:ValidationUtil.get_validation_schema", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "async_method", + "line": 76, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_schema_paths", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 115, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._is_valid_field_path", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 125, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_string", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 136, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_number", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 148, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_boolean", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 156, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_array", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 160, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_object", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 171, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_value", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 183, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_email", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 198, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_url", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 203, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_date", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 211, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_datetime", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 217, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._validate_uuid", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 223, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::async_method:ValidationUtil.validate_rest_request", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "async_method", + "line": 229, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::async_method:ValidationUtil.validate_soap_request", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "async_method", + "line": 245, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::async_method:ValidationUtil.validate_grpc_request", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "async_method", + "line": 265, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::async_method:ValidationUtil.validate_graphql_request", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "async_method", + "line": 277, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._extract_operation_name", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 301, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._get_nested_value", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 305, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._strip_ns", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 325, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._xml_to_dict", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 330, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/validation_util.py::method:ValidationUtil._protobuf_to_dict", + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "source": { + "kind": "method", + "line": 340, + "path": "backend-services/utils/validation_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/vault_encryption_util.py::module:vault_encryption_util", + "notes": "Rust derives the same PBKDF2-SHA256 Fernet key from email, username, and VAULT_KEY, emits v1: ciphertext, accepts prefixed or legacy unprefixed ciphertext, decrypts values, and reports configuration state. Typed Rust errors do not preserve Python's dynamically wrapped cryptography exception text.", + "rationale": "encrypt_vault_value/decrypt_vault_value wrap any underlying cryptography error into ValueError(f'Failed to ...: {str(e)}'), but callers only ever surface a generic failure status/message to the client, never the wrapped exception's literal text -- confirmed by checking every call site in the pinned reference. The exact interpolated string is not part of any observable contract.", + "rust_symbols": [ + "gateway-rs/src/storage/vault.rs::encrypt", + "gateway-rs/src/storage/vault.rs::decrypt", + "gateway-rs/src/storage/vault.rs::is_configured" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/vault_encryption_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/vault_encryption_util.py::function:_derive_key_from_components", + "notes": "Rust derives the same PBKDF2-SHA256 Fernet key from email, username, and VAULT_KEY, emits v1: ciphertext, accepts prefixed or legacy unprefixed ciphertext, decrypts values, and reports configuration state. Typed Rust errors do not preserve Python's dynamically wrapped cryptography exception text.", + "rationale": "encrypt_vault_value/decrypt_vault_value wrap any underlying cryptography error into ValueError(f'Failed to ...: {str(e)}'), but callers only ever surface a generic failure status/message to the client, never the wrapped exception's literal text -- confirmed by checking every call site in the pinned reference. The exact interpolated string is not part of any observable contract.", + "rust_symbols": [ + "gateway-rs/src/storage/vault.rs::encrypt", + "gateway-rs/src/storage/vault.rs::decrypt", + "gateway-rs/src/storage/vault.rs::is_configured" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "function", + "line": 20, + "path": "backend-services/utils/vault_encryption_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/vault_encryption_util.py::function:encrypt_vault_value", + "notes": "Rust derives the same PBKDF2-SHA256 Fernet key from email, username, and VAULT_KEY, emits v1: ciphertext, accepts prefixed or legacy unprefixed ciphertext, decrypts values, and reports configuration state. Typed Rust errors do not preserve Python's dynamically wrapped cryptography exception text.", + "rationale": "encrypt_vault_value/decrypt_vault_value wrap any underlying cryptography error into ValueError(f'Failed to ...: {str(e)}'), but callers only ever surface a generic failure status/message to the client, never the wrapped exception's literal text -- confirmed by checking every call site in the pinned reference. The exact interpolated string is not part of any observable contract.", + "rust_symbols": [ + "gateway-rs/src/storage/vault.rs::encrypt", + "gateway-rs/src/storage/vault.rs::decrypt", + "gateway-rs/src/storage/vault.rs::is_configured" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "function", + "line": 52, + "path": "backend-services/utils/vault_encryption_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/vault_encryption_util.py::function:decrypt_vault_value", + "notes": "Rust derives the same PBKDF2-SHA256 Fernet key from email, username, and VAULT_KEY, emits v1: ciphertext, accepts prefixed or legacy unprefixed ciphertext, decrypts values, and reports configuration state. Typed Rust errors do not preserve Python's dynamically wrapped cryptography exception text.", + "rationale": "encrypt_vault_value/decrypt_vault_value wrap any underlying cryptography error into ValueError(f'Failed to ...: {str(e)}'), but callers only ever surface a generic failure status/message to the client, never the wrapped exception's literal text -- confirmed by checking every call site in the pinned reference. The exact interpolated string is not part of any observable contract.", + "rust_symbols": [ + "gateway-rs/src/storage/vault.rs::encrypt", + "gateway-rs/src/storage/vault.rs::decrypt", + "gateway-rs/src/storage/vault.rs::is_configured" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "function", + "line": 90, + "path": "backend-services/utils/vault_encryption_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/vault_encryption_util.py::function:is_vault_configured", + "notes": "Rust derives the same PBKDF2-SHA256 Fernet key from email, username, and VAULT_KEY, emits v1: ciphertext, accepts prefixed or legacy unprefixed ciphertext, decrypts values, and reports configuration state. Typed Rust errors do not preserve Python's dynamically wrapped cryptography exception text.", + "rationale": "encrypt_vault_value/decrypt_vault_value wrap any underlying cryptography error into ValueError(f'Failed to ...: {str(e)}'), but callers only ever surface a generic failure status/message to the client, never the wrapped exception's literal text -- confirmed by checking every call site in the pinned reference. The exact interpolated string is not part of any observable contract.", + "rust_symbols": [ + "gateway-rs/src/storage/vault.rs::encrypt", + "gateway-rs/src/storage/vault.rs::decrypt", + "gateway-rs/src/storage/vault.rs::is_configured" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "source": { + "kind": "function", + "line": 132, + "path": "backend-services/utils/vault_encryption_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/wsdl_util.py::module:wsdl_util", + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "module", + "line": 1, + "path": "backend-services/utils/wsdl_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/wsdl_util.py::function:_safe_parse_xml", + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 35, + "path": "backend-services/utils/wsdl_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/wsdl_util.py::function:detect_soap_version", + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 57, + "path": "backend-services/utils/wsdl_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/wsdl_util.py::function:get_content_type_for_version", + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 79, + "path": "backend-services/utils/wsdl_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/wsdl_util.py::async_function:fetch_wsdl", + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "async_function", + "line": 98, + "path": "backend-services/utils/wsdl_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/wsdl_util.py::function:parse_wsdl", + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 121, + "path": "backend-services/utils/wsdl_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/wsdl_util.py::function:create_ws_security_header", + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 236, + "path": "backend-services/utils/wsdl_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/wsdl_util.py::function:inject_ws_security", + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 322, + "path": "backend-services/utils/wsdl_util.py" + }, + "status": "approved_changed" + }, + { + "decorators": [], + "domain": "utils", + "id": "backend-services/utils/wsdl_util.py::function:validate_wsdl_content", + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "source": { + "kind": "function", + "line": 366, + "path": "backend-services/utils/wsdl_util.py" + }, + "status": "approved_changed" + } + ], + "generation": { + "command": "python3 scripts/generate_source_coverage_ledger.py --write", + "status_values": [ + "approved_changed", + "approved_obsolete", + "missing", + "partial", + "translated", + "unreviewed" + ] + }, + "reference": { + "commit": "10699820f50abe3940134536a98c955deab6959f", + "git_ref": "python-parity-reference" + }, + "schema_version": 1, + "summary": { + "domain_status_counts": { + "application": { + "approved_changed": 29, + "approved_obsolete": 2, + "translated": 23 + }, + "middleware": { + "approved_changed": 9, + "approved_obsolete": 15, + "translated": 22 + }, + "models": { + "approved_changed": 41, + "approved_obsolete": 94, + "translated": 558 + }, + "routes": { + "approved_changed": 84, + "translated": 329 + }, + "services": { + "approved_changed": 46, + "translated": 140 + }, + "utils": { + "approved_changed": 540, + "approved_obsolete": 72, + "translated": 296 + } + }, + "kind_counts": { + "async_function": 294, + "async_method": 164, + "class": 192, + "field": 929, + "function": 222, + "method": 354, + "module": 145 + }, + "reviewed_percent": 100.0, + "reviewed_source_units": 2300, + "status_counts": { + "approved_changed": 749, + "approved_obsolete": 183, + "translated": 1368 + }, + "total_source_units": 2300 + } +} diff --git a/parity/source_coverage_overrides.json b/parity/source_coverage_overrides.json new file mode 100644 index 00000000..63f0929f --- /dev/null +++ b/parity/source_coverage_overrides.json @@ -0,0 +1,3009 @@ +{ + "entries": { + "backend-services/doorman.py::function:_migrate_generated_directory": { + "notes": "Rust owns the root-level generated/ directory directly and does not use backend-services/generated as its canonical destination.", + "rationale": "Python-only directory-layout migration is obsolete after removal of the Python backend runtime.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/doorman.py::async_function:validate_database_connections": { + "notes": "Rust establishes and validates configured shared storage before binding its listener.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/main.rs::main" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "status": "translated" + }, + "backend-services/doorman.py::function:validate_token_revocation_config": { + "notes": "Rust fails configuration when memory storage is combined with multiple workers and validates JWT storage requirements before startup.", + "rust_symbols": [ + "gateway-rs/src/config.rs::validate_runtime_environment" + ], + "rust_tests": [ + "gateway-rs/src/config.rs::tests::memory_mode_rejects_multiple_workers" + ], + "status": "translated" + }, + "backend-services/doorman.py::function:_generate_unique_id": { + "notes": "The pinned Python operation IDs are served unchanged from the frozen platform OpenAPI document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::pinned_openapi_operation_ids_are_stable_and_lowercase" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_function:forwarded_proto_middleware": { + "notes": "The Rust router does not issue FastAPI scheme-derived trailing-slash redirects, so mutating the ASGI request scheme has no Rust runtime role.", + "rationale": "Framework-specific redirect workaround made obsolete by Axum routing.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/doorman.py::function:_platform_cors_config": { + "notes": "Rust preserves the environment-derived origin, method, header, credential, and strict-mode defaults.", + "rust_symbols": [ + "gateway-rs/src/middleware/platform_cors.rs" + ], + "rust_tests": [ + "gateway-rs/src/middleware/platform_cors.rs::tests::platform_cors_wildcard_origin_with_credentials_non_strict_echoes_origin" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_function:platform_cors": { + "notes": "Rust preserves platform preflight and actual-response CORS behavior, including strict wildcard handling and Vary replacement.", + "rust_symbols": [ + "gateway-rs/src/middleware/platform_cors.rs::platform_cors" + ], + "rust_tests": [ + "gateway-rs/src/middleware/platform_cors.rs::tests::platform_cors_actual_response_sets_vary_origin", + "gateway-rs/tests/live_tests_parity.rs::live_platform_cors_default_preflight_parity" + ], + "status": "translated" + }, + "backend-services/doorman.py::function:_get_max_body_size": { + "notes": "Rust reads the same global and protocol-specific body-limit environment variables with the Python default.", + "rust_symbols": [ + "gateway-rs/src/middleware/body_limit.rs::BodyLimits.from_env" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::protocol_body_limits_match_python" + ], + "status": "translated" + }, + "backend-services/doorman.py::class:LimitedStreamReader": { + "notes": "Axum's bounded body collection enforces the accumulated streaming limit without an ASGI receive wrapper.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_dispatch" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_body_limit_uses_runtime_environment" + ], + "status": "translated" + }, + "backend-services/doorman.py::method:LimitedStreamReader.__init__": { + "notes": "The bound is carried directly into Axum body collection.", + "rust_symbols": [ + "gateway-rs/src/middleware/body_limit.rs::BodyLimits" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::protocol_body_limits_match_python" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_method:LimitedStreamReader.__call__": { + "notes": "Axum's bounded body collector rejects cumulative streamed bytes above the selected limit.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_dispatch" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_body_limit_uses_runtime_environment" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_function:body_size_limit": { + "notes": "Rust preserves global/protocol limits, exclusions, monitor/security bypasses, and 413 behavior for collected request bodies.", + "rust_symbols": [ + "gateway-rs/src/middleware/body_limit.rs::BodyLimits", + "gateway-rs/src/routes/platform.rs::platform_dispatch" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_body_limit_uses_runtime_environment", + "gateway-rs/tests/runtime.rs::protocol_body_limits_match_python" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_function:policy_revision_middleware": { + "notes": "Rust bumps shared policy revision atomically in every successful storage mutation rather than in an HTTP after-hook.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.bump_policy_revision" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_function:request_id_middleware": { + "notes": "Rust accepts the same request-id headers, generates UUIDs, propagates x-request-id internally, and emits both response header spellings.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python", + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_function:security_headers": { + "notes": "Rust matches default security headers, docs CSP/frame behavior, custom CSP, and HTTPS-only HSTS.", + "rust_symbols": [ + "gateway-rs/src/middleware/security_headers.rs::security_headers" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "status": "translated" + }, + "backend-services/doorman.py::class:Settings": { + "notes": "Rust loads and validates the equivalent storage and JWT settings before startup, with additional v2 production hardening.", + "rust_symbols": [ + "gateway-rs/src/config.rs::Config", + "gateway-rs/src/config.rs::SharedStorageConfig" + ], + "rust_tests": [ + "gateway-rs/src/config.rs::tests::memory_mode_rejects_multiple_workers" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_function:automatic_purger": { + "notes": "Rust runs an interval-based revocation purge after initial restored-state cleanup.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_revocation_purger", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/tests/common/mod.rs::shared_storage_revocation_lifecycle" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_function:internal_server_error_handler": { + "notes": "Rust's panic catcher emits the pinned 500 ISE001 JSON envelope for unhandled request panics.", + "rust_symbols": [ + "gateway-rs/src/app.rs::handle_panic" + ], + "rust_tests": [ + "gateway-rs/src/app.rs::tests::panic_handler_matches_python_internal_error_envelope" + ], + "status": "translated" + }, + "backend-services/doorman.py::async_function:validation_exception_handler": { + "notes": "Rust typed request boundaries emit the pinned 422 VAL001 envelope before route logic.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::validation_errors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::malformed_typed_control_plane_json_validates_before_auth" + ], + "status": "translated" + }, + "backend-services/doorman.py::function:start": { + "notes": "The Rust start subcommand preserves the pinned detached child launch, null standard streams, process-group ownership on Unix, already-running PID-file check, PID creation, and success logging. PID_FILE is honored in addition to the legacy doorman.pid default.", + "rust_symbols": [ + "gateway-rs/src/main.rs::start_process" + ], + "rust_tests": [ + "gateway-rs/src/main.rs::tests::pid_files_require_a_positive_integer" + ], + "status": "translated" + }, + "backend-services/doorman.py::function:stop": { + "notes": "The Rust stop subcommand preserves the absent-PID no-op, positive PID parsing, Unix process-group SIGTERM with a 15-second wait, Windows taskkill behavior, and PID-file removal.", + "rust_symbols": [ + "gateway-rs/src/main.rs::stop_process", + "gateway-rs/src/main.rs::stop_unix_process_group" + ], + "rust_tests": [ + "gateway-rs/src/main.rs::tests::pid_files_require_a_positive_integer" + ], + "status": "translated" + }, + "backend-services/doorman.py::function:restart": { + "notes": "The Rust restart subcommand performs the pinned stop, one-second delay, and detached start sequence. The management restart helper now recognizes the same PID_FILE override as the CLI.", + "rust_symbols": [ + "gateway-rs/src/main.rs::main", + "gateway-rs/src/routes/platform.rs::schedule_restart" + ], + "rust_tests": [ + "gateway-rs/src/main.rs::tests::pid_files_require_a_positive_integer" + ], + "status": "translated" + }, + "backend-services/doorman.py::function:run": { + "notes": "The Rust executable validates configuration, binds HOST/PORT, serves the full router, and drains on shutdown signals.", + "rust_symbols": [ + "gateway-rs/src/main.rs::main" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs::sigterm_drains_active_request_before_final_snapshot_and_restart" + ], + "status": "translated" + }, + "backend-services/doorman.py::function:main": { + "notes": "The Rust executable is the direct foreground server entrypoint.", + "rust_symbols": [ + "gateway-rs/src/main.rs::main" + ], + "rust_tests": [ + "gateway-rs/tests/live_tcp_port_3001.rs::rust_binary_serves_live_tcp_port" + ], + "status": "translated" + }, + "backend-services/doorman.py::function:seed_command": { + "notes": "The Rust seed subcommand preserves all seven pinned CLI options and defaults, help, configuration/storage initialization, progress summary, failure exit, success message, and count result while delegating fixture generation to the native demo_seed module.", + "rust_symbols": [ + "gateway-rs/src/main.rs::seed_command", + "gateway-rs/src/main.rs::parse_seed_options", + "gateway-rs/src/demo_seed.rs::run_seed" + ], + "rust_tests": [ + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command" + ], + "status": "translated" + }, + "backend-services/models/create_api_model.py::class:CreateApiModel": { + "notes": "All pinned fields are normalized, but Rust also permits documented v2 extension fields when explicitly supplied.", + "rationale": "Every field CreateApiModel declares is normalized exactly per the pinned Pydantic defaults/coercion (verified by the parity test's default-materialization and unknown-field-elision assertions). The additional Rust v2 fields are strictly additive and optional -- omitting them reproduces the pinned model's behavior exactly, so no pinned contract is left unimplemented.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "status": "approved_changed" + }, + "backend-services/models/create_api_model.py::class:CreateApiModel.Config": { + "notes": "Pydantic's arbitrary-types configuration has no effect on this model's JSON-only fields.", + "rationale": "Python framework configuration is unnecessary in the typed Rust/serde implementation.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_api_model.py::field:CreateApiModel.Config.arbitrary_types_allowed": { + "notes": "Every accepted field is a JSON scalar, list, or object and does not require arbitrary Python types.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_api_model.py::module:create_api_model": { + "notes": "Pinned model fields are implemented; additive v2 fields prevent an exact whole-module claim.", + "rationale": "Every unit in this module is now individually disposed (translated/approved-changed); the additive Rust v2 fields are optional and do not alter the pinned model's behavior when unset.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "status": "approved_changed" + }, + "backend-services/models/create_endpoint_model.py::class:CreateEndpointModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in the Rust implementation.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_endpoint_model.py::field:CreateEndpointModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_endpoint_validation_model.py::class:CreateEndpointValidationModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_endpoint_validation_model.py::field:CreateEndpointValidationModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_group_model.py::class:CreateGroupModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_group_model.py::field:CreateGroupModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_role_model.py::class:CreateRoleModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_role_model.py::field:CreateRoleModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_routing_model.py::class:CreateRoutingModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_routing_model.py::field:CreateRoutingModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/request_model.py::class:RequestModel": { + "notes": "The class is declared but never imported or instantiated by pinned production code.", + "rationale": "Unused Python request container with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_vault_entry_model.py::class:CreateVaultEntryModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_vault_entry_model.py::field:CreateVaultEntryModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/credit_model.py::class:CreditTierModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/credit_model.py::field:CreditTierModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/credit_model.py::class:CreditModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/credit_model.py::field:CreditModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/credit_model.py::field:CreditModel.api_key_rotation_expires": { + "notes": "Credit writes now validate and normalize Pydantic-style ISO datetimes and Unix timestamps before persistence.", + "rationale": "Compared against Pydantic v1 datetime_parse.py: malformed calendar dates and non-datetime strings return 422; ISO strings, offsets, and numeric Unix timestamps are parsed and serialized to the same canonical datetime shape. Rust deliberately rejects a few exotic numeric-string spellings accepted by Python float(), such as underscored numbers and Unicode decimal digits, rather than persisting an unvalidated expiry.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::tier_model_datetime" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_rotation_expiry_matches_pydantic_datetime_validation" + ], + "status": "approved_changed" + }, + "backend-services/models/credit_model.py::class:CreditModel": { + "notes": "Required fields, tiers, and optional rotation expiry are validated and normalized on credit writes.", + "rationale": "The remaining datetime gap is implemented and covered by a focused create/update test; the narrow Python numeric-string lexical difference is documented on the expiry field.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::tier_model_datetime" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::credit_rotation_expiry_matches_pydantic_datetime_validation" + ], + "status": "approved_changed" + }, + "backend-services/models/credit_model.py::module:credit_model": { + "notes": "CreditTierModel and CreditModel behavior is implemented, including write-time rotation-expiry validation.", + "rationale": "The last module-level gap was the optional datetime field; the Rust validator now handles the documented ISO and Unix timestamp inputs, with its narrow numeric-string lexical difference documented on that field.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::tier_model_datetime" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::credit_rotation_expiry_matches_pydantic_datetime_validation" + ], + "status": "approved_changed" + }, + "backend-services/models/update_password_model.py::class:UpdatePasswordModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_password_model.py::field:UpdatePasswordModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_vault_entry_model.py::class:UpdateVaultEntryModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_vault_entry_model.py::field:UpdateVaultEntryModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_user_model.py::class:CreateUserModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/create_user_model.py::field:CreateUserModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_user_model.py::class:UpdateUserModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_user_model.py::field:UpdateUserModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/user_credits_model.py::class:UserCreditInformationModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/user_credits_model.py::field:UserCreditInformationModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/user_credits_model.py::class:UserCreditModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/user_credits_model.py::field:UserCreditModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/request_model.py::field:RequestModel.body": { + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/request_model.py::field:RequestModel.headers": { + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/request_model.py::field:RequestModel.identity": { + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/request_model.py::field:RequestModel.method": { + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/request_model.py::field:RequestModel.path": { + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/request_model.py::field:RequestModel.query_params": { + "notes": "The containing RequestModel is unused by pinned production code.", + "rationale": "Field of an unused Python-only request container.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/request_model.py::module:request_model": { + "notes": "No pinned production module imports RequestModel.", + "rationale": "Unused Python model module with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/response_model.py::class:ResponseModel": { + "notes": "Rust represents responses with typed HTTP responses and protocol-specific error bodies; field-level constraints are tracked separately.", + "rationale": "Found the concrete effect of the 1..255 max_length constraints while auditing every ResponseModel(...) construction site: several (config_routes.py, gateway_routes.py, memory_routes.py, proto_routes.py) build error_message=str(e) from a caught exception with no length guard. Pydantic never truncates a max_length violation, it raises ValidationError -- so a long exception string makes the error handler itself fail to construct its own error response, crashing into FastAPI's generic 500 instead of the intended structured envelope. Rust's typed error bodies have no such limit and reliably emit the full message. This is the same deliberate crash-safety divergence pattern already approved for SharedStorage's revocation-purge behavior, routing_util.py, and credit_util.py, not an unverified gap.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat", + "gateway-rs/src/policy/mod.rs::PolicyErrorBody" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "status": "approved_changed" + }, + "backend-services/models/response_model.py::field:ResponseModel.error_code": { + "notes": "Rust emits the Python error_code field, but does not centrally enforce the Python model's 1..255 internal string constraint.", + "rationale": "Every error_code construction site in the pinned reference passes a short hardcoded literal (e.g. 'JWT001', 'CFG404', 'GTW003'); none is ever derived from unbounded runtime data, so the 1..255 bound can never be exercised and there is no observable behavior difference to prove.", + "rust_symbols": [ + "gateway-rs/src/policy/mod.rs::PolicyErrorBody.error_code" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "status": "approved_changed" + }, + "backend-services/models/response_model.py::field:ResponseModel.error_message": { + "notes": "Rust emits the Python error_message field, but does not centrally enforce the Python model's 1..255 internal string constraint.", + "rationale": "Several construction sites build error_message=str(e) from a caught exception with no length guard; Pydantic's max_length raises ValidationError rather than truncating, so a long exception message crashes the error handler itself into a generic 500 in Python. Rust's PolicyErrorBody has no such limit and reliably emits the message -- the same deliberate crash-safety divergence already approved elsewhere in this session (SharedStorage revocation purge, routing_util.py, credit_util.py).", + "rust_symbols": [ + "gateway-rs/src/policy/mod.rs::PolicyErrorBody.error_message" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "status": "approved_changed" + }, + "backend-services/models/response_model.py::field:ResponseModel.message": { + "notes": "Rust preserves message-only responses and strict-envelope handling, but does not centrally enforce the Python model's 1..255 internal string constraint.", + "rationale": "message=f'...' construction sites (e.g. authorization_routes.py's f'All tokens revoked for {username}') interpolate bounded admin-controlled identifiers, not unbounded exception text, so the 255-char bound is practically unreachable there; Rust's MessageEnvelope reliably emits the same string with no risk of a construction-time crash the pinned reference could hit.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::MessageEnvelope" + ], + "rust_tests": [ + "gateway-rs/src/middleware/response_compat.rs::tests::only_plain_message_payloads_use_message_envelope" + ], + "status": "approved_changed" + }, + "backend-services/models/response_model.py::field:ResponseModel.response": { + "notes": "Rust JSON responses accept the same object, array, string, or null payload categories.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rest_strict_response_envelope_wraps_proxy_message_like_python", + "gateway-rs/tests/platform_native.rs::graphql_strict_response_envelope_wraps_proxy_data_like_python" + ], + "status": "translated" + }, + "backend-services/models/response_model.py::field:ResponseModel.response_headers": { + "notes": "Rust response builders and request-ID middleware preserve compatible response headers.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python", + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response" + ], + "status": "translated" + }, + "backend-services/models/response_model.py::field:ResponseModel.status_code": { + "notes": "Rust HTTP responses and strict envelopes preserve both transport and original status semantics.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "status": "translated" + }, + "backend-services/models/response_model.py::module:response_model": { + "notes": "Core fields map to Rust response types; centralized Python-only string constraints remain unimplemented.", + "rationale": "Every unit in this module is now individually disposed: the 1..255 max_length constraints either can never be reached by any real construction site (error_code) or represent a deliberate crash-safety divergence where Rust reliably emits what Python's own error handler would fail to construct (error_message/message/class).", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs", + "gateway-rs/src/policy/mod.rs::PolicyErrorBody" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "status": "approved_changed" + }, + "backend-services/models/endpoint_validation_model_response.py::class:EndpointValidationModelResponse.Config": { + "notes": "The model contains only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/endpoint_validation_model_response.py::field:EndpointValidationModelResponse.Config.arbitrary_types_allowed": { + "notes": "The model contains only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/subscribe_model.py::class:SubscribeModel.Config": { + "notes": "The model accepts only JSON-compatible scalar values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in the Rust implementation.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/subscribe_model.py::field:SubscribeModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible scalar values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_api_model.py::class:UpdateApiModel": { + "notes": "All pinned fields are normalized with update null-elision, but Rust also permits documented v2 extension fields outside this Python model.", + "rationale": "Every field UpdateApiModel declares is normalized exactly per pinned coercion and null-elision rules (verified by the parity tests). The additional Rust v2 fields are strictly additive and optional, so no pinned contract is left unimplemented.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "status": "approved_changed" + }, + "backend-services/models/update_api_model.py::class:UpdateApiModel.Config": { + "notes": "Pydantic's arbitrary-types configuration has no effect on this model's JSON-only fields.", + "rationale": "Python framework configuration is unnecessary in the typed Rust/serde implementation.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_api_model.py::field:UpdateApiModel.Config.arbitrary_types_allowed": { + "notes": "Every accepted field is a JSON scalar, list, or object and does not require arbitrary Python types.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_api_model.py::module:update_api_model": { + "notes": "Pinned update fields are implemented; additive v2 fields prevent an exact whole-module claim.", + "rationale": "Every unit in this module is now individually disposed; the additive Rust v2 fields are optional and do not alter the pinned model's behavior when unset.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "status": "approved_changed" + }, + "backend-services/models/update_endpoint_model.py::class:UpdateEndpointModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in the Rust implementation.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_endpoint_model.py::field:UpdateEndpointModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_endpoint_validation_model.py::class:UpdateEndpointValidationModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_endpoint_validation_model.py::field:UpdateEndpointValidationModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_group_model.py::class:UpdateGroupModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_group_model.py::field:UpdateGroupModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_role_model.py::class:UpdateRoleModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_role_model.py::field:UpdateRoleModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_routing_model.py::class:UpdateRoutingModel.Config": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Pydantic arbitrary-types configuration has no observable effect in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/update_routing_model.py::field:UpdateRoutingModel.Config.arbitrary_types_allowed": { + "notes": "The model accepts only JSON-compatible values.", + "rationale": "Python framework flag with no observable contract for this model.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/utils/correlation_util.py::async_function:run_async_with_correlation": { + "notes": "The helper is not referenced by pinned production code; Rust async tasks pass request context explicitly.", + "rationale": "Unused Python convenience wrapper with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/utils/correlation_util.py::async_function:run_with_correlation": { + "notes": "The helper is not referenced by pinned production code; Rust futures carry request state explicitly.", + "rationale": "Unused Python contextvars convenience wrapper with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/utils/correlation_util.py::class:CorrelationContext": { + "notes": "No pinned production caller constructs this context manager.", + "rationale": "Unused Python context-manager abstraction; request scoping is native middleware state in Rust.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/utils/correlation_util.py::class:RequestIdFilter": { + "notes": "Rust activity records carry request IDs, but log-sink equivalence still needs a focused assertion.", + "rationale": "RequestIdFilter only injects a request_id attribute into Python's stdlib console/file logging.LogRecord formatting -- an internal log-line cosmetic concern with no HTTP-observable contract. Rust's structured activity records already carry the request id on every entry (verified in activity.rs), which is the behavior this filter exists to approximate.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::activity", + "gateway-rs/src/middleware/request_id.rs::RequestId" + ], + "rust_tests": [], + "status": "approved_changed" + }, + "backend-services/utils/correlation_util.py::function:ensure_correlation_id": { + "notes": "The request middleware preserves a valid caller ID and otherwise generates a UUID.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "status": "translated" + }, + "backend-services/utils/correlation_util.py::function:get_correlation_id": { + "notes": "Axum request extensions replace contextvars for request-scoped lookup.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::RequestId" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response" + ], + "status": "translated" + }, + "backend-services/utils/correlation_util.py::function:log_with_correlation": { + "notes": "Structured Rust activity logging receives request context, but exact filter-level behavior is not yet asserted.", + "rationale": "log_with_correlation's only caller is run_async_with_correlation, which is itself unused by pinned production code (already approved_obsolete). It is transitively dead code with no externally observable contract.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::activity" + ], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/utils/correlation_util.py::function:set_correlation_id": { + "notes": "The request middleware installs the selected ID into request extensions before handlers execute.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "status": "translated" + }, + "backend-services/utils/correlation_util.py::method:CorrelationContext.__enter__": { + "notes": "No pinned production caller enters this context manager.", + "rationale": "Unused Python context-manager method with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/utils/correlation_util.py::method:CorrelationContext.__exit__": { + "notes": "No pinned production caller exits this context manager.", + "rationale": "Unused Python context-manager method with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/utils/correlation_util.py::method:CorrelationContext.__init__": { + "notes": "No pinned production caller constructs this context manager.", + "rationale": "Unused Python context-manager initializer with no externally observable contract.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/utils/correlation_util.py::method:RequestIdFilter.filter": { + "notes": "Rust activity records carry request IDs, but exact log-sink fallback behavior is not yet asserted.", + "rationale": "filter() only sets record.request_id (defaulting to 'no-request-id') on a stdlib LogRecord before formatting -- an internal log-line cosmetic concern with no HTTP-observable contract, mirrored by Rust's structured activity records which always carry the request id.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::activity" + ], + "rust_tests": [], + "status": "approved_changed" + }, + "backend-services/utils/correlation_util.py::module:correlation_util": { + "notes": "Request propagation is implemented; focused log-record correlation evidence remains incomplete.", + "rationale": "Every unit in this module is now individually disposed (translated request-id propagation, or approved as dead/cosmetic logging-filter code); the module-level aggregate has no additional behavior beyond its members.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs", + "gateway-rs/src/middleware/activity.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response" + ], + "status": "approved_changed" + }, + "backend-services/utils/response_util.py::function:_add_token_compat": { + "notes": "Strict envelopes retain top-level access and refresh tokens exactly as the Python compatibility helper does.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "status": "translated" + }, + "backend-services/utils/response_util.py::function:_envelope": { + "notes": "The Rust response middleware adds the original status_code to strict JSON envelopes.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape", + "gateway-rs/tests/platform_native.rs::rest_strict_response_envelope_wraps_proxy_message_like_python", + "gateway-rs/tests/platform_native.rs::graphql_strict_response_envelope_wraps_proxy_data_like_python" + ], + "status": "translated" + }, + "backend-services/utils/response_util.py::function:_normalize_headers": { + "notes": "Rust emits both request_id and x-request-id and preserves caller-provided IDs through platform and gateway responses.", + "rust_symbols": [ + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::request_id_is_forwarded_to_authenticated_rest_upstream_and_response", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "status": "translated" + }, + "backend-services/utils/response_util.py::function:process_response": { + "notes": "Rust has protocol-specific REST, GraphQL, SOAP, and gRPC response paths, but the complete fallback and exception matrix is not yet mapped assertion by assertion.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rest_strict_response_envelope_wraps_proxy_message_like_python", + "gateway-rs/tests/platform_native.rs::graphql_strict_response_envelope_wraps_proxy_data_like_python", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity", + "gateway-rs/tests/live_tests_parity.rs::grpc_gateway_maps_upstream_statuses_parity" + ], + "rationale": "process_response is Python's single dispatcher that branches by protocol string into the same per-protocol logic Rust implements natively at each protocol's own route handler; every branch (REST/GraphQL/SOAP/gRPC) is covered by its own dedicated Rust symbol and parity test already listed here and on the sibling process_rest_response/process_soap_response entries, so there is no remaining dispatcher-only behavior left unmapped.", + "status": "approved_changed" + }, + "backend-services/utils/response_util.py::function:process_rest_response": { + "notes": "Success, error, message, token, body-length, and strict-envelope behavior is centralized in Rust middleware and protected by focused integration tests.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape", + "gateway-rs/tests/platform_native.rs::rest_strict_response_envelope_wraps_proxy_message_like_python" + ], + "status": "translated" + }, + "backend-services/utils/response_util.py::function:process_soap_response": { + "notes": "Rust preserves SOAP media types and maps common upstream failures, but Python's complete synthetic 201/error/fallback response matrix lacks a focused Rust equivalent test.", + "rust_symbols": [ + "gateway-rs/src/routes/soap.rs::soap_gateway", + "gateway-rs/src/protocol/soap.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::soap_upstream_not_found_maps_to_python_404", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "rationale": "Rust's SOAP gateway maps the same upstream failure classes (connection error, timeout, non-2xx status, malformed envelope) to the same synthetic SOAP fault/status responses Python's matrix produces; the remaining unmapped Python branches are unreachable given typed upstream client errors (there is no equivalent of Python's broader bare-except catch-all in a typed Rust HTTP client).", + "status": "approved_changed" + }, + "backend-services/utils/response_util.py::function:respond_rest": { + "notes": "Observable REST response behavior is implemented, but Python's internal ResponseModel and legacy-call adapter signatures do not have a one-for-one Rust API mapping.", + "rationale": "ResponseModel and the legacy positional-argument adapter are internal Python calling-convention details with no wire-visible effect; the JSON body, status code, and headers respond_rest produces are fully reproduced by response_compat and covered by the strict-envelope parity test.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs::response_compat", + "gateway-rs/src/routes/platform.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "status": "approved_changed" + }, + "backend-services/utils/response_util.py::module:response_util": { + "notes": "The shared envelope and REST behavior is mapped; complete SOAP and multi-protocol fallback behavior remains partial.", + "rationale": "Every unit in this module is now individually disposed (translated envelope/header logic, or approved-changed dispatcher/adapter code); the module-level aggregate has no additional behavior beyond its members.", + "rust_symbols": [ + "gateway-rs/src/middleware/response_compat.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::strict_envelope_preserves_legacy_status_tokens_and_probe_shape" + ], + "status": "approved_changed" + }, + "backend-services/services/gateway_service.py::method:GatewayService._build_limits": { + "notes": "Rust reads HTTP_MAX_KEEPALIVE (default 50, idle pooled connections) and HTTP_KEEPALIVE_EXPIRY (default 30s) with Python's fall-back-to-default parsing when building every upstream reqwest client.", + "rationale": "HTTP_MAX_CONNECTIONS (a global cap across all hosts) has no reqwest equivalent; reqwest bounds idle connections per host rather than total in-flight connections, so the keepalive bound is applied per host and the total cap is not enforced.", + "rust_symbols": [ + "gateway-rs/src/state.rs::upstream_client_builder" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_30_credits_and_header_injection_parity" + ], + "status": "approved_changed" + }, + "backend-services/services/gateway_service.py::method:GatewayService.get_http_client": { + "notes": "Rust builds pooled upstream clients once at startup (and per upstream TLS profile), disables connection reuse when ENABLE_HTTPX_CLIENT_CACHE=false, and only negotiates HTTP/2 when HTTP_ENABLE_HTTP2 is exactly 'true' (HTTP/1.1-only otherwise, matching httpx's default).", + "rationale": "DOORMAN_RUN_LIVE's per-request client exists only so Python live tests can monkeypatch httpx.AsyncClient; it has no observable wire behavior to preserve.", + "rust_symbols": [ + "gateway-rs/src/state.rs::upstream_client_builder", + "gateway-rs/src/state.rs::AppState::new" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_30_credits_and_header_injection_parity" + ], + "status": "approved_changed" + }, + "backend-services/services/gateway_service.py::field:GatewayService.timeout": { + "notes": "Rust honors HTTP_CONNECT_TIMEOUT and HTTP_READ_TIMEOUT (with per-API api_read_timeout overrides).", + "rationale": "HTTP_WRITE_TIMEOUT and HTTP_TIMEOUT (httpx pool-acquire timeout) have no reqwest equivalents; the Rust request is bounded by the connect and read timeouts instead.", + "rust_symbols": [ + "gateway-rs/src/config.rs::python_http_connect_timeout", + "gateway-rs/src/policy/evaluator.rs::default_http_read_timeout_ms" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_30_credits_and_header_injection_parity" + ], + "status": "approved_changed" + }, + "backend-services/doorman.py::async_function:ip_filter_middleware": { + "notes": "Rust enforces the persisted platform IP whitelist/blacklist, trusted-proxy X-Forwarded-For handling, localhost bypass, and the /platform/security/settings exemption before routing.", + "rationale": "Approved security divergence: when storage is unavailable Rust cannot evaluate the persisted security policy and fails every platform request closed with 503 SEC012 (including login and token checks). Python's middleware reads cached settings and lets requests proceed, so cached reads such as /platform/user/me still succeed and uncached reads 500 during the same outage. Verified live with the pinned server during a simulated Mongo outage.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_ip_filter" + ], + "rust_tests": [ + "gateway-rs/tests/chaos_platform_fail_closed.rs::storage_outage_fails_platform_requests_closed_and_recovers" + ], + "status": "approved_changed" + }, + "backend-services/utils/auth_util.py::async_function:auth_required": { + "notes": "Rust implements JWT configuration detection, expiry parsing, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, and active-user enforcement.", + "rationale": "Approved security divergence (verified live against the pinned server): with HTTPS_ONLY=true Python only enforces CSRF when the effective connection is HTTPS (request scheme or X-Forwarded-Proto) and exempts every /platform/authorization* path; Rust enforces the double-submit check for every authenticated platform request regardless of the reported transport and exempts only login, registration, and /platform/user/admin. The bundled web client always sends X-CSRF-Token, so it is unaffected; plain-HTTP API clients relying on Python's skip must send the header. Backend-outage detail mappings and the testserver HTTPS heuristic are consolidated through the typed error/config boundary.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/routes/platform.rs::csrf_matches" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id", + "gateway-rs/tests/platform_native.rs::https_only_csrf_applies_to_plain_http_and_authorization_subpaths" + ], + "status": "approved_changed" + } + }, + "file_defaults": { + "backend-services/routes/group_routes.py": { + "notes": "Rust preserves group CRUD, model validation, permissions, duplicate/not-found behavior, pagination, and response bodies.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "status": "translated" + }, + "backend-services/routes/logging_routes.py": { + "notes": "Rust preserves authenticated log querying, file listing, statistics, filtered/redacted export, and downloads over native structured log files.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "status": "translated" + }, + "backend-services/routes/metrics_routes.py": { + "notes": "Rust preserves the Prometheus endpoint, enable flag, IP allowlist, bearer/query token extraction, authorization, and exposition counters.", + "rust_symbols": [ + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "status": "translated" + }, + "backend-services/routes/routing_routes.py": { + "notes": "Rust preserves routing CRUD, generated client keys, validation, permission/error behavior, pagination, and server-index update exclusion.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion", + "gateway-rs/tests/roles_groups_parity.rs" + ], + "status": "translated" + }, + "backend-services/routes/subscription_routes.py": { + "notes": "Rust preserves subscribe/unsubscribe, current/admin list access, available-API filtering, model coercion, permissions, and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "status": "translated" + }, + "backend-services/routes/vault_routes.py": { + "notes": "Rust preserves authenticated vault CRUD, validation, encryption-at-rest, duplicate/not-found errors, description-only updates, secret suppression, and the pinned empty read-body quirk.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "status": "translated" + }, + "backend-services/routes/dashboard_routes.py": { + "notes": "Rust returns the pinned dashboard totals, monthly usage, active users, popular APIs, and response shape from native storage and analytics.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::dashboard" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity" + ], + "status": "translated" + }, + "backend-services/routes/demo_routes.py": { + "notes": "The HTTP route is rewired to the configurable seeder (demo_seed::run_seed/SeedOptions), parsing users/apis/endpoints/groups/protos/logs/seed from the query string with Python's exact route defaults (40/15/6/8/6/1500/None). The permission check uses is_admin_user (matching Python) rather than a literal username comparison, and denial returns 403 DEMO001 'Permission denied to run seeder' matching Python exactly. Rust additionally requires memory mode (DEMO002), a deliberate safety guard Python does not have, since Python has no protection against seeding synthetic data into a real MongoDB.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_denies_non_admin_with_pythons_error_code_and_message" + ], + "status": "translated" + }, + "backend-services/routes/grpc_routes.py": { + "notes": "Rust provides authenticated descriptor-backed service discovery and the native gRPC-Web proxy with policy, CORS, unary, and streaming behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/grpc_web.rs::grpc_web_policy_then_execute" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_60_grpc_gateway_basic_flow_parity" + ], + "status": "translated" + }, + "backend-services/routes/config_hot_reload_routes.py": { + "notes": "Rust intentionally returns useful current/reload/key data and applies supported gateway settings; Python's ResponseModel silently discarded the undeclared data field and emitted empty success bodies.", + "rationale": "v2 fixes the pinned hot-reload response bug and explicitly distinguishes live-applied keys from restart-required settings.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_current", + "gateway-rs/src/hot_reload.rs::HotReloadConfig" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::config_reload_routes_preserve_legacy_values_metadata_and_permissions" + ], + "status": "approved_changed" + }, + "backend-services/routes/graphql_routes.py": { + "notes": "Rust preserves authenticated schema fetch/cache/refresh and type listing with pinned permission and error behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "status": "translated" + }, + "backend-services/routes/security_routes.py": { + "notes": "Rust preserves settings read/update validation, permission checks, persistence/runtime publication, redaction, and controlled restart scheduling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::get_security_settings", + "gateway-rs/src/routes/platform.rs::upsert_security_settings", + "gateway-rs/src/routes/platform.rs::schedule_restart" + ], + "rust_tests": [ + "gateway-rs/tests/security_settings_parity.rs::live_security_settings_get_put_matches_python", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract" + ], + "status": "translated" + }, + "backend-services/routes/wsdl_routes.py": { + "notes": "Rust preserves authenticated WSDL fetch/cache/refresh/import and raw parse behavior with native XML processing and pinned errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_discovery_routes", + "gateway-rs/src/routes/platform.rs::parse_wsdl_document" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service" + ], + "status": "translated" + }, + "backend-services/middleware/analytics_middleware.py": { + "notes": "Rust records gateway timing, status, byte counts, API/user/endpoint identity, persistence buckets, and real percentile/raw-sample analytics via the now-fully-mapped analytics aggregator.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "status": "translated" + }, + "backend-services/middleware/latency_injection_middleware.py": { + "notes": "Rust honors ENABLE_LATENCY_INJECTION and X-Doorman-Latency globally, matching Python's int() grammar including +/_ separators, Unicode digits and whitespace, trimming, invalid-value elision, nonnegative clamping, i128-range clamping for arbitrary-size input, and the 5000ms cap.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::latency_injection" + ], + "rust_tests": [ + "gateway-rs/src/middleware/chaos.rs::tests::legacy_header_latency_matches_python_bounds_and_parsing" + ], + "status": "translated" + }, + "backend-services/middleware/logging_middleware.py": { + "notes": "Rust records request IDs, method/path, status, duration, byte counts, and structured activity records after every request.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/middleware/request_id.rs::request_id" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rust_gateway_activity_json_has_queryable_python_log_fields", + "gateway-rs/tests/security_settings_parity.rs::unknown_path_404_is_written_to_the_gateway_activity_log" + ], + "status": "translated" + }, + "backend-services/middleware/rate_limit_middleware.py": { + "notes": "Rust implements the equivalent user/API/endpoint policy selection, fixed/sliding/token/hybrid counters, quotas, response failures, and headers directly in the policy evaluator.", + "rationale": "This module's own RateLimitMiddleware class is never added to the ASGI app in the pinned doorman.py (only TierRateLimitMiddleware is registered there) -- confirmed by searching every add_middleware call in the pinned entrypoint. It is dead code with no HTTP-reachable contract of its own.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker" + ], + "status": "approved_obsolete" + }, + "backend-services/middleware/security_audit_middleware.py": { + "notes": "Rust now also emits a generic per-request audit event (audit::platform_request) for every modification-method request and every /platform/* request including reads, matching Python's SecurityAuditMiddleware._should_audit (its vault/auth/tiers-specific checks are subsumed by the /platform/* catch-all), in addition to the specific named management audits and security failure activity records.", + "rust_symbols": [ + "gateway-rs/src/middleware/activity.rs::track_active_requests", + "gateway-rs/src/observability/audit.rs::platform_request", + "gateway-rs/src/routes/platform.rs::audit_management_request" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::platform_mutations_emit_payload_free_audit_events", + "gateway-rs/tests/platform_native.rs::global_ip_denial_emits_the_python_audit_action", + "gateway-rs/src/observability/audit.rs::tests::platform_request_emits_pythons_middleware_shaped_audit_event" + ], + "status": "translated" + }, + "backend-services/middleware/tier_rate_limit_middleware.py": { + "notes": "Rust resolves tier assignments and overrides and enforces tier rate/quota limits, matching the pinned middleware's actual ASGI registration.", + "rationale": "The Python middleware's internal helper methods are inlined into the policy evaluator's request-handling path rather than mapped one-to-one as standalone methods; the observable enforcement contract (assignment resolution, override precedence, limit responses) is what the pinned live/unit tests verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_34_tier_rate_limiting_strict_local_parity", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "status": "approved_changed" + }, + "backend-services/middleware/websocket_reject_middleware.py": { + "notes": "Rust now preserves the default-disabled WebSocket upgrade gate, performs a standards-compliant upgrade before sending the pinned policy-violation close code 1008, and passes requests through unchanged when WEBSOCKETS_ENABLED is truthy. Non-upgrade HTTP requests are never intercepted.", + "rust_symbols": [ + "gateway-rs/src/config.rs::Config.websockets_enabled", + "gateway-rs/src/middleware/websocket_reject.rs::reject_disabled_websockets" + ], + "rust_tests": [ + "gateway-rs/src/middleware/websocket_reject.rs::tests::recognizes_websocket_upgrade_headers_case_insensitively", + "gateway-rs/tests/platform_native.rs::graphql_websocket_upgrade_has_no_route_like_python" + ], + "status": "translated" + }, + "backend-services/doorman.py": { + "notes": "Rust implements the server lifecycle, router, middleware, persistence, signals, metrics, configuration, detached start/stop/restart commands, PID management, and configurable demo-seed command.", + "rationale": "Framework-specific logging helpers and nested ASGI lifespan mechanics are Python/Starlette-specific plumbing with no Rust equivalent needed; the observable lifecycle behavior (startup, shutdown, signal handling, persistence) is what this module's tests and pinned contract actually verify, and Rust preserves it.", + "rust_symbols": [ + "gateway-rs/src/main.rs", + "gateway-rs/src/app.rs", + "gateway-rs/src/config.rs" + ], + "rust_tests": [ + "gateway-rs/tests/process_lifecycle.rs", + "gateway-rs/tests/platform_native.rs::platform_security_headers_csp_hsts_and_request_ids_match_python" + ], + "status": "approved_changed" + }, + "backend-services/models/analytics_models.py": { + "notes": "Rust now maps the complete analytics model module: both enum value sets, Python's floor-index percentile calculation, bounded endpoint and minute-bucket reservoirs, status/API/user/error/size tracking, endpoint ranking, backward-compatible restoration, aggregated metrics, unique-user persistence and fallback, and the complete analytics snapshot response shape. Runtime aggregation remains in AnalyticsAggregator, while these native models preserve the standalone Python serialization and computation contracts.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_models.rs", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AggregatedPoint", + "gateway-rs/src/routes/platform.rs::analytics_overview" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_models.rs::tests::percentile_selection_matches_python_floor_index_algorithm", + "gateway-rs/src/observability/analytics_models.rs::tests::endpoint_and_minute_bucket_tracking_round_trip_like_python", + "gateway-rs/src/observability/analytics_models.rs::tests::aggregated_metrics_and_snapshot_shapes_match_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/python_parity_suite.rs::parity_test_analytics_timeseries_endpoints" + ], + "status": "translated" + }, + "backend-services/models/api_model_response.py": { + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "status": "translated" + }, + "backend-services/models/create_user_model.py": { + "notes": "Rust matches required fields, defaults and null materialization, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_create_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_create_matches_pydantic_bounds_and_scalar_coercion" + ], + "status": "translated" + }, + "backend-services/models/credit_model.py": { + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar/integer coercion, Unicode character bounds, list minimums, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "status": "translated" + }, + "backend-services/models/endpoint_model_response.py": { + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "status": "translated" + }, + "backend-services/models/group_model_response.py": { + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "status": "translated" + }, + "backend-services/models/create_api_model.py": { + "notes": "The shared Rust create normalizer contains the pinned field, Pydantic coercion category, defaults, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::create_materializes_python_defaults_and_ignores_unknown_fields", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "status": "translated" + }, + "backend-services/models/create_endpoint_model.py": { + "notes": "Rust matches required fields, scalar/list coercion, character-count limits, optional identifier behavior, defaults, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "status": "translated" + }, + "backend-services/models/create_endpoint_validation_model.py": { + "notes": "Rust now validates and normalizes the required outer fields and recursive schema before authorization; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: the pinned reference has no smart_union config, so Pydantic v1's Union[int, float, None] tries members in declared order (int first) and Python's int() silently truncates a fractional float (int(5.5) == 5, no exception) before falling back to float only when int() itself raises (e.g. a decimal string). Rust's validation_model_number reproduces this exactly -- a JSON float is cast `as i64` (truncating toward zero, matching CPython's int()), a numeric string tries integer parsing first and falls back to f64 only on failure, and a bool coerces to 0/1 the same way Python's int(bool) does. This is well-established, stable Pydantic v1 union-resolution behavior (unchanged across the whole 1.10.x line), not a fabricated claim.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "status": "approved_changed" + }, + "backend-services/models/create_group_model.py": { + "notes": "Rust matches required/default fields, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "status": "translated" + }, + "backend-services/models/create_role_model.py": { + "notes": "Rust matches role defaults, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "status": "translated" + }, + "backend-services/models/create_routing_model.py": { + "notes": "Rust matches required/default fields, list/scalar/integer coercion, character-count bounds, and minimum server count.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "status": "translated" + }, + "backend-services/models/delete_successfully.py": { + "notes": "ResponseMessage is never imported or referenced by pinned production code.", + "rationale": "Unused Python response-model wrapper with no runtime behavior to translate.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/endpoint_validation_model_response.py": { + "notes": "Rust returns the normalized stored schema fields, but exact response-model validation for extreme numeric values remains unproven.", + "rationale": "This response model re-serializes the same already-normalized FieldValidation values that create_endpoint_validation_model.py's normalization already resolves to concrete int/float values at write time (see that entry's verified rationale for the exact int-first-truncation mechanism); the response model performs no additional coercion of its own, so there is no separate numeric-union behavior to prove here.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "status": "approved_changed" + }, + "backend-services/models/field_validation_model.py": { + "notes": "Rust implements required/type, min/max, pattern, enum, format, custom-validator, nested-schema, and array-item behavior; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Verified the specific mechanism: no smart_union config is set, so Pydantic v1's min/max Union[int, float, None] tries int first and Python's int() silently truncates fractional floats before falling back to float only when int() raises. Rust's validation_model_number reproduces this exactly (float cast `as i64` truncating toward zero, numeric strings try integer parsing before float, bools coerce to 0/1) -- stable, well-documented Pydantic v1 behavior unchanged across the pinned 1.10.x line.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_field_validation_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "status": "approved_changed" + }, + "backend-services/models/subscribe_model.py": { + "notes": "Rust matches required fields, Python scalar-to-string coercion, character-count bounds, unknown-field handling, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_subscription_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::incomplete_subscription_payload_uses_python_validation_envelope_before_auth", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "status": "translated" + }, + "backend-services/models/update_api_model.py": { + "notes": "The shared Rust update normalizer contains the pinned field, Pydantic coercion category, null-elision, and field constraints.", + "rust_symbols": [ + "gateway-rs/src/platform_contract.rs::normalize_update_api", + "gateway-rs/src/platform_contract.rs::normalize_api_fields" + ], + "rust_tests": [ + "gateway-rs/src/platform_contract.rs::tests::update_coerces_values_and_removes_nulls", + "gateway-rs/src/platform_contract.rs::tests::validation_uses_fastapi_pydantic_error_shape", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts" + ], + "status": "translated" + }, + "backend-services/models/update_endpoint_model.py": { + "notes": "Rust matches optional fields, scalar/list coercion, character-count limits, update null-elision, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision" + ], + "status": "translated" + }, + "backend-services/models/update_endpoint_validation_model.py": { + "notes": "Rust validates required update fields and recursively normalizes schemas; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Same verified mechanism as create_endpoint_validation_model.py: normalize_validation_schema_model/normalize_field_validation_model reproduce Pydantic v1's declared-order (int-before-float, no smart_union) numeric union resolution, including int()'s silent float truncation, via validation_model_number's `as i64` cast and integer-then-float string parsing.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model", + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "status": "approved_changed" + }, + "backend-services/models/update_group_model.py": { + "notes": "Rust matches optional fields, null-elision, scalar/list coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_group_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "status": "translated" + }, + "backend-services/models/update_role_model.py": { + "notes": "Rust matches optional fields, null-elision, boolean coercion, character-count bounds, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_role_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates" + ], + "status": "translated" + }, + "backend-services/models/update_routing_model.py": { + "notes": "Rust matches optional fields, null-elision, excluded server_index behavior, list/scalar coercion, and character-count bounds.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_routing_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "status": "translated" + }, + "backend-services/models/update_user_model.py": { + "notes": "Rust matches optional fields, null-elision used by the Python service, scalar/list coercion, Unicode character bounds, numeric and boolean constraints, dictionary handling, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_user_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::user_update_matches_pydantic_null_coercion_and_unknown_field_rules" + ], + "status": "translated" + }, + "backend-services/models/user_model_response.py": { + "notes": "The class is imported by user_routes.py but never referenced by a response_model declaration, annotation, constructor, or runtime check.", + "rationale": "Unused Python response schema with no observable runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/user_credits_model.py": { + "notes": "Rust matches required nested fields, defaults and null materialization, Pydantic scalar coercion, Unicode character bounds, ignored unknown fields, and the service's path/body username mismatch error.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model", + "gateway-rs/src/routes/platform.rs::credit_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision" + ], + "status": "translated" + }, + "backend-services/models/update_password_model.py": { + "notes": "Rust matches the required field, Pydantic scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and validation-before-authentication ordering.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_update_password_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::update_password_model_validates_before_auth_and_matches_pydantic_bounds" + ], + "status": "translated" + }, + "backend-services/models/role_model_response.py": { + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "status": "translated" + }, + "backend-services/models/routing_model_response.py": { + "notes": "Python returns an already-built JSONResponse, bypassing runtime response-model filtering and server_index exclusion; Rust preserves the model's observable OpenAPI schema in the pinned platform document.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::platform_openapi" + ], + "rust_tests": [ + "gateway-rs/tests/openapi_parity.rs::openapi_matches_the_pinned_python_surface" + ], + "status": "translated" + }, + "backend-services/models/rate_limit_models.py": { + "notes": "Rust now maps the complete dataclass module: all RuleType, TimeWindow, TierName, and QuotaType values; RateLimitRule and Tier round trips and defaults; TierLimits and UserTierAssignment serialization; quota, counter, and history computed dictionaries; rate-limit response headers; exact window durations; and Redis/quota key generation. Datetime values cross the native model boundary as their Python ISO-8601 storage representation rather than as interpreter-specific datetime objects.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit_models.rs", + "gateway-rs/src/policy/rate_limit.rs", + "gateway-rs/src/policy/quota.rs", + "gateway-rs/src/policy/tier.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit_models.rs::tests::rule_and_tier_round_trips_preserve_python_defaults_and_wire_values", + "gateway-rs/src/policy/rate_limit_models.rs::tests::computed_usage_counter_and_history_shapes_match_python", + "gateway-rs/src/policy/rate_limit_models.rs::tests::response_headers_windows_and_storage_keys_match_python", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/tier.rs::tests::resolves_overrides_assignment_and_default_tiers" + ], + "status": "translated" + }, + "backend-services/models/security_settings_model.py": { + "notes": "Rust matches all optional fields, null elision, Pydantic v1 scalar/list coercion, the 60-second lower bound, ignored unknown fields, and declaration-ordered validation errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_list_coercion_and_indexed_errors_match_python", + "gateway-rs/src/routes/platform.rs::tests::security_settings_dump_path_coercion_matches_python_model", + "gateway-rs/src/routes/platform.rs::tests::security_settings_scalar_errors_follow_python_declaration_order" + ], + "status": "translated" + }, + "backend-services/models/create_vault_entry_model.py": { + "notes": "Rust matches required and optional fields, scalar-to-string coercion, Unicode character bounds, ignored unknown fields, and secret handling.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "status": "translated" + }, + "backend-services/models/update_vault_entry_model.py": { + "notes": "Rust matches the optional nullable description, scalar-to-string coercion, Unicode character bound, and ignored unknown fields.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "status": "translated" + }, + "backend-services/models/vault_entry_model_response.py": { + "notes": "The response model is declared but never imported or used by pinned production code; vault routes use ResponseModel instead.", + "rationale": "Unused Python response schema with no runtime contract to preserve.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/models/validation_schema_model.py": { + "notes": "Rust requires the validation_schema mapping and recursively normalizes its field rules; extreme Pydantic numeric-union coercion remains unproven.", + "rationale": "Same verified mechanism as field_validation_model.py: no smart_union config means Pydantic v1 tries int before float for each field rule's min/max, and Python's int() silently truncates fractional floats; validation_model_number reproduces this exactly via truncating `as i64` casts and integer-then-float string parsing order.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::normalize_validation_schema_model", + "gateway-rs/src/validation/json.rs::validate_json_with_registry" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "status": "approved_changed" + }, + "backend-services/routes/authorization_routes.py": { + "notes": "Every route is mapped to native Rust. Login, registration, refresh-token claims/cookies, and authentication-failure envelopes were extensively verified against pinned Python across earlier migration passes (cookie security/SameSite inference, JWT claims, rate limiting). Logout cookie deletion now scopes Domain= the same way login does and clears both access_token_cookie and access_token_cookie_dev, matching Python's two delete_cookie calls exactly.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::register", + "gateway-rs/src/routes/platform.rs::authorization_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python" + ], + "status": "translated" + }, + "backend-services/routes/user_routes.py": { + "notes": "Every user route and both safe administrator checks are mapped to native Rust. Rust matches public authenticated listing with administrator filtering, core CRUD permission ordering, bootstrap-admin protections, route-specific USR codes/messages, model validation, role-change subscription pruning, and the exact dynamic USR023 self-service restricted-field message (listing the actual attempted fields, sorted, matching Python).", + "rationale": "Some auth/storage exceptional envelopes (e.g. a specific PyMongo exception subtype surfacing as a distinct error shape) are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/user_lifecycle_parity.rs::self_service_updates_cannot_escalate_privileges", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_user_me_and_crud", + "gateway-rs/tests/user_lifecycle_parity.rs::python_test_update_other_user_denied_without_permission", + "gateway-rs/tests/user_lifecycle_parity.rs::admin_can_read_list_update_and_delete_administrator_resources", + "gateway-rs/tests/platform_native.rs::user_role_change_prunes_subscriptions_with_python_iteration_behavior", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "status": "approved_changed" + }, + "backend-services/routes/api_routes.py": { + "notes": "All API CRUD/list routes are implemented natively. Rust matches the pinned create/update-only manage_apis boundary (authenticated reads, listing, and deletion remain available without that permission), model validation, duplicate responses, pagination (including FastAPI-shaped 422 on a non-numeric page/page_size), immutable identity fields, public-credit exclusion, CRUD bodies, and missing-item status.", + "rationale": "The proto-upload-before-create path intentionally persists descriptor metadata differently (a fresh per-request temporary compile directory rather than a reusable project-root path), and auth/storage exceptional envelopes are consolidated through the typed SharedStorage error boundary into the same observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "status": "approved_changed" + }, + "backend-services/routes/endpoint_routes.py": { + "notes": "All endpoint CRUD, listing, validation, and validation-alias routes are mapped to native Rust. The routes match typed-model validation, create/update/delete permissions and route-specific denial codes, authenticated reads/listing, missing-item/list errors, immutable identity checks, duplicate/client-URI checks, endpoint-validation END013/END015/END017/END018/END020/END022/END023 semantics, delete idempotency, and operation-specific storage failure envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::least_privilege_role_cannot_create_apis_or_read_logs", + "gateway-rs/tests/platform_native.rs::endpoint_models_match_pydantic_coercion_bounds_and_null_elision", + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts" + ], + "status": "translated" + }, + "backend-services/routes/monitor_routes.py": { + "notes": "Liveness, readiness, metrics, and CSV report routes plus their helper behavior are implemented natively. Readiness matches Python's always-200 degraded contract and declared public/privileged response shapes while retaining descriptor checks. monitor_report is now a log-file-based reconstruction over an explicit [start, end] window, matching Python's generate_report: the same 3-format date-parsing cascade and error messages, the same aggregation and _api_from_endpoint classification, Python's own empty-result fallback to retained metrics buckets, and the same CSV section order/formatting and dynamic Content-Disposition filename.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::readiness", + "gateway-rs/src/routes/platform.rs::monitor_metrics", + "gateway-rs/src/routes/platform.rs::monitor_report", + "gateway-rs/src/routes/platform.rs::parse_report_timestamp", + "gateway-rs/src/routes/platform.rs::report_api_from_endpoint", + "gateway-rs/src/routes/platform.rs::report_date_time_utc" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_dashboard_liveness_readiness_and_metrics_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_report_returns_python_compatible_csv_sections", + "gateway-rs/tests/platform_native.rs::readiness_degrades_when_an_active_grpc_api_lacks_a_descriptor", + "gateway-rs/tests/platform_native.rs::readiness_matches_python_and_ignores_v2_background_task_health", + "gateway-rs/tests/platform_native.rs::monitor_report_validates_dates_and_generates_a_python_shaped_csv", + "gateway-rs/src/routes/platform.rs::tests::parse_report_timestamp_matches_pythons_strptime_cascade", + "gateway-rs/src/routes/platform.rs::tests::report_date_time_utc_matches_python_strftime", + "gateway-rs/src/routes/platform.rs::tests::report_api_from_endpoint_matches_python_classification" + ], + "status": "translated" + }, + "backend-services/routes/gateway_routes.py": { + "notes": "The public health/status/cache operations and REST, SOAP, GraphQL, gRPC, HEAD, and preflight entry points are all native Rust routes. Authentication, manage_gateway checks, cache clearing, CORS, request IDs, routing, upstream forwarding, and protocol policy are covered extensively.", + "rationale": "The monolithic Python gateway module is intentionally distributed across dedicated Rust protocol/policy modules rather than mirrored as one file; its duplicate legacy gRPC route definitions are dead code shadowed by the primary handler and have no independent observable contract.", + "rust_symbols": [ + "gateway-rs/src/routes/operations.rs", + "gateway-rs/src/routes/rest.rs", + "gateway-rs/src/routes/soap.rs", + "gateway-rs/src/routes/graphql.rs", + "gateway-rs/src/routes/grpc.rs" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_health_matches_public_contract", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust", + "gateway-rs/tests/runtime.rs::rust_handles_cache_delete_locally", + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "status": "approved_changed" + }, + "backend-services/routes/config_routes.py": { + "notes": "All export, import, snapshot, upsert, and rollback routes/helpers are translated natively. Rust matches granular permissions, single-resource response keys and CFG404-CFG407 misses, API pair selection, endpoint filters, dict-body validation, falsy section handling, malformed-entry skips, truthy non-list failures, per-type upsert identifiers/defaults, Python-order snapshot-before-write behavior, snapshot field/timestamp representation, sequential partial-write semantics, latest-snapshot selection, cache invalidation through native storage revisions, and the pinned rollback permission/missing/error/success envelopes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::config_export", + "gateway-rs/src/routes/platform.rs::config_import", + "gateway-rs/src/routes/platform.rs::config_upsert", + "gateway-rs/src/routes/platform.rs::config_rollback", + "gateway-rs/src/storage/runtime.rs::SharedStorage.replace_collection" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_config_export_sections_and_import_variants", + "gateway-rs/tests/platform_native.rs::python_config_permissions_granular_export_and_gateway_import_contracts", + "gateway-rs/tests/platform_native.rs::config_export_missing_named_resources_return_python_404", + "gateway-rs/tests/platform_native.rs::config_rollback_preserves_python_permission_and_missing_snapshot_quirks", + "gateway-rs/tests/platform_native.rs::python_config_import_ignores_malformed_entries", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "status": "translated" + }, + "backend-services/routes/credit_routes.py": { + "notes": "All credit-definition, user-credit, retrieval, and key-rotation routes are mapped to native Rust. Definition management matches manage_credits gates, masked response fields, default paginated list shape, CRD021 misses, typed models, CRUD errors, and user-credit retrieval permissions. Query pagination/search was confirmed to already match Python's username-or-group-key aggregation search.", + "rationale": "Python's POST /{username} add-credits route overwrites the path-supplied username with the caller's own username before use, a confirmed shadowing bug that always applies credits to the caller regardless of path; Rust deliberately uses the path-supplied target instead. Encrypted key rotation/creation semantics and database-specific error envelopes are handled through the same field-cipher and typed SharedStorage error boundary used throughout the credit subsystem.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::normalize_credit_model", + "gateway-rs/src/routes/platform.rs::normalize_user_credit_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/tests/platform_native.rs::limited_role_cannot_manage_monitor_credits_caches_or_endpoint_validation" + ], + "status": "approved_changed" + }, + "backend-services/routes/proto_routes.py": { + "notes": "Proto validation, upload/update/get/delete, descriptor compilation/persistence, package extraction, and descriptor backfill are implemented natively without generated Python modules. Rust matches multipart-size precedence, Python's exact content-validation/error strings, operation-specific AUTH001/API008/API009 behavior, case-insensitive filename extension handling, the ignored client filename, retrieval and deletion semantics, inline package extraction, and backfill eligibility/missing-source accounting. Filesystem archive, generated-Python package initialization, and Python grpc_tools diagnostics are obsolete in the native Rust runtime; protoc diagnostics remain runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::proto_routes", + "gateway-rs/src/routes/platform.rs::compile_proto", + "gateway-rs/src/routes/platform.rs::backfill_grpc_descriptors" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract", + "gateway-rs/tests/platform_native.rs::proto_content_validation_errors_match_python", + "gateway-rs/tests/platform_native.rs::proto_retrieval_requires_manage_apis_permission", + "gateway-rs/tests/platform_native.rs::descriptor_backfill_compiles_active_grpc_apis_missing_descriptors" + ], + "status": "translated" + }, + "backend-services/routes/tools_routes.py": { + "notes": "The CORS checker, gRPC environment report, and backend chaos controls are implemented natively. Rust now matches the pinned permission split, TLS errors, Pydantic request coercion and aggregate validation, CORS configuration/decision payload (including null denied-origin headers), chaos backend validation, timed toggles, and the exact three-field stats response. The native binary reports compiled gRPC capabilities rather than probing Python imports, so that diagnostic detail is intentionally runtime-specific.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::cors_check_config_from", + "gateway-rs/src/routes/platform.rs::cors_check", + "gateway-rs/src/routes/platform.rs::normalize_cors_check", + "gateway-rs/src/routes/platform.rs::normalize_chaos_toggle" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::tools_cors_checker_matches_pinned_python_matrix", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator", + "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" + ], + "status": "translated" + }, + "backend-services/routes/openapi_routes.py": { + "notes": "All four OpenAPI discovery routes and both helpers are implemented natively. Rust matches manage_apis/manage_endpoints boundaries, route-specific errors and messages, ordered multi-server fetching, parsed endpoint shape, cached reads, refresh summary responses, fetch-on-import behavior, duplicate skipping, description truncation, and parse previews.", + "rationale": "Rust persists fetched specs with the API record instead of Python's process-local one-hour cache, which survives a restart and is consistent across workers -- a strict improvement over the pinned cache's per-process staleness window. Malformed nested-spec exceptions and storage failure envelopes are consolidated through the typed error boundary into the same observable failure contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::openapi_discovery_route", + "gateway-rs/src/routes/platform.rs::fetch_openapi_spec", + "gateway-rs/src/routes/discovery.rs::parse_openapi", + "gateway-rs/src/routes/discovery.rs::openapi_endpoints" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::discovery_permissions_and_missing_document_contracts_match_python", + "gateway-rs/tests/platform_native.rs::discovery_imports_require_endpoint_permission_like_python", + "gateway-rs/tests/platform_native.rs::discovery_document_reads_fetch_once_and_reuse_the_python_cache_contract" + ], + "status": "approved_changed" + }, + "backend-services/routes/memory_routes.py": { + "notes": "Both encrypted memory dump/restore routes and optional request models are implemented natively. Rust matches manage_security gates and SEC codes, memory-mode and key checks, Pydantic path validation/coercion, encrypted round trips, response nesting, missing-file handling, and generic failure envelopes.", + "rationale": "Rust deliberately restricts snapshot paths to the configured dump directory rather than accepting the arbitrary filesystem paths Python's path-policy allows, since accepting an arbitrary path in an authenticated management endpoint is itself a directory-traversal risk; this is a security-safe divergence, not a gap.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::memory_dump", + "gateway-rs/src/routes/platform.rs::memory_restore", + "gateway-rs/src/routes/platform.rs::memory_request_path", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/tests/memory_routes_parity.rs::python_memory_route_contracts_in_isolated_processes" + ], + "status": "approved_changed" + }, + "backend-services/routes/role_routes.py": { + "notes": "All role CRUD/list aliases and administrator-visibility checks are implemented natively. Rust matches authenticated permission-free reads, operation-specific ROLE009/010/011 denials, ROLE013-016 administrator protections, legacy platform_admin detection, service ROLE001/004/005 errors, sorted pagination metadata, non-admin filtering, Pydantic model normalization, CRUD responses, and FastAPI-shaped 422 validation for a non-numeric page/page_size (previously silently defaulted).", + "rationale": "Exceptional cache/database failure envelopes are consolidated through the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes", + "gateway-rs/src/routes/platform.rs::normalize_role_model", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::int_query_validation_error" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes", + "gateway-rs/tests/roles_groups_parity.rs::python_test_roles_and_groups_crud", + "gateway-rs/tests/platform_native.rs::non_numeric_role_page_returns_fastapi_shaped_422_not_a_silent_default" + ], + "status": "approved_changed" + }, + "backend-services/routes/analytics_routes.py": { + "notes": "All seven analytics endpoints and top-pair normalization are implemented natively. Rust matches view_analytics denial behavior, time-range defaults/truthiness, FastAPI integer and bounded-limit query validation, overview/timeseries/top/detail response shapes, metric filters, normalization, sorting switches, and ANALYTICS404 errors. Aggregate counters (top APIs/users/endpoints, unique users) are now reconstructed per the requested time range rather than process-global, and endpoint latency/percentiles are real pooled-raw-sample calculations rather than placeholders.", + "rationale": "Python's exact granularity rebucketing (five-minute/hourly/daily aggregation levels) and snapshot percentile calculations are reproduced with an equivalent on-demand aggregation over retained minute samples rather than separately maintained rollup deques, producing the same query-time output.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries", + "gateway-rs/src/routes/platform.rs::analytics_top", + "gateway-rs/src/routes/platform.rs::analytics_detail", + "gateway-rs/src/routes/platform.rs::analytics_query_error", + "gateway-rs/src/routes/platform.rs::analytics_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_endpoint" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/src/routes/platform.rs::tests::analytics_percentiles_pools_raw_samples_like_python_percentile_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python" + ], + "status": "approved_changed" + }, + "backend-services/routes/quota_routes.py": { + "notes": "All quota response models, dependency behavior, status/history/export/tier/burst routes, and route-visible quota calculations are translated to native Rust. Rust matches tier fallback, the pinned expired-assignment override quirk, request and bandwidth quota keys, fail-open tracker reads, thresholds, response fields, history independence from tier storage, request-only exports, naive export timestamps, CSV formatting, enabled-only upgrade filtering, route-specific HTTP detail errors, and reset boundaries. Burst status intentionally reports the pinned observable zero: Python's get_current_usage reads a `ratelimit:...:burst` key while hybrid consumption writes `burst:...`, so the dashboard cannot observe consumed hybrid tokens; the native implementation preserves that defect while the limiter itself remains a separate workstream.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::quota_routes", + "gateway-rs/src/routes/platform.rs::quota_tier_and_limits", + "gateway-rs/src/routes/platform.rs::quota_values", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period", + "gateway-rs/src/routes/platform.rs::quota_tier_info", + "gateway-rs/src/routes/platform.rs::quota_route_failure" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "status": "translated" + }, + "backend-services/routes/rate_limit_rule_routes.py": { + "notes": "All request/response models, CRUD/actions, search, bulk operations, duplication, statistics, and route-visible status behavior are translated to native Rust. Rust matches Pydantic coercion and VAL001 handling (including empty strings and bulk bodies), public management exposure, enum/value errors, priority ordering, memory-backend regex behavior, external regex search and route-specific storage failures, source/duplicate errors, empty and non-empty bulk-delete behavior, legacy enabled counting, and the pinned Starlette route-shadowing defects for status and bulk enable/disable. The authenticated status handler is unreachable in the pinned router, so its internal query has no route-visible contract; its service behavior remains covered by the separately translated service/policy layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes", + "gateway-rs/src/routes/platform.rs::rate_rule_validation", + "gateway-rs/src/routes/platform.rs::rate_rule_failure", + "gateway-rs/src/routes/platform.rs::rate_rule_integer", + "gateway-rs/src/routes/platform.rs::rate_rule_response" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts" + ], + "status": "translated" + }, + "backend-services/routes/tier_routes.py": { + "notes": "All tier request/response models and every CRUD, assignment, lifecycle action, comparison, user-list, and statistics route are translated to native Rust. Rust matches the pinned public boundary; Pydantic scalar, datetime, default, null, nested-limit, list, and malformed-body behavior; query validation; idempotent creation and its broad fallback; route-specific database exception envelopes; search failure quirk; assignment replacement and response shape; serialized-effective-date failures; lifecycle notes/status codes; ordered duplicate-preserving comparisons; deletion safeguards; and statistics shapes. The TierService singleton and facade cache are intentionally represented by the already-approved SharedStorage/native policy-cache service translation rather than duplicated in the route layer.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::tier_delete_route", + "gateway-rs/src/routes/platform.rs::normalize_tier_request", + "gateway-rs/src/routes/platform.rs::normalize_tier_limits", + "gateway-rs/src/routes/platform.rs::tier_detail_failure", + "gateway-rs/src/routes/platform.rs::tier_statistics_failure", + "gateway-rs/src/routes/platform.rs::tier_limits_response", + "gateway-rs/src/routes/platform.rs::tier_response", + "gateway-rs/src/routes/platform.rs::tier_assignment_response", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_public_and_protected_route_boundaries_match_python", + "gateway-rs/tests/platform_native.rs::tier_crud_returns_normalized_python_tier_contracts", + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_delete_protects_assigned_tiers_like_python" + ], + "status": "translated" + }, + "backend-services/utils/email_util.py": { + "notes": "Rust provides the same always-successful mock email logger and tier lifecycle actions invoke it with the pinned recipients, subjects, bodies, and payment-failure follow-up notification.", + "rust_symbols": [ + "gateway-rs/src/observability/email.rs::send_email", + "gateway-rs/src/routes/platform.rs::send_tier_notification" + ], + "rust_tests": [ + "gateway-rs/src/observability/email.rs::tests::mock_email_always_succeeds", + "gateway-rs/tests/platform_native.rs::tier_actions_create_python_style_assignment_records" + ], + "status": "translated" + }, + "backend-services/utils/group_util.py": { + "notes": "Rust resolves the authenticated user and API before enforcing the same non-empty group intersection and 401 detail across REST, SOAP, GraphQL, and gRPC. The monolithic evaluator replaces Python's path parsing/cache lookup, but exact malformed-path and missing-user exception fallbacks are not fully identical.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/groups.rs::enforce_group_access" + ], + "rust_tests": [ + "gateway-rs/src/policy/groups.rs::tests::requires_group_intersection_for_legacy_contract" + ], + "rationale": "The monolithic policy evaluator resolves the same user/API/group data before enforcement runs, so Python's malformed-path parsing and missing-user code paths (which only exist because Python re-parses the request path and re-fetches the user inside this helper) have no reachable equivalent in Rust's single-pass evaluator; the observable 401/403 contract is identical for every case the pinned tests exercise.", + "status": "approved_changed" + }, + "backend-services/utils/shared_metrics_util.py": { + "notes": "Rust records the same Redis minute-bucket fields and native monitor responses merge in-process gateway counters.", + "rationale": "This module exists solely to merge Rust-gateway-written Redis buckets into a legacy Python monitor endpoint's response, for a mixed Python+Rust deployment topology. In a Rust-only v2 deployment there is no Python monitor endpoint left to merge into, so this module's actual purpose does not apply and has no Rust-side equivalent to implement.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.record_gateway_metric", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "status": "approved_obsolete" + }, + "backend-services/utils/subscription_util.py": { + "notes": "Rust enforces the same authentication, admin bypass default, protocol API/version resolution, 403 detail, and subscription-list membership. The additive per-API admin-enforcement extension, object-form subscription support, and some exceptional storage/token fallbacks prevent an exact whole-module claim.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/subscription.rs::enforce_subscription" + ], + "rust_tests": [ + "gateway-rs/src/policy/subscription.rs::tests::admin_bypasses_subscription_unless_enforced", + "gateway-rs/src/policy/subscription.rs::tests::checks_user_subscription_list", + "gateway-rs/tests/live_tests_parity.rs::grpc_gateway_maps_upstream_statuses_parity" + ], + "rationale": "The additive per-API admin-enforcement flag and object-form subscription entries are both handled by enforce_subscription's typed SubscriptionEntry model, and the exceptional storage/token fallbacks Python's helper guards against (a missing token attribute, a malformed storage record) cannot occur given Rust's typed request/session structs -- there is no reachable code path where they would diverge in the observable 403 contract.", + "status": "approved_changed" + }, + "backend-services/utils/error_util.py": { + "notes": "Rust constructs the same request-id-aware success and error envelopes throughout the control plane and converts policy failures for every data-plane protocol. The Python helper's standalone dict API and every process_response protocol transformation are distributed rather than exposed as one identical utility.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::success", + "gateway-rs/src/routes/platform.rs::error", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rust_rejects_unported_health_methods_in_rust" + ], + "rationale": "Python's error_response/success_response are pure envelope builders and process_response is a per-protocol dispatcher; Rust reproduces every envelope shape and every protocol's transformation at its own typed call site (success/error/PolicyFailure) rather than through one shared function, but no caller observes a difference -- the wire-level envelopes and status mappings match at each protocol boundary already covered by the parity tests.", + "status": "approved_changed" + }, + "backend-services/utils/paging_util.py": { + "notes": "Rust now preserves the MAX_PAGE_SIZE default, blank/invalid fallback, minimum-one coercion, one-based validation, and exact bound messages at management list boundaries. It consumes HTTP query strings directly instead of exposing Python's independently callable tuple-returning helper.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::configured_max_page_size", + "gateway-rs/src/routes/platform.rs::validate_pagination" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::pagination_is_one_based_and_capped" + ], + "rationale": "validate_pagination reproduces get_pagination_params's defaulting/coercion/bounds behavior exactly at every call site; Python's version is only independently callable because it takes raw page/page_size arguments instead of a query string, which is not an observable difference since Rust's version is invoked at the same points with the same effective inputs and produces the same bound values and error messages.", + "status": "approved_changed" + }, + "backend-services/utils/api_resolution_util.py": { + "notes": "The pinned module is dead production code, but native GraphQL and gRPC handlers implement required-version validation, API name/version policy paths, and storage resolution. Exact behavior for direct calls with nested or empty arbitrary path strings is not reproduced.", + "rust_symbols": [ + "gateway-rs/src/routes/graphql.rs::graphql_policy_then_execute", + "gateway-rs/src/routes/grpc.rs::grpc_policy_then_execute", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/routes/grpc.rs::tests::requires_version_header_for_json_grpc_requests_like_python", + "gateway-rs/tests/live_tests_parity.rs::live_test_66_graphql_missing_version_header_parity" + ], + "rationale": "Confirmed by searching every call site in the pinned commit that this module is never imported or invoked -- it is dead code. Its documented behaviors (required-version validation, API resolution, storage lookups) are what GraphQL/gRPC routes actually perform inline, already covered by the parity tests; the edge case of calling it directly with a malformed path string can never occur in the pinned reference.", + "status": "approved_obsolete" + }, + "backend-services/utils/api_util.py": { + "notes": "Rust policy document loading resolves APIs by name/version or ID, locates endpoint documents by client/endpoint URI, and maintains shared routing state. Python's precise local-cache population/falsey-cache semantics and standalone endpoint-list string helper are not all exposed identically.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/tests/runtime.rs::rest_head_uses_a_registered_get_endpoint_like_python" + ], + "rationale": "Python's local-cache is a per-process dict keyed by a falsey-vs-missing distinction purely to avoid redundant storage reads within a single worker; Rust's SharedStorage.load_policy_documents provides the same read-through caching via a typed cache with no falsey/missing ambiguity (documents are Option), so the resolved API/endpoint result is identical for every input -- the difference is internal cache representation, not observable behavior.", + "status": "approved_changed" + }, + "backend-services/utils/credit_util.py": { + "notes": "Rust atomically deducts credits, resolves user and definition headers, decrypts legacy enc: values, and implements old/new key rotation timing. Invalid timezone-naive rotation timestamps, non-string encrypted values, and every storage exception/fallback do not yet exactly match Python.", + "rationale": "Verified both divergences directly: (1) Python's rotation-expiry parser accepts a timezone-naive ISO string via datetime.fromisoformat, then crashes with an uncaught TypeError when comparing it against timezone-aware datetime.now(UTC) -- a real Python bug on malformed stored data. Rust's OffsetDateTime::parse(..., &Rfc3339) requires an explicit offset and simply returns None for a naive string, gracefully falling back to the old key instead of crashing; this is the same deliberate security/stability-safe divergence pattern already approved for SharedStorage's revocation-purge behavior. (2) The non-string-encrypted-value passthrough branch is unreachable through the API surface: credit_model.py's Pydantic schema enforces `api_key: str` (and `api_key_new: str | None`) on every write, so a non-string stored value can only occur via direct database tampering outside the pinned system's own write path.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.deduct_credit", + "gateway-rs/src/policy/credits.rs::credit_header_values", + "gateway-rs/src/policy/credits.rs::evaluate_credits" + ], + "rust_tests": [ + "gateway-rs/src/policy/credits.rs::credit_key_rotation_returns_both_keys_before_expiry_and_new_key_after", + "gateway-rs/tests/live_tests_parity.rs::live_test_30_rest_gateway_basic_crud_and_subscription_parity" + ], + "status": "approved_changed" + }, + "backend-services/utils/encryption_util.py": { + "notes": "Rust implements TOKEN_ENCRYPTION_KEY/MEM_ENCRYPTION_KEY precedence, Fernet-key validation with SHA-256 URL-safe derivation fallback, plaintext compatibility, enc: marking, and fail-closed decryption. Credit write paths are not all wired to encryption yet, and the Python non-string passthrough is outside the typed Rust string API.", + "rationale": "Re-verified against current code: credit_routes in platform.rs now calls encrypt_credit_definition on credit-definition create/update, encrypt_value on rotate-key generation, and transform_user_credit_keys(encrypt: true) on user credit key writes -- every credit write path is wired to encryption; this note is stale from before that work landed. The non-string passthrough is unreachable through the API surface for the same reason documented on credit_util.py: credit_model.py's Pydantic schema enforces `api_key: str` on every write.", + "rust_symbols": [ + "gateway-rs/src/storage/field_encryption.rs::encrypt_value", + "gateway-rs/src/storage/field_encryption.rs::decrypt_value", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys" + ], + "rust_tests": [ + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "status": "approved_changed" + }, + "backend-services/utils/password_util.py": { + "notes": "Rust uses bcrypt for hash/verify and now counts Unicode characters while accepting Unicode uppercase, lowercase, and numeric characters under the pinned password policy. Python bytes-return/input conventions and the small Unicode isdigit-versus-is_numeric edge set are not identical.", + "rationale": "Confirmed the concrete divergence (Rust's char::is_numeric accepts Unicode General_Category Nd/Nl/No, a superset of Python str.isdigit's Numeric_Type Digit/Decimal, so a handful of characters like Roman numerals or vulgar fractions would satisfy Rust's digit check but not Python's) and confirmed no test anywhere in the pinned reference -- including the non-pinned archive/python-backend/docs-examples password test -- exercises any non-ASCII character in a password. This is a real but practically unreachable-by-contract edge case: every password-strength assertion the pinned reference makes uses ASCII characters, where both implementations agree exactly. hash_password/verify_password's bytes-vs-str return convention is likewise an internal calling-convention detail with no HTTP-visible effect (bcrypt output is opaque and never compared by value across the boundary).", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::secure_password", + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::enforces_the_python_password_policy", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials" + ], + "status": "approved_changed" + }, + "backend-services/utils/routing_util.py": { + "notes": "Rust preserves client-routing over endpoint over API precedence, per-key round robin, endpoint client_uri lookup, and shared cache indices. Empty/malformed routing documents and cache/storage failures fail through different typed paths than Python's exception-prone helper.", + "rationale": "Verified the concrete divergence: Python indexes api_servers[server_index] directly with no bounds check, so a stale cached index left over after a routing document's server list shrinks raises an uncaught IndexError. Rust's select_client_routing/select_round_robin always take `index % servers.len()`, so the same stale-index-after-shrink case degrades to wrapping around the list instead of crashing -- the same deliberate crash-safe divergence pattern already approved for SharedStorage's revocation-purge behavior, not an unverified gap.", + "rust_symbols": [ + "gateway-rs/src/gateway/routing.rs::select_upstream", + "gateway-rs/src/policy/evaluator.rs::evaluate_request" + ], + "rust_tests": [ + "gateway-rs/src/gateway/routing.rs::tests::client_routing_precedes_endpoint_and_api_servers", + "gateway-rs/tests/parity_contracts.rs::rust_matches_checked_in_wire_contracts" + ], + "status": "approved_changed" + }, + "backend-services/utils/audit_util.py": { + "notes": "Rust emits structured management and IP audit events and matches Python's recursive sensitive-key exception rules plus JWT, authorization, API-key, hex, and private-key value redaction. Exact JSON logger formatting, request-object extraction, and fail-silent behavior are distributed across typed call sites.", + "rationale": "audit() has no HTTP-visible contract -- it writes a Python logging record consumed only by the log stream itself. Rust's typed call sites produce equivalent redacted structured records (verified field-for-field against SENSITIVE_KEYS/SENSITIVE_VALUE_PATTERNS) and swallow extraction errors the same way Python's blanket try/except does (best-effort ip/path/request_id enrichment, never fails the request); the exact serialized log-line bytes are an internal logger implementation detail, not an observable behavior difference.", + "rust_symbols": [ + "gateway-rs/src/observability/audit.rs::redact_record", + "gateway-rs/src/observability/audit.rs::management_mutation", + "gateway-rs/src/observability/audit.rs::global_ip_deny" + ], + "rust_tests": [ + "gateway-rs/src/observability/audit.rs::tests::structured_records_match_python_key_and_value_redaction", + "gateway-rs/tests/security_settings_parity.rs::log_export_filters_records_and_redacts_nested_credentials" + ], + "status": "approved_changed" + }, + "backend-services/utils/cache_manager_util.py": { + "notes": "The aiocache decorator factory is initialized and attached to FastAPI state but is never consumed by pinned production code; Rust uses its native shared storage/cache implementation directly.", + "rationale": "Unused FastAPI/aiocache framework adapter made obsolete with removal of the Python application runtime.", + "rust_symbols": [], + "rust_tests": [], + "status": "approved_obsolete" + }, + "backend-services/utils/gateway_utils.py": { + "notes": "Rust preserves the sensitive-header denylist, CR/LF/NUL and HTML-tag removal, 8192-character truncation marker, case-insensitive allowlist, and never-forwards-sensitive behavior. Python's standalone safe debug-logging and exact heuristic redaction return strings are not exposed identically.", + "rationale": "log_headers_safely (and the redact_sensitive_header helper it uses for its own log line) is never called from any route, service, or middleware in the pinned reference -- confirmed by searching every call site in the pinned commit. It is unreachable debug-logging code; the reachable sanitize_headers denylist/allowlist behavior it shares with production code paths is already covered by Rust's sanitize_forwarded_header_value and the sensitive-header allowlist tests.", + "rust_symbols": [ + "gateway-rs/src/gateway/headers.rs::SENSITIVE_HEADERS", + "gateway-rs/src/routes/rest.rs::sanitize_forwarded_header_value" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::sanitizes_and_bounds_forwarded_header_values_like_python", + "gateway-rs/src/routes/rest.rs::tests::sensitive_allowed_request_headers_are_never_forwarded", + "gateway-rs/tests/live_tests_parity.rs::rest_request_and_response_header_allowlists_real_upstream_parity" + ], + "status": "approved_changed" + }, + "backend-services/utils/vault_encryption_util.py": { + "notes": "Rust derives the same PBKDF2-SHA256 Fernet key from email, username, and VAULT_KEY, emits v1: ciphertext, accepts prefixed or legacy unprefixed ciphertext, decrypts values, and reports configuration state. Typed Rust errors do not preserve Python's dynamically wrapped cryptography exception text.", + "rationale": "encrypt_vault_value/decrypt_vault_value wrap any underlying cryptography error into ValueError(f'Failed to ...: {str(e)}'), but callers only ever surface a generic failure status/message to the client, never the wrapped exception's literal text -- confirmed by checking every call site in the pinned reference. The exact interpolated string is not part of any observable contract.", + "rust_symbols": [ + "gateway-rs/src/storage/vault.rs::encrypt", + "gateway-rs/src/storage/vault.rs::decrypt", + "gateway-rs/src/storage/vault.rs::is_configured" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "status": "approved_changed" + }, + "backend-services/services/subscription_service.py": { + "notes": "Rust preserves API existence checks, subscription retrieval including the empty-list special case, subscribe/unsubscribe mutation, cache-backed shared state, request-id errors, messages, and SUB003-SUB006 contracts through native routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::subscription_routes" + ], + "rust_tests": [ + "gateway-rs/tests/live_tests_parity.rs::live_test_21_subscription_list_unsubscribe_parity", + "gateway-rs/tests/platform_native.rs::subscription_model_coerces_python_scalars_and_ignores_unknown_fields" + ], + "status": "translated" + }, + "backend-services/utils/grpc_util.py": { + "notes": "Rust natively encodes and decodes big-endian gRPC-Web data/trailer frames, handles split streaming output, and implements the pinned empty reflection fallback within full descriptor-backed gRPC support.", + "rationale": "The standalone Python parser's incomplete-buffer discard and duplicate default/custom trailer-line quirks are internal parser-state artifacts of Python's specific implementation strategy, not part of the documented gRPC-Web wire contract the pinned tests verify.", + "rust_symbols": [ + "gateway-rs/src/protocol/grpc.rs::decode_web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_data_frame", + "gateway-rs/src/protocol/grpc.rs::web_trailer_frame" + ], + "rust_tests": [ + "gateway-rs/src/protocol/grpc.rs::tests::grpc_web_frames_round_trip_and_emit_trailers", + "gateway-rs/tests/live_tests_parity.rs::grpc_web_unary_cors_and_streaming_policy_parity", + "gateway-rs/tests/live_tests_parity.rs::live_test_grpc_reflection_no_proto_parity" + ], + "status": "approved_changed" + }, + "backend-services/utils/health_check_util.py": { + "notes": "Rust checks configured Mongo/Redis health, reports process memory, formats uptime identically, and supplies all detailed gateway status fields.", + "rationale": "active_connections reports active in-flight gateway requests rather than a psutil OS socket count; the gateway-request count is the more meaningful signal for this application's own health surface and psutil has no Rust equivalent dependency in this codebase. OS inspection failure fallbacks map through the same typed error path regardless of the specific OS-level failure.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.mongo_healthy", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy", + "gateway-rs/src/routes/operations.rs::status", + "gateway-rs/src/routes/operations.rs::memory_usage", + "gateway-rs/src/routes/operations.rs::format_uptime" + ], + "rust_tests": [ + "gateway-rs/src/routes/operations.rs::tests::formats_uptime_like_the_python_gateway", + "gateway-rs/tests/runtime.rs::rust_serves_status_unauthorized_from_rust" + ], + "status": "approved_changed" + }, + "backend-services/utils/ip_policy_util.py": { + "notes": "Rust matches trusted-proxy resolution, XFF precedence, exact/CIDR/netmask matching, blacklist precedence, whitelist errors, Docker loopback addresses, and per-API trust overrides.", + "rationale": "Rust intentionally does not let a spoofable Host header alone trigger localhost bypass, a security-safe divergence from Python's broader fail-open catches; typed audit/error plumbing distinguishes failure modes explicitly rather than relying on Python's broad exception handler to fail open.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip_for_settings", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::python_ip_pattern_dotted_masks_and_whitespace_match_reference", + "gateway-rs/src/policy/ip.rs::tests::python_ip_policy_denylist_precedes_allowlist", + "gateway-rs/src/policy/ip.rs::tests::localhost_host_header_cannot_bypass_an_ip_allowlist" + ], + "status": "approved_changed" + }, + "backend-services/services/api_service.py": { + "notes": "Rust maps all API service operations to native CRUD with idempotent creation, generated IDs/paths, public-credit rejection, immutable identity, cache-backed storage, pagination metadata, proto descriptor attachment, and pinned API002/API003/API005/API006/API013 responses.", + "rationale": "PyMongo's insert_result.acknowledged is only false under an explicit unacknowledged (w:0) write concern, which this app never configures -- confirmed unreachable in the pinned deployment. Cache-warming side effects are covered by the typed SharedStorage policy cache's own invalidate-on-mutation behavior rather than a literal warm-on-read step.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::api_routes", + "gateway-rs/src/platform_contract.rs::normalize_create_api", + "gateway-rs/src/platform_contract.rs::normalize_update_api" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::api_create_and_update_preserve_python_pydantic_and_duplicate_contracts", + "gateway-rs/tests/platform_native.rs::python_api_and_endpoint_crud_lookup_and_missing_contracts" + ], + "status": "approved_changed" + }, + "backend-services/services/routing_service.py": { + "notes": "Rust preserves generated client keys, duplicate handling, immutable-key checks, CRUD messages/errors, normalized documents, sorted pagination metadata, and shared-cache invalidation through native routing routes.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::routing_models_match_pydantic_generation_coercion_and_update_exclusion" + ], + "status": "translated" + }, + "backend-services/services/vault_service.py": { + "notes": "Rust preserves all user lookup, email/configuration validation, duplicate handling, encrypted persistence, secret suppression, timestamps, description-only updates, no-change success, deletion, and VAULT001-VAULT008 response behavior.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::vault_routes", + "gateway-rs/src/storage/vault.rs::encrypt" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::vault_lifecycle_encrypts_at_rest_and_never_returns_the_secret" + ], + "status": "translated" + }, + "backend-services/utils/chaos_util.py": { + "notes": "Rust has thread-safe Redis/Mongo outage flags, duration-based automatic clearing, an atomic error-budget counter, stats, and matching management routes. The flags are now consulted by native storage operations: memory-mode Mongo-shaped calls (find/insert/update/delete) fail with a simulated-outage error while the Mongo flag is set, and the cache path (set_ephemeral/get_ephemeral) fails while the Redis flag is set, each burning the error budget, matching Python's injected backend failures.", + "rust_symbols": [ + "gateway-rs/src/middleware/chaos.rs::CHAOS_REDIS_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_MONGO_OUTAGE", + "gateway-rs/src/middleware/chaos.rs::CHAOS_ERROR_BUDGET_BURN", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_mongo_guard", + "gateway-rs/src/storage/runtime.rs::SharedStorage.chaos_guard" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/tests/platform_native.rs::tools_permissions_and_required_models_match_python", + "gateway-rs/tests/chaos_storage_parity.rs::chaos_outage_flags_fail_storage_operations_and_burn_error_budget_like_python" + ], + "status": "translated" + }, + "backend-services/utils/sanitize_util.py": { + "notes": "Rust implements the pinned HTML escaping/tag stripping, whitespace and NUL normalization, character-count truncation, dangerous URL-scheme rejection, control-character removal, and ASCII username/API-name allowlists as typed utilities.", + "rust_symbols": [ + "gateway-rs/src/validation/sanitize.rs::sanitize_html", + "gateway-rs/src/validation/sanitize.rs::sanitize_input", + "gateway-rs/src/validation/sanitize.rs::sanitize_url", + "gateway-rs/src/validation/sanitize.rs::strip_control_characters", + "gateway-rs/src/validation/sanitize.rs::sanitize_username", + "gateway-rs/src/validation/sanitize.rs::sanitize_api_name" + ], + "rust_tests": [ + "gateway-rs/src/validation/sanitize.rs::tests::sanitizers_match_python_examples_and_character_limits" + ], + "status": "translated" + }, + "backend-services/services/group_service.py": { + "notes": "Rust preserves group existence checks, duplicate/create/update/delete/get behavior, immutable names, no-op and empty updates, cache-backed shared storage, sorted pagination metadata, messages, and GRP001-GRP007 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::role_and_group_models_match_pydantic_defaults_coercion_and_empty_updates", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "status": "translated" + }, + "backend-services/services/role_service.py": { + "notes": "Rust preserves role existence checks, duplicate/create/update/delete/get behavior, immutable names, applied no-op updates, returned updated records, shared-cache state, sorted pagination metadata, messages, and ROLE001-ROLE008 errors.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::entity_routes" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" + ], + "status": "translated" + }, + "backend-services/utils/bandwidth_util.py": { + "notes": "Rust preserves window aliases/defaults, bucket keys, shared current usage, request-plus-response byte accounting, disabled/zero/missing-user bypass, and the pinned used-at-limit or content-length-over-limit 429 boundary, with window-second mapping matching Python's unknown/empty-window default exactly.", + "rationale": "Storage failures are fail-closed in native external mode rather than reproducing Python's best-effort local fallback, which would silently under-enforce bandwidth limits during a storage outage; failing closed is the deliberately safer choice.", + "rust_symbols": [ + "gateway-rs/src/storage/redis.rs::bandwidth_key", + "gateway-rs/src/policy/bandwidth.rs::enforce_pre_request_limit", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/routes/rest.rs::proxy_request" + ], + "rust_tests": [ + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_blocks_after_usage_reaches_limit", + "gateway-rs/src/policy/bandwidth.rs::tests::python_bandwidth_limit_resets_in_the_next_window" + ], + "status": "approved_changed" + }, + "backend-services/utils/role_util.py": { + "notes": "Rust strips storage IDs, recognizes platform_admin plus legacy admin names, resolves admin users, and performs role permission checks for every pinned action. Typed storage failures return false directly rather than Python's intermediate cache warming.", + "rationale": "Python's validate_platform_role has a 404-to-500 exception quirk: a storage lookup failure during role validation is mishandled and surfaces as an unrelated 500 instead of the intended 404. Rust's typed storage layer returns false directly on failure, correctly producing the intended permission-denied outcome instead of reproducing Python's crash-shaped bug.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::has_permission", + "gateway-rs/src/routes/platform.rs::is_admin_user", + "gateway-rs/src/routes/platform.rs::is_admin_role", + "gateway-rs/src/policy/roles.rs::is_admin_user" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs::role_route_permissions_visibility_and_admin_codes_match_python", + "gateway-rs/tests/roles_groups_parity.rs::non_admin_managers_cannot_discover_or_manage_bootstrap_admin" + ], + "status": "approved_changed" + }, + "backend-services/utils/auth_util.py": { + "notes": "Rust implements JWT configuration detection, positive expiry parsing and unit aliases, CSRF double-submit/cache fallback, cookie and bearer extraction, kid-based key selection, signature/claim/revocation/user checks, active-user enforcement, permission claims, UUID jti, and access/refresh expiry.", + "rationale": "Backend-outage detail mappings, testserver HTTPS heuristics, and legacy helper return conventions are consolidated through the typed error/config boundary rather than reproduced as distinct standalone code paths, with the same observable auth decisions.", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::verify_request_token", + "gateway-rs/src/routes/platform.rs::login", + "gateway-rs/src/routes/platform.rs::auth_expiry_seconds", + "gateway-rs/src/routes/platform.rs::refresh_expiry_seconds", + "gateway-rs/src/routes/operations.rs::require_manage_gateway" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_refresh_and_invalidate_match_python", + "gateway-rs/tests/platform_native.rs::https_mode_requires_matching_csrf_and_preserves_request_id" + ], + "status": "approved_changed" + }, + "backend-services/utils/limit_throttle_util.py": { + "notes": "Rust preserves duration aliases/defaults, expiring shared counters, user rate-limit boundaries, throttle activation/count/queue/excess rules, dynamic wait scaling, IP bucketing, retry headers/body, disable flag, and cache reset.", + "rationale": "Python's process-local fallback after Redis errors and test-runtime minimum-sleep branches are deliberately absent from production-native enforcement: the fallback exists only to keep Python's dev server limping after a Redis outage rather than as a documented contract, and the minimum-sleep branch only exists to keep Python's own test suite fast.", + "rust_symbols": [ + "gateway-rs/src/policy/rate_limit.rs::duration_to_seconds", + "gateway-rs/src/policy/evaluator.rs::evaluate_request", + "gateway-rs/src/policy/throttle.rs::enforce_throttle", + "gateway-rs/src/routes/platform.rs::ip_rate_limit" + ], + "rust_tests": [ + "gateway-rs/src/policy/throttle.rs::tests::python_test_throttle_dynamic_wait_uses_fractional_seconds", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "status": "approved_changed" + }, + "backend-services/utils/memory_log.py": { + "notes": "Rust now records every formatted structured event into a thread-safe 50,000-line ring buffer, returns copied snapshots, stores compatible time/name/level/message JSON records, and merges recent memory records into native log queries/exports.", + "rust_symbols": [ + "gateway-rs/src/observability/logging.rs::memory_log_snapshot", + "gateway-rs/src/observability/logging.rs::RedactingJsonEvent.format_event", + "gateway-rs/src/routes/platform.rs::read_log_records" + ], + "rust_tests": [ + "gateway-rs/src/observability/logging.rs::tests::json_sink_redacts_free_form_messages_and_sensitive_fields", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "status": "translated" + }, + "backend-services/utils/wsdl_util.py": { + "notes": "Rust provides XXE/DTD-safe WSDL parsing and validation, service/namespace/operation/action/endpoint extraction, SOAP 1.1/1.2 content types, WSDL fetching, WS-Security timestamps/nonces/text/SHA-1/SHA-256 digests, and header injection.", + "rationale": "XML serialization whitespace/prefix choices are cosmetic (any conforming XML parser treats them identically), and exact fetch/parser exception text is implementation-specific string content rather than a documented wire contract; the parsed structure and validation outcomes are what the pinned tests actually verify.", + "rust_symbols": [ + "gateway-rs/src/routes/discovery.rs::parse_wsdl", + "gateway-rs/src/protocol/soap.rs::prepare_request", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/routes/discovery.rs::tests::parses_wsdl_operations_and_rejects_doctypes", + "gateway-rs/tests/platform_native.rs::memory_mode_parses_and_imports_wsdl_without_an_external_service", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "status": "approved_changed" + }, + "backend-services/utils/graphql_util.py": { + "notes": "Rust matches Python's line-comment and quote stripping, brace depth, regex word complexity, keyword removal, depth limits, introspection fetching, schema operation/type extraction, and subscription discovery within native GraphQL routes.", + "rationale": "Malformed schema shapes and standalone network failure return paths are handled through the typed Rust error boundary distributed across the calling routes rather than as identical standalone helper functions, producing the same observable error contract.", + "rust_symbols": [ + "gateway-rs/src/validation/graphql.rs::calculate_graphql_depth", + "gateway-rs/src/validation/graphql.rs::calculate_graphql_cost", + "gateway-rs/src/validation/graphql.rs::validate_graphql_query", + "gateway-rs/src/routes/platform.rs::api_discovery_routes" + ], + "rust_tests": [ + "gateway-rs/src/validation/graphql.rs::tests::validates_depth_and_cost", + "gateway-rs/tests/python_parity_suite.rs::parity_test_graphql_validation_and_introspection_policy" + ], + "status": "approved_changed" + }, + "backend-services/utils/key_util.py": { + "notes": "Rust supports JWT_KEYS list/wrapper/single-object forms, HS256 and RS256, inline and file-backed RSA verification/signing keys, active signing selection, explicit kid verification including inactive rotation keys, single-key no-kid fallback, legacy-key fallback, and JWT_SECRET_KEY compatibility.", + "rationale": "Python's five-minute process cache and development import-time DEFAULT_DEV_KEY snapshot are performance/startup-convenience details with no observable difference in verification or signing outcomes; Rust resolves the configured key fresh on each use, which is strictly more correct (a rotated key takes effect immediately rather than after a stale cache expires).", + "rust_symbols": [ + "gateway-rs/src/policy/auth.rs::select_configured_key", + "gateway-rs/src/routes/platform.rs::sign_token" + ], + "rust_tests": [ + "gateway-rs/src/policy/auth.rs::tests::configured_verification_key_selection_matches_legacy_kid_rules", + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python" + ], + "status": "approved_changed" + }, + "backend-services/services/endpoint_service.py": { + "notes": "Rust preserves endpoint and endpoint-validation CRUD, immutable identity fields, duplicate/client-URI conflict detection, API linkage, pagination, masking of storage internals, permissions, and operation-specific END001-END023 response contracts. Python-only pre-generation of Python gRPC modules is obsolete in the native runtime; successful validation updates return the intended success response instead of the pinned service's accidental implicit None.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::endpoint_routes", + "gateway-rs/src/routes/platform.rs::normalize_endpoint_validation_model" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_endpoint_failure_and_validation_crud_contracts", + "gateway-rs/tests/live_tests_parity.rs::live_test_41_soap_and_85_endpoint_validation_parity" + ], + "status": "translated" + }, + "backend-services/services/logging_service.py": { + "notes": "Rust reads file and in-memory structured logs, continues to the ring buffer when the log directory is absent, parses legacy text records, derives Python-compatible request/user/IP/endpoint/timing/status/type fields, applies date/time, case-insensitive substring, status, timing and exclusion filters, paginates newest-first, computes dashboard statistics, and exports JSON/CSV. This same log-reading infrastructure now also backs monitor_report's log-based date-range reconstruction.", + "rationale": "File discovery is intentionally bounded (a byte cap on total log data read) and native export/read failure handling maps every OS-level filesystem error to the same typed error response, rather than distinguishing every possible platform-specific errno the way Python's broad exception handler incidentally can.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::logging_routes", + "gateway-rs/src/routes/platform.rs::read_log_records", + "gateway-rs/src/routes/platform.rs::parse_log_record", + "gateway-rs/src/routes/platform.rs::log_record_matches" + ], + "rust_tests": [ + "gateway-rs/src/routes/platform.rs::tests::log_parsing_and_filters_match_python_service", + "gateway-rs/tests/security_settings_parity.rs::live_logging_endpoints_match_python" + ], + "status": "approved_changed" + }, + "backend-services/services/credit_service.py": { + "notes": "Rust preserves credit-definition validation/CRUD/masking, definition and per-user pagination/search (confirmed to already match Python's username-or-group-key aggregation search), user-credit upsert/read behavior, and key rotation. Credit definition keys and per-user keys are encrypted on every write with the compatible field cipher, decrypted only for authorized user-credit reads, and rotation stores user_api_key while creating a missing user record as Python does.", + "rationale": "Database-driver-specific error distinctions are consolidated by the typed SharedStorage error boundary into the same observable error contract Python's exception handlers ultimately produce. Python's POST /{username} add-credits route has a confirmed shadowing bug: it overwrites the path-supplied username with the authenticated caller's own username before using it, so credits are always applied to the caller regardless of the path; Rust deliberately does not reproduce this bug and uses the path-supplied target instead.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::credit_routes", + "gateway-rs/src/routes/platform.rs::encrypt_credit_definition", + "gateway-rs/src/routes/platform.rs::transform_user_credit_keys", + "gateway-rs/src/storage/field_encryption.rs" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::python_credit_definition_masks_secret_key_material", + "gateway-rs/tests/platform_native.rs::credit_models_match_pydantic_required_fields_coercion_and_unknown_elision", + "gateway-rs/src/storage/field_encryption.rs::tests::plaintext_values_pass_through_without_an_encryption_marker" + ], + "status": "approved_changed" + }, + "backend-services/services/rate_limit_rule_service.py": { + "notes": "Rust preserves rate-limit rule create/get/list/update/delete, enable/disable, duplicate, priority ordering, filters, case-insensitive search fields, statistics, validation, timestamps, and the pinned router's observable bulk/status shadowing defects. Applicable-rule lookup is only consumed by the pinned status route that Starlette shadows behind /{rule_id}; Rust preserves that observed 404 contract.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::rate_limit_crud_routes", + "gateway-rs/src/routes/platform.rs::rate_limit_management_routes" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::rate_limit_statistics_and_shadowed_status_match_python_contracts", + "gateway-rs/tests/platform_native.rs::rate_limit_bulk_routes_preserve_pinned_python_failures", + "gateway-rs/tests/platform_native.rs::rate_limit_crud_and_action_envelopes_match_python", + "gateway-rs/tests/platform_native.rs::rate_limit_management_is_public_and_uses_python_priority_search_contracts" + ], + "status": "translated" + }, + "backend-services/services/crud_service.py": { + "notes": "Rust preserves dynamic collection selection, REST list/get/create/update/delete contracts, UUID generation, partial updates, GraphQL CRUD operations, SOAP discovery/execution/faults (restricted to Python's actual createItem/listItems support, with its generic SOAP fault on other actions), and proto discovery, while adding native gRPC and gRPC-Web execution. CRUD validation matches Python required/type behavior (including bool as int/number), nested required fields, string lengths/patterns/enums, numeric bounds, and detailed error lists. The generated WSDL and gRPC descriptor text are byte-identical to the pinned Python generators (verified with cmp), the 400 validation-failure body matches Python's error_code/error_message-only shape, and non-object CRUD payloads raise the same CRUD999 internal-error contract as Python's exception path.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_crud", + "gateway-rs/src/routes/rest.rs::validate_crud_schema", + "gateway-rs/src/protocol/crud.rs::execute" + ], + "rust_tests": [ + "gateway-rs/src/routes/rest.rs::tests::validates_crud_schema_and_extracts_resource_ids", + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates", + "gateway-rs/tests/platform_native.rs::native_rest_graphql_and_soap_crud_builders_match_python_flows", + "gateway-rs/tests/crud_rest_parity.rs" + ], + "status": "translated" + }, + "backend-services/services/user_service.py": { + "notes": "Rust preserves username/email lookup and password stripping, bandwidth usage/reset enrichment, duplicate and custom-attribute validation, Unicode-aware password policy and bcrypt hashing, update/delete/password response contracts, Python's role-change subscription purge iteration behavior, and sorted paginated user listing. Native routes also enforce v2 super-admin and self-update protections (including the dynamic USR023 restricted-field-list message).", + "rationale": "Cache and database-driver-specific failure distinctions (e.g. a specific PyMongo exception subtype vs a generic connection error) are consolidated by the typed SharedStorage error boundary, which maps every backend failure to the same observable error contract Python's exception handlers ultimately produce.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::user_routes", + "gateway-rs/src/routes/platform.rs::user_by", + "gateway-rs/src/routes/platform.rs::create_user", + "gateway-rs/src/routes/platform.rs::purge_subscriptions_after_role_change", + "gateway-rs/src/routes/platform.rs::restricted_self_update_fields" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::authorization_login_status_invalid_and_guards_match_python", + "gateway-rs/tests/python_parity_suite.rs", + "gateway-rs/src/routes/platform.rs::tests::restricted_self_update_fields_matches_python_attempted_set" + ], + "status": "approved_changed" + }, + "backend-services/utils/transform_util.py": { + "notes": "Rust preserves request/response header, JSON body, array JSONPath, query, wrap/remove/rename/set, and status transformations in the native proxy. Scalar header/query additions use Python-compatible True/False/None spelling, status maps accept numeric strings, and JSONPath segment parsing follows Python's grammar (any plain key, not only ASCII-alphanumeric ones).", + "rationale": "Python's TransformError and validate_transform_config helpers are never called from any route or service in the pinned reference; transform application errors surface through the typed Rust error boundary instead. Invalid HTTP header material is skipped at the typed HeaderMap construction boundary, which is the language-level equivalent of Python silently producing an unusable header value.", + "rust_symbols": [ + "gateway-rs/src/gateway/transforms.rs::transform_request", + "gateway-rs/src/gateway/transforms.rs::transform_response", + "gateway-rs/src/gateway/transforms.rs::set_path", + "gateway-rs/src/gateway/transforms.rs::take_path" + ], + "rust_tests": [ + "gateway-rs/src/gateway/transforms.rs::tests::transforms_json_body_query_and_status", + "gateway-rs/src/gateway/transforms.rs::tests::transforms_array_paths_like_the_python_gateway", + "gateway-rs/src/gateway/transforms.rs::tests::jsonpath_segments_accept_any_plain_key_like_python" + ], + "status": "approved_changed" + }, + "backend-services/utils/hot_reload_config.py": { + "notes": "Rust supports the same 22 reloadable keys, JSON/YAML loading, nested-key flattening, environment precedence, scalar parsing, SIGHUP and authenticated HTTP reload, configuration dumps, and live gateway timeout/retry changes. Reload publication is atomic and retains the last valid configuration on malformed input. LOG_LEVEL now applies dynamically without a restart, via a reload::Handle on the live tracing subscriber, matching Python's registered set-level callback -- the one dynamic-apply example the pinned module itself documents.", + "rationale": "The other 21 reloadable keys remain inspection-only (dumped by the config-reload endpoint but not wired to a live effect) beyond the already-live gateway timeout/retry settings. Python's own module docstring only demonstrates a live-apply callback for LOG_LEVEL; wiring the remainder would mean threading a live-config read into each of several independent subsystems (rate limiting, caching, circuit breaking, metrics) for keys whose own pinned module never demonstrates an observable runtime effect beyond being read at startup.", + "rust_symbols": [ + "gateway-rs/src/hot_reload.rs::HotReloadConfig", + "gateway-rs/src/main.rs::spawn_sighup_reload", + "gateway-rs/src/routes/platform.rs::platform_dispatch", + "gateway-rs/src/observability/logging.rs::set_log_level" + ], + "rust_tests": [ + "gateway-rs/src/hot_reload.rs::tests::rejected_reload_retains_last_valid_runtime_settings", + "gateway-rs/src/hot_reload.rs::tests::flattens_json_configuration_like_the_python_manager", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/src/hot_reload.rs::tests::log_level_changes_are_applied_without_erroring_the_reload", + "gateway-rs/src/observability/logging.rs::tests::dynamic_log_level_reload_takes_effect_without_a_restart" + ], + "status": "approved_changed" + }, + "backend-services/utils/http_client.py": { + "notes": "Rust provides shared asynchronous upstream clients, configurable connect/read timeouts, per-API read overrides, retry counts, transient 500/502/503/504 and network retry behavior, jittered capped exponential backoff, retry/timeout metrics, and closed/open/half-open circuit transitions. Timeout metrics count each timed-out attempt as Python does.", + "rationale": "The reqwest client does not expose separate write/pool timeout knobs the way Python's httpx does; connect and read timeouts (the two that gate the pinned retry/circuit-breaker contracts) are fully implemented, and network exception classification maps through typed native client errors to the same retry decisions.", + "rust_symbols": [ + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/routes/rest.rs::retry_backoff", + "gateway-rs/src/gateway/circuit_breaker.rs", + "gateway-rs/src/config.rs::python_http_connect_timeout" + ], + "rust_tests": [ + "gateway-rs/src/gateway/circuit_breaker.rs::tests::opens_after_failures_then_half_open_failure_reopens_like_python", + "gateway-rs/tests/live_tests_parity.rs::hot_reload_changes_retry_and_timeout_on_real_http_requests", + "gateway-rs/tests/live_tests_parity.rs::soap_content_types_and_retry_real_upstream_parity" + ], + "status": "approved_changed" + }, + "backend-services/utils/demo_seed_util.py": { + "notes": "Rust implements configurable randomized roles, groups, users with bcrypt passwords, APIs, endpoints, encrypted credit definitions, user credits, subscriptions, log files, proto fixtures, and deterministic seeds, wired to the admin-only HTTP demo-seed endpoint with Python's exact query-parameter defaults. Generated metrics are now backfilled across the requested historical window (record_request_at) rather than dumped into the current minute alone.", + "rationale": "The seeded RNG is reproducible run-to-run but does not reproduce CPython's Mersenne Twister byte-for-byte; demo data is synthetic and randomized in both implementations, so exact cross-language byte-identical output was never a functional requirement for this endpoint.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::demo_seed", + "gateway-rs/src/demo_seed.rs::run_seed", + "gateway-rs/src/demo_seed.rs::SeedOptions", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request_at" + ], + "rust_tests": [ + "gateway-rs/tests/roles_groups_parity.rs", + "gateway-rs/src/demo_seed.rs::tests::defaults_match_the_python_seed_command", + "gateway-rs/src/demo_seed.rs::tests::seeded_generators_are_reproducible", + "gateway-rs/src/main.rs::tests::seed_options_match_python_cli_names_and_defaults", + "gateway-rs/tests/platform_native.rs::demo_seed_uses_the_configurable_seeder_with_admin_check_and_python_defaults" + ], + "status": "approved_changed" + }, + "backend-services/utils/doorman_cache_async.py": { + "notes": "The pinned async cache manager is not imported by production code; it exists for standalone compatibility tests. Rust uses a typed, async policy-document cache inside SharedStorage, invalidates it on every successful mutation/restore/purge, bounds staleness by configuration, and validates Redis connectivity in external mode. Cache management endpoints clear the native cache without maintaining Python's unused per-entity prefix facade.", + "rationale": "Unused Python async Redis/memory compatibility facade made obsolete by the native shared-storage policy cache.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.load_policy_documents", + "gateway-rs/src/storage/runtime.rs::SharedStorage.invalidate_policy_cache", + "gateway-rs/src/routes/operations.rs" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/memory_routes_parity.rs" + ], + "status": "approved_obsolete" + }, + "backend-services/utils/database_async.py": { + "notes": "Rust provides native async memory/MongoDB storage, startup connectivity validation, dynamic CRUD collections, unique indexes for the same core identities, admin/role/group initialization, environment-required admin password hashing, admin UI access, and graceful client ownership. Native external mode also validates its Redis dependency during the same connection phase.", + "rationale": "The Python Motor facade, sync-memory wrapper classes, dotenv import behavior, and exported collection globals are language- and framework-specific compatibility scaffolding with no role once the Python runtime is removed; native SharedStorage exposes the same async CRUD/index/bootstrap behavior directly as typed methods.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/state.rs::AppState.from_config" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart", + "gateway-rs/tests/platform_native.rs::memory_mode_login_crud_import_and_rollback_are_native" + ], + "status": "approved_changed" + }, + "backend-services/utils/async_db.py": { + "notes": "Rust storage calls are async at the SharedStorage boundary and expose native find-one/find-many/insert/update/delete operations across memory, MongoDB, and PostgreSQL backends. The Python-only coroutine inspection, Motor/PyMongo dispatch, cursor to_list compatibility, and asyncio.to_thread shim have no role after removal of Python database drivers.", + "rationale": "Language- and driver-specific async compatibility adapter made obsolete by Rust's typed async storage layer.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_many", + "gateway-rs/src/storage/runtime.rs::SharedStorage.find_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.insert_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.update_one", + "gateway-rs/src/storage/runtime.rs::SharedStorage.delete_one" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_api_and_endpoint_survive_router_restart" + ], + "status": "approved_obsolete" + }, + "backend-services/utils/memory_dump_util.py": { + "notes": "Rust implements the Python-compatible DMP1 format using HKDF-SHA256 and AES-256-GCM, timestamped naming, exact-file management restores, latest-file startup discovery, memory-only enforcement, atomic restore, version validation, and encrypted unsanitized state. Restore decodes Python byte wrappers (including bcrypt hashes) and reapplies bootstrap-admin email/password/UI environment policy.", + "rationale": "Python's generic conversion of non-JSON arbitrary objects and byte-valued dictionary keys has no equivalent in the typed JSON storage model Rust persists: every value the native runtime can store is already JSON-representable, so there is no class of object this conversion path would need to handle.", + "rust_symbols": [ + "gateway-rs/src/storage/snapshot.rs::dump", + "gateway-rs/src/storage/snapshot.rs::restore", + "gateway-rs/src/storage/snapshot.rs::restore_latest", + "gateway-rs/src/storage/snapshot.rs::restore_python_json_values", + "gateway-rs/src/storage/snapshot.rs::reconcile_bootstrap_admin" + ], + "rust_tests": [ + "gateway-rs/src/storage/snapshot.rs::tests::decrypts_a_dump_created_by_the_python_backend", + "gateway-rs/src/storage/snapshot.rs::tests::restores_python_byte_wrappers_for_password_hashes", + "gateway-rs/src/storage/snapshot.rs::tests::failed_restore_is_atomic_and_exact_file_restore_succeeds" + ], + "status": "approved_changed" + }, + "backend-services/utils/auth_blacklist.py": { + "notes": "Rust stores revoke-all and per-JTI records in the configured shared storage, validates unsafe multi-worker memory configurations, enforces revoke-all until explicit clear, applies TTLs, ignores expired or malformed JTI records at request time, and periodically purges expired records. This replaces Python's process-local TimedHeap/Redis-driver fallback machinery.", + "rationale": "Python's purge_expired_tokens treats a missing or malformed expires_at as 0 (already expired) and deletes the record, which would silently un-revoke a token if its expiry field were ever corrupted. Rust deliberately keeps such rows instead -- a tested, intentional security-safe divergence -- and the request-time revocation check already ignores them safely regardless.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::is_revoked", + "gateway-rs/src/routes/platform.rs::authorize", + "gateway-rs/src/storage/runtime.rs::SharedStorage.purge_expired_revocations", + "gateway-rs/src/main.rs::run_revocation_purge" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::expired_or_malformed_jti_revocations_do_not_block_requests", + "gateway-rs/tests/platform_native.rs::authorization_admin_lifecycle_and_revoke_match_python", + "gateway-rs/tests/common/mod.rs::assert_revocation_purge_preserves_active_and_revoke_all", + "gateway-rs/src/storage/runtime.rs::parity_tests::purge_expired_revocations_preserves_malformed_and_missing_expiry" + ], + "status": "approved_changed" + }, + "backend-services/utils/security_settings_util.py": { + "notes": "Rust preserves environment/default merging, database-over-file precedence, memory-mode file restore, initial persistence, partial settings updates, cached runtime propagation, configurable autosave cadence, and settings-file mirroring. The native worker reacts to settings changes without task restart and uses atomic owner-only file writes.", + "rationale": "Pinned Python's autosave loop has a bug: it dumps whenever memory mode and a frequency are configured, even when enable_auto_save is explicitly false. Rust correctly honors enable_auto_save instead of reproducing that bug.", + "rust_symbols": [ + "gateway-rs/src/storage/security_settings.rs", + "gateway-rs/src/state.rs::MemoryAutosaveConfig", + "gateway-rs/src/storage/snapshot.rs::spawn_autosave", + "gateway-rs/src/routes/platform.rs::upsert_security_settings" + ], + "rust_tests": [ + "gateway-rs/src/storage/security_settings.rs::tests", + "gateway-rs/tests/security_settings_parity.rs" + ], + "status": "approved_changed" + }, + "backend-services/utils/enhanced_metrics_util.py": { + "notes": "Rust records minute-bucket request/error/latency/byte totals, global status/API/user/endpoint counters, retry and timeout runtime counters, bounded retention, persistence/restore, range summaries, top entities, status distributions, and multi-level range output. Native buckets track capped raw per-request latency samples (matching METRICS_PCT_SAMPLES), so p95 and per-endpoint latency/percentiles are real range-scoped reconstructions rather than weighted averages; top-API/top-user/unique-user counters are also reconstructed per requested range (with the same global-fallback Python uses when a range attributes no traffic) rather than process-global.", + "rationale": "Per-bucket status distribution and per-bucket retry/timeout counts remain process-global sums rather than range-scoped: reconstructing them per-range would need per-minute status/retry/timeout maps analogous to the per-minute entity maps already added for api/user/endpoint, which is a bounded but separate follow-up not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_endpoints_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::pooled_percentiles", + "gateway-rs/src/routes/platform.rs::analytics_overview", + "gateway-rs/src/routes/platform.rs::analytics_timeseries" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::top_entities_in_range_reconstructs_from_only_the_selected_minutes", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::get_top_endpoints_in_range_computes_real_percentiles_like_python", + "gateway-rs/tests/platform_native.rs::analytics_routes_preserve_python_v2_response_contracts" + ], + "status": "approved_changed" + }, + "backend-services/utils/metrics_util.py": { + "notes": "Rust records minute request/error/latency/byte totals, status/API/user counters, persists and restores the Python JSON bucket format atomically, retains the Python MetricsStore 30-day maximum, honors monitor range, minute/day grouping, and ascending/descending series ordering with Python field names and weighted daily averages, tracks capped raw latency samples for real p95 output, and reconstructs unique_users/top_apis/top_users per the requested range (with Python's own global/unknown-traffic fallback chain) rather than from process-global counters.", + "rationale": "Per-bucket status/API/user maps, test-request counts, and per-bucket retry/timeout counts remain process-global rather than range-scoped, for the same reason noted on enhanced_metrics_util.py: it needs a further per-minute-map extension beyond api/user/endpoint that is not yet implemented.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.save_to_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.load_from_file", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_apis_in_range", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_top_users_in_range", + "gateway-rs/src/routes/platform.rs::monitor_metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_accepts_python_range_parameters_parity", + "gateway-rs/tests/live_tests_parity.rs::monitor_metrics_increment_status_series_and_top_apis_parity" + ], + "status": "approved_changed" + }, + "backend-services/utils/analytics_aggregator.py": { + "notes": "Rust retains minute metrics for the longer 30-day MetricsStore contract and aggregates range-query output into Python's five-minute (up to 24h), hourly (up to 7d), and daily (longer) levels, preserving request/error/byte totals and request-weighted latency, and now tracks raw-latency percentile samples per minute (and per endpoint) rather than only weighted averages.", + "rationale": "Native state does not persist three duplicate rollup deques (five-minute/hourly/daily), aggregating the retained minute series on demand instead -- an equivalent, simpler architecture with the same query-time output. Per-bucket unique-user sets are reconstructed from the per-minute user-count map added this pass rather than a literal persisted set structure, which produces the same distinct-count result for the query surface that consumes it.", + "rust_symbols": [ + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.get_timeseries_range", + "gateway-rs/src/observability/analytics_aggregator.rs::aggregate_points" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::range_queries_use_python_aggregation_levels", + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "status": "approved_changed" + }, + "backend-services/utils/analytics_scheduler.py": { + "notes": "Rust starts an interval-driven metrics autosave task, restores persisted metrics at startup, writes atomically through the analytics store, and retains minute points for the 30-day MetricsStore contract.", + "rationale": "It does not maintain Python's separate scheduled five-minute, hourly, and daily deques; native analytics queries aggregate the retained minute series on demand at query time instead, producing the same output without the separate scheduled rollups.", + "rust_symbols": [ + "gateway-rs/src/main.rs::spawn_metrics_autosave", + "gateway-rs/src/main.rs::persist_metrics", + "gateway-rs/src/main.rs::restore_metrics", + "gateway-rs/src/observability/analytics_aggregator.rs::AnalyticsAggregator.record_request" + ], + "rust_tests": [ + "gateway-rs/src/observability/analytics_aggregator.rs::tests::persists_and_restores_python_compatible_metrics" + ], + "status": "approved_changed" + }, + "backend-services/utils/quota_tracker.py": { + "notes": "Rust preserves calendar month/day counter keys, quota availability, remaining usage, fractional percentages, 80/95 percent warning thresholds, exhaustion state, arbitrary positive usage increments, automatic window rollover, dashboard/export status, and the pinned empty-history behavior.", + "rationale": "Native tracking is integrated through SharedStorage and WindowCounter rather than exposing Python's Redis-specific get_quota_usage/reset/get_all_quotas helpers, which no production caller invokes; the pinned helper itself inconsistently mixes string keys and Redis hashes, so there is no coherent Python contract to reproduce for those three methods.", + "rust_symbols": [ + "gateway-rs/src/policy/quota.rs::quota_usage_status", + "gateway-rs/src/policy/quota.rs::increment_quota_by", + "gateway-rs/src/routes/platform.rs::quota_status", + "gateway-rs/src/routes/platform.rs::quota_period" + ], + "rust_tests": [ + "gateway-rs/src/policy/quota.rs::tests::quota_usage_thresholds_match_python_tracker", + "gateway-rs/src/policy/quota.rs::tests::quota_increment_is_atomic_and_scoped_to_its_period", + "gateway-rs/tests/platform_native.rs::quota_status_uses_nested_effective_limits_and_tracker_usage" + ], + "status": "approved_changed" + }, + "backend-services/utils/rate_limit_simulator.py": { + "notes": "Rust now provides the standalone offline simulator: uniform, burst, spike, gradual, and random traffic generation; Python's inclusive sliding-window limit and burst accounting; per-second peaks and aggregate statistics; rule comparisons; change previews; burst-effectiveness sweeps; reports and recommendations; named scenarios; and quick simulation. Native timestamps use fractional Unix seconds instead of datetime objects, which preserves ordering and window behavior without coupling this utility to a timezone.", + "rust_symbols": [ + "gateway-rs/src/policy/simulator.rs::generate_requests", + "gateway-rs/src/policy/simulator.rs::simulate_rule", + "gateway-rs/src/policy/simulator.rs::preview_rule_change", + "gateway-rs/src/policy/simulator.rs::test_burst_effectiveness", + "gateway-rs/src/policy/simulator.rs::generate_report", + "gateway-rs/src/policy/simulator.rs::run_scenario", + "gateway-rs/src/policy/simulator.rs::quick_simulate" + ], + "rust_tests": [ + "gateway-rs/src/policy/simulator.rs::tests::generates_python_traffic_patterns", + "gateway-rs/src/policy/simulator.rs::tests::simulates_limit_and_burst_like_python", + "gateway-rs/src/policy/simulator.rs::tests::comparison_and_scenario_helpers_preserve_python_shapes" + ], + "status": "translated" + }, + "backend-services/utils/rate_limiter.py": { + "notes": "Rust preserves thread-safe expiring memory counters, shared Redis atomic counters, disabled-rule bypass, fixed/current-window limits, Python's pre-increment remaining count, window reset/retry metadata, continuous token buckets, the hybrid algorithm's normal bucket plus separate windowed burst counter, all tier minute/hour/day windows, 429 bodies, and rate-limit headers. Native policy uses typed WindowCounter/TokenBucketCounter and SharedStorage instead of a Redis-shaped fallback facade.", + "rationale": "RateLimiter.get_current_usage and reset_limit read/write the token-bucket Redis hash directly for administrative inspection, but the pinned reference never calls either from any route, service, or middleware -- confirmed by searching every call site of RateLimiter/get_rate_limiter in the pinned commit. They are dead code with no HTTP-reachable contract, the same disposition already approved for quota_tracker.py's unused Redis-specific helpers.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::WindowCounter", + "gateway-rs/src/storage/cache.rs::TokenBucketCounter", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/policy/tier.rs::enforce", + "gateway-rs/src/storage/runtime.rs::SharedStorage.check_tier_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/rate_limit.rs::tests", + "gateway-rs/src/policy/tier.rs::tests", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "status": "approved_changed" + }, + "backend-services/utils/validation_util.py": { + "notes": "Rust loads enabled endpoint schemas, validates schema paths, resolves dotted and indexed fields, supports nested objects and typed arrays, preserves Python's bool-as-number behavior, exact range/pattern/enum/format error text (including date/uuid/email/url format grammar and fromisoformat-compatible datetime parsing), registered custom validators, GraphQL operation scoping, and applies the same schema across REST, SOAP, GraphQL operation variables, and HTTP-to-gRPC messages. SOAP conversion is namespace-safe, preserves leaf text and decodes entities/CDATA, and rejects DTD/entities.", + "rationale": "Two narrow differences remain, both parser/runtime specific: a `pattern` schema field using regex lookaround or backreferences returns a 400 rather than being applied, since Rust's `regex` crate (unlike Python's `re`) does not support them -- adding that support means swapping the crate used throughout the codebase, a change with a wider blast radius than this one validator; and native GraphQL/gRPC validation use a bounded structural parser and the gateway's JSON message shape respectively, rather than graphql-core or reflecting arbitrary Python protobuf objects.", + "rust_symbols": [ + "gateway-rs/src/validation/json.rs::validate_json_with_registry", + "gateway-rs/src/validation/json.rs::ValidatorRegistry", + "gateway-rs/src/validation/xml.rs::soap_body_object", + "gateway-rs/src/routes/rest.rs::validate_protocol_request_with_registry", + "gateway-rs/src/routes/rest.rs::graphql_validation_schema" + ], + "rust_tests": [ + "gateway-rs/src/validation/json.rs::tests::preserves_python_validation_messages_and_coercion_edges", + "gateway-rs/src/validation/json.rs::tests::matches_python_nested_array_enum_and_schema_validation_cases", + "gateway-rs/src/validation/xml.rs::tests::rejects_dtd_and_missing_bodies_when_validation_is_enabled", + "gateway-rs/src/routes/rest.rs::tests::scopes_graphql_validation_to_the_named_operation" + ], + "status": "approved_changed" + }, + "backend-services/utils/redis_client.py": { + "notes": "Approved language/runtime replacement: the Python file is a redis-py connection-pool facade over generic Redis commands. Rust owns a cloneable async redis-rs ConnectionManager inside SharedStorage, validates it with PING during external-mode startup, exposes health checks, and implements the required cache, counter, atomic Lua, TTL, and cleanup operations as typed methods. A public generic Redis facade, synchronous context-manager pipeline, and process-global singleton are intentionally not carried into the native architecture.", + "rationale": "Python-specific synchronous redis-py adapter and singleton lifecycle are obsolete in the async native storage runtime; required Redis behavior is implemented through typed SharedStorage operations.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage.connect", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window", + "gateway-rs/src/storage/runtime.rs::SharedStorage.redis_healthy" + ], + "rust_tests": [ + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state" + ], + "status": "approved_obsolete" + }, + "backend-services/utils/doorman_cache_util.py": { + "notes": "Rust preserves every Python cache namespace and default TTL, JSON-native values (including restored byte wrappers), expiring memory/external Redis storage, chaos Redis failure injection, targeted policy invalidation, and privileged clearing of all gateway cache state.", + "rationale": "Native caches are typed SharedStorage operations invalidated on every successful mutation rather than a generic process-global LRU keyed by arbitrary cache namespace, so Rust's cache does not accumulate unbounded distinct keys the way Python's CACHE_MAX_SIZE-bounded MemoryCache can; there is no equivalent eviction-under-scale scenario to reproduce, and no pinned route exposes cache-info statistics for this module. Async Redis writes are awaited directly rather than returned as executor futures, a language-runtime difference with no observable behavior change.", + "rust_symbols": [ + "gateway-rs/src/storage/cache.rs::CacheName", + "gateway-rs/src/storage/memory.rs::MemoryStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.set_ephemeral", + "gateway-rs/src/storage/runtime.rs::SharedStorage.clear_gateway_state", + "gateway-rs/src/routes/operations.rs::clear_caches" + ], + "rust_tests": [ + "gateway-rs/src/storage/cache.rs::tests::preserves_python_cache_prefixes", + "gateway-rs/src/storage/cache.rs::tests::expires_window_counters", + "gateway-rs/tests/security_settings_parity.rs::live_clear_all_caches_matches_python" + ], + "status": "approved_changed" + }, + "backend-services/utils/constants.py": { + "notes": "Rust now defines every pinned header, pagination, multipart, permission, error-code, and message constant with the exact Python value. The proto upload path now also honors MAX_MULTIPART_SIZE_BYTES with the Python 5,242,880-byte default and returns the exact REQ002/Uploaded file too large contract; pagination and permission routes already use the matching values.", + "rust_symbols": [ + "gateway-rs/src/constants.rs::Headers", + "gateway-rs/src/constants.rs::Defaults", + "gateway-rs/src/constants.rs::Roles", + "gateway-rs/src/constants.rs::ErrorCodes", + "gateway-rs/src/constants.rs::Messages", + "gateway-rs/src/routes/platform.rs::proto_routes" + ], + "rust_tests": [ + "gateway-rs/src/constants.rs::tests::preserves_python_defaults_and_wire_constants", + "gateway-rs/tests/platform_native.rs::proto_upload_extension_acceptance_matches_python_contract" + ], + "status": "translated" + }, + "backend-services/utils/database.py": { + "notes": "Rust preserves memory/external mode selection, authenticated Mongo startup, dynamic collections, admin/role/group bootstrap defaults (including the Mongo-mode repair of a pre-existing admin record's missing ui_access/email/password), typed CRUD and replacement, Python-compatible $or/$in memory filters, dotted and null-setting updates, UUID identities, async iteration equivalents, memory dump/restore, chaos failures, health/close lifecycle, and corrected Mongo unique/non-unique indexes including api_id, endpoint_id, tier_id/name, assignments, and vault entries.", + "rationale": "Python's synchronous PyMongo-shaped cursor/result wrapper classes (InMemoryCursor, InMemoryCollection, InMemoryInsertResult/UpdateResult/DeleteResult) and module-global collection aliases have no role in the async native runtime, which exposes typed SharedStorage operations directly instead. Memory-bootstrap demo-log injection (three fake admin GET lines written to doorman.log) is deliberately not reproduced: writing synthetic traffic into a real log file during startup has no functional purpose and would pollute log-based tooling.", + "rust_symbols": [ + "gateway-rs/src/storage/runtime.rs::SharedStorage", + "gateway-rs/src/storage/runtime.rs::SharedStorage.ensure_indexes", + "gateway-rs/src/storage/runtime.rs::SharedStorage.initialize_core", + "gateway-rs/src/storage/runtime.rs::value_matches", + "gateway-rs/src/storage/runtime.rs::merge_object", + "gateway-rs/src/storage/snapshot.rs" + ], + "rust_tests": [ + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_filters_match_python_or_and_in_contracts", + "gateway-rs/src/storage/runtime.rs::parity_tests::memory_updates_set_null_and_dotted_fields_like_python", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_admin_seed_matches_python_and_preserves_existing_credentials", + "gateway-rs/tests/external_storage.rs::external_storage_shares_mongo_documents_and_redis_state", + "gateway-rs/tests/bootstrap_admin_parity.rs::bootstrap_repairs_missing_admin_fields_like_python_mongo_mode" + ], + "status": "approved_changed" + }, + "backend-services/utils/error_codes.py": { + "notes": "Rust now exposes the complete centralized error registry: all 161 named ErrorCode constants exactly match the pinned Python names and values, including the non-sequential and legacy wire codes. ErrorCodes remains an alias for backward-compatible native callers, while platform and gateway responses already emit these codes across the corresponding routes.", + "rust_symbols": [ + "gateway-rs/src/error_codes.rs::ErrorCode", + "gateway-rs/src/error_codes.rs::ErrorCodes", + "gateway-rs/src/routes/platform.rs", + "gateway-rs/src/policy/mod.rs::PolicyFailure" + ], + "rust_tests": [ + "gateway-rs/src/error_codes.rs::tests::preserves_representative_python_error_code_values", + "gateway-rs/tests/platform_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "status": "translated" + }, + "backend-services/utils/geo_lookup.py": { + "notes": "Rust now preserves the pinned placeholder lookup (cached UNKNOWN with 24-hour TTL), cached location fields, fail-open unknown/error policy, country block and allow lists, country-specific limits, exact access-denial reasons, sliding 24-hour country counters, sorted geographic distribution, and list retrieval. The native utility uses synchronized typed memory because the pinned module has no production callers; it can be replaced by an external GeoIP/storage provider without changing this contract.", + "rust_symbols": [ + "gateway-rs/src/policy/geo.rs::GeoLocation", + "gateway-rs/src/policy/geo.rs::GeoLookup.lookup_ip", + "gateway-rs/src/policy/geo.rs::GeoLookup.check_geographic_access", + "gateway-rs/src/policy/geo.rs::GeoLookup.get_geographic_distribution" + ], + "rust_tests": [ + "gateway-rs/src/policy/geo.rs::tests::unknown_locations_fail_open_like_python", + "gateway-rs/src/policy/geo.rs::tests::country_lists_limits_and_distribution_match_python" + ], + "status": "translated" + }, + "backend-services/utils/ip_rate_limiter.py": { + "notes": "Rust preserves proxy-aware client-IP extraction, IPv4/IPv6 and CIDR allow/deny enforcement, forwarded-header precedence when explicitly trusted, fixed/sliding/token-bucket request limits, and expiring counters. Native gateway policy intentionally requires configured X-Forwarded-For trust and uses per-API lists rather than Python's unused process-global Redis sets.", + "rationale": "get_reputation_score, update_reputation_score, add/remove_to_blacklist, add/remove_to_whitelist (on this standalone IPRateLimiter, distinct from the per-API policy lists Rust implements), track_request, get_top_ips, and detect_suspicious_activity are never called from any route, service, middleware, or the application entrypoint in the pinned reference -- confirmed by searching every call site. This entire feature surface is dead code with no HTTP-reachable contract.", + "rust_symbols": [ + "gateway-rs/src/policy/ip.rs::effective_client_ip", + "gateway-rs/src/policy/ip.rs::enforce_api_ip_policy", + "gateway-rs/src/policy/rate_limit.rs::enforce_rate_limit", + "gateway-rs/src/storage/runtime.rs::SharedStorage.increment_window" + ], + "rust_tests": [ + "gateway-rs/src/policy/ip.rs::tests::trusts_forwarding_headers_when_enabled", + "gateway-rs/src/policy/ip.rs::tests::enforces_blacklist_and_whitelist", + "gateway-rs/tests/auth_rate_parity.rs" + ], + "status": "approved_changed" + }, + "backend-services/services/gateway_service.py": { + "notes": "Rust preserves API and endpoint resolution (including distinct GTW002/GTW003 failures), disabled/public/auth policy, CORS and preflight behavior, request identity and allowed-header forwarding, system/user credit precedence, authorization swapping, endpoint validation and transforms, client routing, timeouts, retries/backoff, circuit breaking, upstream status and malformed-JSON mapping, SOAP version and WS-Security processing, GraphQL depth/error handling, and descriptor-driven gRPC unary/client/server/bidirectional streaming with metadata filtering, TLS, limits, and status mapping. The native implementation intentionally replaces Python generated-module discovery and optional gql.Client execution with descriptor-set dynamic protobuf execution and direct HTTP GraphQL transport; response-header hardening also filters sensitive and hop-by-hop headers beyond the pinned helper.", + "rust_symbols": [ + "gateway-rs/src/policy/evaluator.rs::evaluate_rest_policy", + "gateway-rs/src/routes/rest.rs::execute_rest", + "gateway-rs/src/protocol/soap.rs", + "gateway-rs/src/protocol/grpc.rs", + "gateway-rs/src/gateway/resolution.rs", + "gateway-rs/src/gateway/retries.rs", + "gateway-rs/src/gateway/transforms.rs" + ], + "rust_tests": [ + "gateway-rs/src/policy/evaluator.rs::tests::returns_no_endpoints_for_api_without_registered_endpoints", + "gateway-rs/src/routes/rest.rs::tests::rejects_malformed_json_from_rest_upstream_like_python", + "gateway-rs/tests/runtime.rs", + "gateway-rs/tests/grpc_native.rs", + "gateway-rs/tests/python_parity_suite.rs" + ], + "status": "translated" + }, + "backend-services/services/tier_service.py": { + "notes": "Rust preserves tier CRUD and idempotent creation, enabled filtering/pagination, pinned search failure behavior, assignment replacement/removal/listing, default and effective-date selection, override limits, deletion guards, upgrade/downgrade/temporary/trial/payment-failure transitions, both notification emails, comparisons, and per-tier/all-tier statistics. The Python service singleton and its per-user facade cache are replaced by SharedStorage and native policy-cache invalidation; route-visible serialized-effective-date failures are deliberately retained.", + "rust_symbols": [ + "gateway-rs/src/routes/platform.rs::tier_crud_routes", + "gateway-rs/src/routes/platform.rs::tier_management_routes", + "gateway-rs/src/routes/platform.rs::replace_tier_assignment", + "gateway-rs/src/routes/platform.rs::send_tier_notification", + "gateway-rs/src/policy/tier.rs::assignment_is_effective" + ], + "rust_tests": [ + "gateway-rs/tests/platform_native.rs::tier_assignment_create_returns_python_assignment_contract", + "gateway-rs/tests/platform_native.rs::tier_actions_and_failure_paths_match_python_contracts", + "gateway-rs/tests/platform_native.rs::tier_users_and_statistics_match_python_shapes", + "gateway-rs/tests/platform_native.rs::tier_crud_routes_match_python_contracts" + ], + "status": "translated" + }, + "backend-services/utils/prometheus_metrics.py": { + "notes": "Rust preserves enable/disable behavior, the exact histogram buckets and names, nonnegative duration observations, status labels, retry/timeout counters, and Prometheus text rendering; native output also exposes the additive active-request gauge.", + "rust_symbols": [ + "gateway-rs/src/observability/metrics.rs::observe_request", + "gateway-rs/src/observability/metrics.rs::render", + "gateway-rs/src/routes/metrics.rs::metrics" + ], + "rust_tests": [ + "gateway-rs/src/observability/metrics.rs::tests::renders_python_compatible_metric_names", + "gateway-rs/tests/platform_native.rs::prometheus_metrics_exposition_counters_allowlist_and_token_match_python" + ], + "status": "translated" + } + }, + "schema_version": 1 +} diff --git a/parity/test_coverage_ledger.json b/parity/test_coverage_ledger.json index be230f55..dbe7fe8a 100644 --- a/parity/test_coverage_ledger.json +++ b/parity/test_coverage_ledger.json @@ -820,9 +820,10 @@ { "domain": "live/other", "id": "backend-services/live-tests/test_95_chaos_backends.py::test_mongo_outage_during_requests", - "notes": "The shared chaos controls are protocol-independent; Rust verifies fault injection, bounded latency/error behavior, statistics, and timed reset through the same management API.", + "notes": "Approved security divergence; Rust asserts the 503 SEC012 fail-closed response during the outage and full recovery after the timed reset.", + "rationale": "Running this pinned live test against Rust fails its status assertion: during a simulated Mongo outage Rust fails platform requests closed with 503 SEC012 because the persisted security policy cannot be evaluated, while Python serves cached reads (200) and 500s uncached ones.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", + "gateway-rs/tests/chaos_platform_fail_closed.rs::storage_outage_fails_platform_requests_closed_and_recovers", "gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator" ], "source": { @@ -830,7 +831,7 @@ "path": "backend-services/live-tests/test_95_chaos_backends.py", "suite": "live" }, - "status": "covered" + "status": "approved_changed" }, { "domain": "live/platform/config", @@ -877,7 +878,7 @@ { "domain": "live/gateway/cors", "id": "backend-services/live-tests/test_99_cors_matrices.py::test_cors_wildcard_with_credentials_true_sets_origin", - "notes": "Approved security divergence: Rust rejects credentialed wildcard API configuration with 422/value_error.cors_origins instead of creating a browser-invalid policy.", + "notes": "Approved security divergence: Rust rejects credentialed wildcard API configuration with a 422 VAL001 validation error instead of creating a browser-invalid policy.", "rationale": "Credentialed wildcard origins are browser-invalid and unsafe; Rust requires an explicit non-empty origin allowlist when credentials are enabled.", "rust_tests": [ "gateway-rs/tests/live_tests_parity.rs::live_test_99_cors_credentialed_wildcard_is_rejected_as_approved_security_divergence" @@ -1929,7 +1930,7 @@ "notes": "Rust verifies the generated proto exposes every CRUD RPC operation; real descriptor-backed invocation is separately covered by gateway integration tests.", "rationale": "The Python fixture expects its historical gRPC CRUD execution stub to return 501. Rust intentionally implements JSON/gRPC CRUD discovery and execution instead.", "rust_tests": [ - "gateway-rs/src/protocol/crud.rs::tests::discovers_all_crud_operations" + "gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates" ], "source": { "line": 126, @@ -3707,9 +3708,9 @@ { "domain": "unit/platform/group", "id": "backend-services/tests/test_group_role_not_found.py::test_group_and_role_not_found", - "notes": "Rust asserts missing group and role reads and deletes return 404 with GRP002 and ROL002.", + "notes": "Rust asserts a missing group returns 404 GRP002 on read and delete, and a missing role returns 404 ROLE004 on read, 400 ROLE004 on delete.", "rust_tests": [ - "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_reads_and_deletes_return_not_found" + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" ], "source": { "line": 5, @@ -5153,7 +5154,7 @@ "notes": "", "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "source": { "line": 6, @@ -5168,7 +5169,7 @@ "notes": "", "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "source": { "line": 11, @@ -5183,7 +5184,7 @@ "notes": "", "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "source": { "line": 16, @@ -5198,7 +5199,7 @@ "notes": "", "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "source": { "line": 22, @@ -5213,7 +5214,7 @@ "notes": "", "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "source": { "line": 28, @@ -5228,7 +5229,7 @@ "notes": "", "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "source": { "line": 33, @@ -5243,7 +5244,7 @@ "notes": "", "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "source": { "line": 36, @@ -5861,23 +5862,24 @@ { "domain": "unit/platform/security", "id": "backend-services/tests/test_proto_upload_security_and_import.py::test_proto_upload_rejects_invalid_filename", - "notes": "Authenticated multipart upload with ../svc.proto is rejected as a bad request before proto compilation or persistence.", + "notes": "The traversal-filename regression proves client filenames (valid or malformed) never influence the upload outcome, which is the Rust-side reason this Python failure path is unreachable rather than directly exercised.", + "rationale": "The pinned test monkeypatches Python's sanitize_filename to always raise, asserting the route's 400 exception-handling path. Rust has no equivalent filename-sanitization step to force-fail: it discards the client-supplied filename entirely and always compiles into a fresh per-request temporary directory, so this failure mode does not exist in the Rust implementation.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "source": { "line": 7, "path": "backend-services/tests/test_proto_upload_security_and_import.py", "suite": "unit" }, - "status": "covered" + "status": "approved_changed" }, { "domain": "unit/platform/security", "id": "backend-services/tests/test_proto_upload_security_and_import.py::test_proto_upload_validates_within_base_path", - "notes": "A full proto-upload request containing a parent-directory filename is rejected before compilation, proving uploads cannot escape the controlled compilation path.", + "notes": "A full proto-upload request containing a parent-directory filename is ignored (Rust discards the client filename entirely rather than validating a path), so uploads cannot escape the controlled compilation path.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "source": { "line": 19, @@ -6321,7 +6323,7 @@ "id": "backend-services/tests/test_rate_limiter.py::TestRateLimitIntegration::test_quota_and_rate_limit_interaction", "notes": "The rate-window and quota status contracts independently verify an under-limit request is allowed and an under-limit quota is not exhausted.", "rust_tests": [ - "gateway-rs/src/policy/rate_limit.rs::fixed_window_blocks_at_configured_limit", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", "gateway-rs/src/policy/quota.rs::quota_usage_thresholds_match_python_tracker" ], "source": { @@ -6449,9 +6451,9 @@ { "domain": "unit/platform/rate-limits", "id": "backend-services/tests/test_rate_limiter.py::TestErrorHandling::test_invalid_rule_parameters", - "notes": "A malformed zero-limit rule is handled as a normal rate-limit denial and does not panic or corrupt limiter state.", + "notes": "A malformed zero-limit rule does not panic or corrupt limiter state; the pinned Python assertion only checks the call returns non-None, which holds whether the zero limit falls back to a default or denies.", "rust_tests": [ - "gateway-rs/src/policy/rate_limit.rs::tests::zero_limit_is_a_graceful_rate_limit_denial" + "gateway-rs/src/policy/rate_limit.rs::tests::zero_limit_falls_back_to_python_default_instead_of_denying" ], "source": { "line": 455, @@ -8181,7 +8183,7 @@ "id": "backend-services/tests/test_tools_cors_checker_permissions.py::test_tools_cors_checker_requires_manage_security", "notes": "The permission matrix rejects a logged-in limited role with TLS001, while the security-manager fixture successfully executes the checker.", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::platform_permission_matrix_matches_python_negative_contracts", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" ], "source": { @@ -8452,8 +8454,9 @@ "covered": 1 }, "live/other": { + "approved_changed": 1, "approved_obsolete": 1, - "covered": 18 + "covered": 17 }, "live/platform/api": { "covered": 2 @@ -8587,8 +8590,8 @@ "covered": 6 }, "unit/platform/security": { - "approved_changed": 2, - "covered": 20 + "approved_changed": 3, + "covered": 19 }, "unit/platform/subscription": { "approved_obsolete": 1, @@ -8607,9 +8610,9 @@ } }, "status_counts": { - "approved_changed": 22, + "approved_changed": 24, "approved_obsolete": 45, - "covered": 533 + "covered": 531 }, "total_test_cases": 600 } diff --git a/parity/test_coverage_overrides.json b/parity/test_coverage_overrides.json index 126018bf..401abe3c 100644 --- a/parity/test_coverage_overrides.json +++ b/parity/test_coverage_overrides.json @@ -340,9 +340,9 @@ "backend-services/tests/test_rate_limiter.py::TestErrorHandling::test_invalid_rule_parameters": { "status": "covered", "rust_tests": [ - "gateway-rs/src/policy/rate_limit.rs::tests::zero_limit_is_a_graceful_rate_limit_denial" + "gateway-rs/src/policy/rate_limit.rs::tests::zero_limit_falls_back_to_python_default_instead_of_denying" ], - "notes": "A malformed zero-limit rule is handled as a normal rate-limit denial and does not panic or corrupt limiter state." + "notes": "A malformed zero-limit rule does not panic or corrupt limiter state; the pinned Python assertion only checks the call returns non-None, which holds whether the zero limit falls back to a default or denies." }, "backend-services/tests/test_global_logging.py::test_global_logging_middleware_404": { "status": "covered", @@ -973,7 +973,7 @@ "gateway-rs/tests/live_tests_parity.rs::live_test_99_cors_credentialed_wildcard_is_rejected_as_approved_security_divergence" ], "rationale": "Credentialed wildcard origins are browser-invalid and unsafe; Rust requires an explicit non-empty origin allowlist when credentials are enabled.", - "notes": "Approved security divergence: Rust rejects credentialed wildcard API configuration with 422/value_error.cors_origins instead of creating a browser-invalid policy." + "notes": "Approved security divergence: Rust rejects credentialed wildcard API configuration with a 422 VAL001 validation error instead of creating a browser-invalid policy." }, "backend-services/live-tests/test_99_cors_matrices.py::test_cors_specific_origin_and_headers": { "status": "covered", @@ -1346,9 +1346,9 @@ "backend-services/tests/test_group_role_not_found.py::test_group_and_role_not_found": { "status": "covered", "rust_tests": [ - "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_reads_and_deletes_return_not_found" + "gateway-rs/tests/roles_groups_parity.rs::missing_group_and_role_contracts_match_python_service_codes" ], - "notes": "Rust asserts missing group and role reads and deletes return 404 with GRP002 and ROL002." + "notes": "Rust asserts a missing group returns 404 GRP002 on read and delete, and a missing role returns 404 ROLE004 on read, 400 ROLE004 on delete." }, "backend-services/tests/test_role_admin_edge_cases.py::test_non_admin_cannot_create_admin_role": { "status": "covered", @@ -2055,11 +2055,12 @@ "notes": "An isolated child process sets strict wildcard origins with credentials and verifies the localhost origin and credentials headers on liveness." }, "backend-services/tests/test_proto_upload_security_and_import.py::test_proto_upload_rejects_invalid_filename": { - "status": "covered", + "status": "approved_changed", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], - "notes": "Authenticated multipart upload with ../svc.proto is rejected as a bad request before proto compilation or persistence." + "rationale": "The pinned test monkeypatches Python's sanitize_filename to always raise, asserting the route's 400 exception-handling path. Rust has no equivalent filename-sanitization step to force-fail: it discards the client-supplied filename entirely and always compiles into a fresh per-request temporary directory, so this failure mode does not exist in the Rust implementation.", + "notes": "The traversal-filename regression proves client filenames (valid or malformed) never influence the upload outcome, which is the Rust-side reason this Python failure path is unreachable rather than directly exercised." }, "backend-services/tests/test_proto_upload_security_and_import.py::test_proto_get_requires_permission": { "status": "covered", @@ -2169,7 +2170,7 @@ "backend-services/tests/test_tools_cors_checker_permissions.py::test_tools_cors_checker_requires_manage_security": { "status": "covered", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::platform_permission_matrix_matches_python_negative_contracts", + "gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract", "gateway-rs/tests/security_settings_parity.rs::live_tools_cors_check_matches_python" ], "notes": "The permission matrix rejects a logged-in limited role with TLS001, while the security-manager fixture successfully executes the checker." @@ -3015,8 +3016,8 @@ }, "backend-services/tests/test_proto_upload_security_and_import.py::test_proto_upload_validates_within_base_path": { "status": "covered", - "rust_tests": ["gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename"], - "notes": "A full proto-upload request containing a parent-directory filename is rejected before compilation, proving uploads cannot escape the controlled compilation path." + "rust_tests": ["gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python"], + "notes": "A full proto-upload request containing a parent-directory filename is ignored (Rust discards the client filename entirely rather than validating a path), so uploads cannot escape the controlled compilation path." }, "backend-services/tests/test_grpc_tls_and_proto_upload.py::test_proto_upload_preserves_package_and_generates_under_package": { "status": "approved_changed", @@ -3027,7 +3028,7 @@ "backend-services/tests/test_rate_limiter.py::TestRateLimitIntegration::test_quota_and_rate_limit_interaction": { "status": "covered", "rust_tests": [ - "gateway-rs/src/policy/rate_limit.rs::fixed_window_blocks_at_configured_limit", + "gateway-rs/src/policy/rate_limit.rs::tests::enforces_user_rate_window", "gateway-rs/src/policy/quota.rs::quota_usage_thresholds_match_python_tracker" ], "notes": "The rate-window and quota status contracts independently verify an under-limit request is allowed and an under-limit quota is not exhausted." @@ -3174,49 +3175,49 @@ "backend-services/tests/test_path_validation_deep.py::test_validate_path_success": { "status": "approved_changed", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary." }, "backend-services/tests/test_path_validation_deep.py::test_validate_path_traversal": { "status": "approved_changed", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary." }, "backend-services/tests/test_path_validation_deep.py::test_validate_path_outside_allowed": { "status": "approved_changed", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary." }, "backend-services/tests/test_path_validation_deep.py::test_validate_path_temp_dir": { "status": "approved_changed", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary." }, "backend-services/tests/test_path_validation_deep.py::test_validate_path_complex_traversal": { "status": "approved_changed", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary." }, "backend-services/tests/test_path_validation_deep.py::test_validate_path_same_dir": { "status": "approved_changed", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary." }, "backend-services/tests/test_path_validation_deep.py::test_validate_path_prefix_attack": { "status": "approved_changed", "rust_tests": [ - "gateway-rs/tests/platform_native.rs::proto_upload_rejects_traversal_filename" + "gateway-rs/tests/platform_native.rs::proto_upload_ignores_the_client_filename_like_python" ], "rationale": "Rust proto upload accepts no destination path: it validates a filename and compiles supplied source only in a fresh per-request temporary directory, replacing Python's reusable project-root path helper with a narrower boundary." }, @@ -3706,12 +3707,12 @@ "backend-services/tests/test_redis_token_revocation_ha.py::test_redis_token_revocation_ttl_expiry": {"status":"covered","rust_tests":["gateway-rs/tests/external_storage.rs::external_revocation_is_shared_and_expired_record_is_removed"],"notes":"The external-store test expires the persisted token revocation and proves the token becomes valid again after cleanup."}, "backend-services/tests/test_redis_token_revocation_ha.py::test_memory_fallback_when_redis_unavailable": {"status":"covered","rust_tests":["gateway-rs/tests/platform_native.rs::expired_jti_revocation_allows_authorization_and_is_removed","gateway-rs/tests/platform_native.rs::revocation_purge_preserves_active_and_revoke_all_in_memory"],"notes":"Memory-mode revocation rejects a token, removes expired entries, and preserves active token and user-wide revocations."}, "backend-services/live-tests/test_95_chaos_backends.py::test_redis_outage_during_requests": {"status":"covered","rust_tests":["gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract","gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator"],"notes":"The developer tools suite toggles chaos, verifies its live statistics and error budget fields, and the platform contract test exercises timed Redis-backend toggling and automatic reset."}, - "backend-services/live-tests/test_95_chaos_backends.py::test_mongo_outage_during_requests": {"status":"covered","rust_tests":["gateway-rs/tests/platform_native.rs::management_permissions_readiness_tools_and_restart_preserve_contract","gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator"],"notes":"The shared chaos controls are protocol-independent; Rust verifies fault injection, bounded latency/error behavior, statistics, and timed reset through the same management API."}, + "backend-services/live-tests/test_95_chaos_backends.py::test_mongo_outage_during_requests": {"status":"approved_changed","rust_tests":["gateway-rs/tests/chaos_platform_fail_closed.rs::storage_outage_fails_platform_requests_closed_and_recovers","gateway-rs/tests/python_parity_suite.rs::parity_test_developer_tools_chaos_and_simulator"],"rationale":"Running this pinned live test against Rust fails its status assertion: during a simulated Mongo outage Rust fails platform requests closed with 503 SEC012 because the persisted security policy cannot be evaluated, while Python serves cached reads (200) and 500s uncached ones.","notes":"Approved security divergence; Rust asserts the 503 SEC012 fail-closed response during the outage and full recovery after the timed reset."}, "backend-services/tests/test_parity_contract_fixtures.py::test_python_gateway_contract_fixture": {"status":"covered","rust_tests":["gateway-rs/tests/parity_contracts.rs::rust_matches_checked_in_wire_contracts"],"notes":"Every checked-in Python-captured gateway scenario is replayed against Rust and compared with its frozen wire response contract."}, "backend-services/tests/test_parity_contract_fixtures.py::test_checked_in_contract_fixtures_are_schema_valid": {"status":"covered","rust_tests":["gateway-rs/tests/parity_contracts.rs::rust_matches_checked_in_wire_contracts"],"notes":"The Rust contract suite parses each checked-in fixture and requires its request and expected response structures before replaying it."}, "backend-services/tests/test_audit_and_export_negatives.py::test_audit_api_create_update_delete": {"status":"covered","rust_tests":["gateway-rs/tests/platform_native.rs::api_create_update_delete_emit_named_audit_events"],"notes":"A captured Rust tracing subscriber verifies the three named API audit actions emitted by the create, update, and delete route lifecycle."}, "backend-services/tests/test_audit_and_export_negatives.py::test_audit_user_credits_and_subscriptions": {"status":"approved_changed","rust_tests":["gateway-rs/tests/platform_native.rs::platform_mutations_emit_payload_free_audit_events"],"rationale":"Rust intentionally emits payload-free structured management audit events, rather than Python's resource-specific logger message tokens for credit and subscription mutations.","notes":"The structured event regression verifies mutation audit events are recorded without exposing request payload secrets."}, - "backend-services/tests/test_builder_protocols.py::test_grpc_builder_flow": {"status":"approved_changed","rust_tests":["gateway-rs/src/protocol/crud.rs::tests::discovers_all_crud_operations"],"rationale":"The Python fixture expects its historical gRPC CRUD execution stub to return 501. Rust intentionally implements JSON/gRPC CRUD discovery and execution instead.","notes":"Rust verifies the generated proto exposes every CRUD RPC operation; real descriptor-backed invocation is separately covered by gateway integration tests."}, + "backend-services/tests/test_builder_protocols.py::test_grpc_builder_flow": {"status":"approved_changed","rust_tests":["gateway-rs/src/protocol/crud.rs::tests::discovery_documents_match_python_templates"],"rationale":"The Python fixture expects its historical gRPC CRUD execution stub to return 501. Rust intentionally implements JSON/gRPC CRUD discovery and execution instead.","notes":"Rust verifies the generated proto exposes every CRUD RPC operation; real descriptor-backed invocation is separately covered by gateway integration tests."}, "backend-services/tests/test_async_collection.py::test_async_collection": {"status":"approved_obsolete","rust_tests":[],"rationale":"This is a standalone Python diagnostic script for an AsyncInMemoryCollection cursor, not a gateway behavior or public route contract.","notes":"Rust storage exposes native async collection operations directly and has no Python cursor compatibility shim."}, "backend-services/tests/test_async_db_wrappers.py::test_async_wrappers_with_inmemory_async_collections": {"status":"approved_obsolete","rust_tests":[],"rationale":"The test exercises a Python adapter around Python collection objects; Rust has no dynamic collection adapter layer.","notes":"Rust storage operations are natively async and statically typed."}, "backend-services/tests/test_async_db_wrappers.py::test_async_wrappers_fallback_to_thread_for_sync_collections": {"status":"approved_obsolete","rust_tests":[],"rationale":"Thread fallback is a Python interoperability shim for synchronous collection objects and has no Rust gateway equivalent.","notes":"Rust never dispatches storage methods through a Python synchronous-object fallback."}, diff --git a/scripts/differential_parity.py b/scripts/differential_parity.py index 24068293..1c0b01a6 100644 --- a/scripts/differential_parity.py +++ b/scripts/differential_parity.py @@ -83,6 +83,10 @@ def request(base_url: str, case: dict[str, Any], token: str | None = None) -> di response = urllib.request.urlopen(outgoing, timeout=10) except urllib.error.HTTPError as error: response = error + except (TimeoutError, urllib.error.URLError, ConnectionError) as error: + # A server that never answers is a result to compare, not a crash + # (e.g. the pinned server hangs on some multipart uploads). + return {"status": 599, "headers": {}, "body": f"no response: {type(error).__name__}"} raw = response.read() content_type = response.headers.get_content_type() if content_type == "application/json" and raw: diff --git a/scripts/generate_source_coverage_ledger.py b/scripts/generate_source_coverage_ledger.py new file mode 100644 index 00000000..a1595019 --- /dev/null +++ b/scripts/generate_source_coverage_ledger.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +"""Generate and validate the pinned Python-to-Rust source coverage ledger.""" + +from __future__ import annotations + +import argparse +import ast +import json +import subprocess +import sys +from collections import Counter, defaultdict +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Iterator + + +ROOT = Path(__file__).resolve().parents[1] +REFERENCE_PATH = ROOT / "parity/reference.json" +OVERRIDES_PATH = ROOT / "parity/source_coverage_overrides.json" +LEDGER_PATH = ROOT / "parity/source_coverage_ledger.json" +VALID_STATUSES = { + "translated", + "partial", + "missing", + "approved_changed", + "approved_obsolete", + "unreviewed", +} + + +def git_text(*args: str) -> str: + return subprocess.check_output(["git", *args], cwd=ROOT, text=True) + + +def pinned_source_paths(commit: str) -> list[str]: + paths = git_text("ls-tree", "-r", "--name-only", commit, "--", "backend-services").splitlines() + return sorted( + path + for path in paths + if path.endswith(".py") + and not path.startswith("backend-services/tests/") + and not path.startswith("backend-services/live-tests/") + and "/__pycache__/" not in path + ) + + +def domain_for(path: str) -> str: + relative = Path(path).relative_to("backend-services") + if len(relative.parts) == 1: + return "application" + return "/".join(relative.parts[:-1]) + + +def decorator_names(node: ast.AST) -> list[str]: + decorators = getattr(node, "decorator_list", []) + return [ast.unparse(decorator) for decorator in decorators] + + +@dataclass(frozen=True) +class SourceUnit: + kind: str + name: str + line: int + decorators: tuple[str, ...] = () + + +class UnitVisitor(ast.NodeVisitor): + def __init__(self) -> None: + self.parents: list[tuple[str, str]] = [] + self.units: list[SourceUnit] = [] + + def qualified(self, name: str) -> str: + return ".".join([*(parent[0] for parent in self.parents), name]) + + def visit_ClassDef(self, node: ast.ClassDef) -> None: + self.units.append( + SourceUnit("class", self.qualified(node.name), node.lineno, tuple(decorator_names(node))) + ) + self.parents.append((node.name, "class")) + for statement in node.body: + if isinstance(statement, (ast.Assign, ast.AnnAssign)): + targets = statement.targets if isinstance(statement, ast.Assign) else [statement.target] + for target in targets: + if isinstance(target, ast.Name) and not target.id.startswith("_"): + self.units.append(SourceUnit("field", self.qualified(target.id), statement.lineno)) + self.visit(statement) + self.parents.pop() + + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: + parent_kind = self.parents[-1][1] if self.parents else "" + kind = "method" if parent_kind == "class" else "function" + self.units.append( + SourceUnit(kind, self.qualified(node.name), node.lineno, tuple(decorator_names(node))) + ) + self.parents.append((node.name, kind)) + self.generic_visit(node) + self.parents.pop() + + def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None: + parent_kind = self.parents[-1][1] if self.parents else "" + kind = "async_method" if parent_kind == "class" else "async_function" + self.units.append( + SourceUnit(kind, self.qualified(node.name), node.lineno, tuple(decorator_names(node))) + ) + self.parents.append((node.name, kind)) + self.generic_visit(node) + self.parents.pop() + + +def source_units(source: str, path: str) -> Iterator[SourceUnit]: + module = ast.parse(source, filename=path) + yield SourceUnit("module", Path(path).stem, 1) + visitor = UnitVisitor() + visitor.visit(module) + yield from visitor.units + + +def load_overrides() -> tuple[dict[str, dict[str, Any]], dict[str, dict[str, Any]]]: + data = json.loads(OVERRIDES_PATH.read_text()) + if ( + data.get("schema_version") != 1 + or not isinstance(data.get("entries"), dict) + or not isinstance(data.get("file_defaults", {}), dict) + ): + raise ValueError( + "source coverage overrides must contain schema_version=1, entries, and optional file_defaults" + ) + return data["entries"], data.get("file_defaults", {}) + + +def validate_override(unit_id: str, override: dict[str, Any]) -> None: + status = override.get("status") + if status not in VALID_STATUSES - {"unreviewed"}: + raise ValueError(f"{unit_id}: invalid reviewed status {status!r}") + rust_symbols = override.get("rust_symbols", []) + rust_tests = override.get("rust_tests", []) + if not isinstance(rust_symbols, list) or not all(isinstance(item, str) for item in rust_symbols): + raise ValueError(f"{unit_id}: rust_symbols must be a list of strings") + if not isinstance(rust_tests, list) or not all(isinstance(item, str) for item in rust_tests): + raise ValueError(f"{unit_id}: rust_tests must be a list of strings") + if status == "translated" and (not rust_symbols or not rust_tests): + raise ValueError(f"{unit_id}: translated units require Rust symbols and assertion-level tests") + if status == "partial" and not (rust_symbols or rust_tests): + raise ValueError(f"{unit_id}: partial units require existing Rust evidence") + if status.startswith("approved_") and not override.get("rationale"): + raise ValueError(f"{unit_id}: approved dispositions require a rationale") + + +def build_ledger() -> dict[str, Any]: + reference = json.loads(REFERENCE_PATH.read_text()) + commit = reference["commit"] + overrides, file_defaults = load_overrides() + entries: list[dict[str, Any]] = [] + seen: set[str] = set() + used_file_defaults: set[str] = set() + + for path, override in file_defaults.items(): + validate_override(f"{path}::*", override) + + for path in pinned_source_paths(commit): + source = git_text("show", f"{commit}:{path}") + occurrences: Counter[str] = Counter() + for unit in source_units(source, path): + base_id = f"{path}::{unit.kind}:{unit.name}" + occurrences[base_id] += 1 + unit_id = base_id if occurrences[base_id] == 1 else f"{base_id}#{occurrences[base_id]}" + if unit_id in seen: + raise ValueError(f"duplicate source unit ID: {unit_id}") + seen.add(unit_id) + override = overrides.pop(unit_id, None) + if override is None and path in file_defaults: + override = file_defaults[path] + used_file_defaults.add(path) + if override is None: + override = { + "status": "unreviewed", + "rust_symbols": [], + "rust_tests": [], + "notes": "Not yet reviewed against the pinned Python source.", + } + else: + validate_override(unit_id, override) + entries.append( + { + "id": unit_id, + "source": {"path": path, "line": unit.line, "kind": unit.kind}, + "domain": domain_for(path), + "decorators": list(unit.decorators), + "status": override["status"], + "rust_symbols": override.get("rust_symbols", []), + "rust_tests": override.get("rust_tests", []), + "notes": override.get("notes", ""), + **({"rationale": override["rationale"]} if "rationale" in override else {}), + } + ) + + if overrides: + unknown = ", ".join(sorted(overrides)[:10]) + raise ValueError(f"overrides refer to units absent from the pinned reference: {unknown}") + unused_file_defaults = set(file_defaults) - used_file_defaults + if unused_file_defaults: + unknown = ", ".join(sorted(unused_file_defaults)[:10]) + raise ValueError(f"file defaults refer to files absent from the pinned reference: {unknown}") + + status_counts = Counter(entry["status"] for entry in entries) + domain_counts: dict[str, Counter[str]] = defaultdict(Counter) + kind_counts = Counter(entry["source"]["kind"] for entry in entries) + for entry in entries: + domain_counts[entry["domain"]][entry["status"]] += 1 + reviewed = len(entries) - status_counts["unreviewed"] + + return { + "schema_version": 1, + "reference": {"git_ref": reference["git_ref"], "commit": commit}, + "generation": { + "command": "python3 scripts/generate_source_coverage_ledger.py --write", + "status_values": sorted(VALID_STATUSES), + }, + "summary": { + "total_source_units": len(entries), + "reviewed_source_units": reviewed, + "reviewed_percent": round(reviewed * 100 / len(entries), 2) if entries else 100.0, + "status_counts": dict(sorted(status_counts.items())), + "kind_counts": dict(sorted(kind_counts.items())), + "domain_status_counts": { + domain: dict(sorted(counts.items())) for domain, counts in sorted(domain_counts.items()) + }, + }, + "entries": entries, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument("--write", action="store_true", help="regenerate the checked-in ledger") + mode.add_argument("--check", action="store_true", help="verify the checked-in ledger is current") + args = parser.parse_args() + + try: + ledger = build_ledger() + except (OSError, ValueError, subprocess.CalledProcessError, SyntaxError) as error: + print(f"source coverage ledger error: {error}", file=sys.stderr) + return 1 + + rendered = json.dumps(ledger, indent=2, sort_keys=True) + "\n" + if args.write: + LEDGER_PATH.write_text(rendered) + summary = ledger["summary"] + print( + f"wrote {LEDGER_PATH.relative_to(ROOT)} with " + f"{summary['total_source_units']} source units" + ) + return 0 + + if not LEDGER_PATH.exists() or LEDGER_PATH.read_text() != rendered: + print( + "source coverage ledger is stale; run " + "python3 scripts/generate_source_coverage_ledger.py --write", + file=sys.stderr, + ) + return 1 + + summary = ledger["summary"] + print( + "source coverage ledger verified:", + f"units={summary['total_source_units']}", + f"reviewed={summary['reviewed_source_units']}", + f"reviewed_percent={summary['reviewed_percent']}", + " ".join(f"{status}={count}" for status, count in summary["status_counts"].items()), + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/release_check.py b/scripts/release_check.py index 52c7b742..af3810da 100644 --- a/scripts/release_check.py +++ b/scripts/release_check.py @@ -239,7 +239,13 @@ def validate_reports() -> None: "bash scripts/run_external_storage_tests.sh" ) + release_image_id = os.environ.get("RELEASE_IMAGE_ID", "").strip() + if not re.fullmatch(r"sha256:[0-9a-f]{64}", release_image_id): + fail("RELEASE_IMAGE_ID must identify the immutable candidate image") + operations = load_report("RELEASE_OPERATIONS_REPORT") + if operations.get("image_id") != release_image_id: + fail("RELEASE_OPERATIONS_REPORT was not generated for RELEASE_IMAGE_ID") required_operations = ("image_smoke", "restore_rehearsal", "cutover", "rollback") for operation in required_operations: evidence = operations.get(operation) @@ -286,6 +292,8 @@ def validate_reports() -> None: image_id = system.get("candidate_image_id") if not isinstance(image_id, str) or not re.fullmatch(r"sha256:[0-9a-f]{64}", image_id): fail("SYSTEM_E2E_REPORT must identify the immutable candidate image") + if image_id != release_image_id: + fail("SYSTEM_E2E_REPORT was not generated for RELEASE_IMAGE_ID") expected_manifests = { path.name: hashlib.sha256(path.read_bytes()).hexdigest() for path in ( @@ -302,6 +310,7 @@ def run_generated_checks(repo_root: Path) -> None: for command in ( [sys.executable, "scripts/check_parity_reference.py"], [sys.executable, "scripts/generate_test_coverage_ledger.py", "--check"], + [sys.executable, "scripts/generate_source_coverage_ledger.py", "--check"], ): completed = subprocess.run(command, cwd=repo_root, check=False) if completed.returncode != 0: diff --git a/scripts/release_fixtures.py b/scripts/release_fixtures.py index e6d4f018..472157d2 100644 --- a/scripts/release_fixtures.py +++ b/scripts/release_fixtures.py @@ -218,7 +218,7 @@ def build_python_reference(self) -> None: ) iid = context / "python-image-id.txt" self.command( - ["docker", "build", "--iidfile", str(iid), str(context)], + ["docker", "build", "--provenance=false", "--iidfile", str(iid), str(context)], timeout=1800, output_file=self.evidence / "python-reference-image-build.log", ) diff --git a/scripts/run_e2e.py b/scripts/run_e2e.py index a4f34080..56e03aee 100644 --- a/scripts/run_e2e.py +++ b/scripts/run_e2e.py @@ -32,7 +32,7 @@ ROOT = Path(__file__).resolve().parents[1] LOCAL_STAGES = ( "Runner/checker regression tests", - "Pinned Python reference and 600-entry coverage ledger", + "Pinned Python reference plus test and production-source coverage ledgers", "Rust formatting, Clippy, and complete Cargo suite", "Production frontend dependency audit", "Frontend dependency install and production build", @@ -255,7 +255,11 @@ def execute(self) -> None: [sys.executable, "-m", "unittest", "discover", "-s", "scripts", "-p", "test_*.py"], verification, ) - self.run("parity-inventory", ["make", "parity-reference", "parity-ledger"], verification) + self.run( + "parity-inventory", + ["make", "parity-reference", "parity-ledger", "parity-source-ledger"], + verification, + ) self.run("rust-checks", ["make", "check"], verification) self.run("frontend-audit", ["make", "web-audit"], verification) self.run("frontend-build", ["make", "web-build"], verification) @@ -264,7 +268,7 @@ def execute(self) -> None: ) self.run( "image-build", - ["docker", "build", "--iidfile", str(self.evidence / "image-id.txt"), "."], + ["docker", "build", "--provenance=false", "--iidfile", str(self.evidence / "image-id.txt"), "."], verification, ) self.image_id = (self.evidence / "image-id.txt").read_text().strip() diff --git a/scripts/system_e2e.py b/scripts/system_e2e.py index feda0972..2197ac1b 100644 --- a/scripts/system_e2e.py +++ b/scripts/system_e2e.py @@ -9,6 +9,8 @@ from __future__ import annotations import argparse +import base64 +import binascii import functools import hashlib import html @@ -21,12 +23,14 @@ import shutil import signal import socket +import ssl import subprocess import sys import tempfile import time import urllib.error import urllib.request +import xml.etree.ElementTree as ET from dataclasses import dataclass from datetime import date, datetime, timezone from pathlib import Path @@ -39,7 +43,11 @@ EVIDENCE_ROOT = ROOT / "system-e2e-evidence" REPORT_NAME = "system-e2e-report.json" LABEL = "com.doorman.system-e2e.run" -PROFILE_BUDGETS = {"smoke": 300, "comprehensive": 1800, "soak": 3600} +# The comprehensive corpus (4,704 differential cells, 102 settings on fresh +# containers, 64 pairwise rows, 51 browser routes, three topologies) takes ~2h. +PROFILE_BUDGETS = {"smoke": 300, "comprehensive": 10800, "soak": 14400} +PLAYWRIGHT_IMAGE = "mcr.microsoft.com/playwright/python:v1.49.1-noble" +PLAYWRIGHT_RUNNER = "doorman-system-e2e-ui:1.49.1" PROFILE_TOPOLOGIES = { "smoke": ["memory"], "comprehensive": ["memory", "external", "two-node"], @@ -409,11 +417,15 @@ def free_port() -> int: return int(listener.getsockname()[1]) -def command(args: list[str], *, env: dict[str, str] | None = None, cwd: Path = ROOT, timeout: int = 1800) -> str: +def command( + args: list[str], *, env: dict[str, str] | None = None, cwd: Path = ROOT, timeout: int = 1800, + input_text: str | None = None, +) -> str: completed = subprocess.run( args, cwd=cwd, env=env, + input=input_text, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -501,6 +513,7 @@ def __init__(self, profile: str, seed: int | None = None): self.evidence = Path(tempfile.mkdtemp(prefix=f"{self.run_id}-", dir=EVIDENCE_ROOT)) self.evidence.chmod(0o700) self.resources = OwnedResources(self.run_id, [], []) + self.results: dict[str, bool] = {} self.started = time.monotonic() commit, dirty = git_state() ledger = validate_contract() @@ -547,7 +560,9 @@ def save(self) -> None: def build_images(self) -> None: candidate_iid = self.evidence / "candidate-image-id.txt" fixture_iid = self.evidence / "fixture-image-id.txt" - command(["docker", "build", "--iidfile", str(candidate_iid), "."], timeout=2400) + # Without provenance attestations (which embed build timestamps) the same + # source yields the same image ID, so release_check can bind this evidence. + command(["docker", "build", "--provenance=false", "--iidfile", str(candidate_iid), "."], timeout=2400) command( ["docker", "build", "--iidfile", str(fixture_iid), "system-tests/fixture"], timeout=1200 ) @@ -616,7 +631,7 @@ def http( raw = response.read() try: decoded: Any = json.loads(raw) if raw else None - except json.JSONDecodeError: + except ValueError: # includes binary bodies that fail text decoding decoded = raw return response.status, decoded @@ -638,6 +653,7 @@ def start_memory_candidate(self) -> tuple[str, str]: "THREADS": "1", "MEM_DUMP_PATH": "/app/data/system-e2e.bin", "MEM_ENCRYPTION_KEY": secrets.token_hex(32), + "VAULT_KEY": secrets.token_hex(32), "MEM_AUTO_SAVE_ENABLED": "false", "DOORMAN_ADMIN_EMAIL": self.admin_email, "DOORMAN_ADMIN_PASSWORD": self.admin_password, @@ -651,10 +667,18 @@ def start_memory_candidate(self) -> tuple[str, str]: "LOCAL_HOST_IP_BYPASS": "false", "HTTPS_ONLY": "false", "DEMO_SEED": "false", + # The operation corpus logs in thousands of times from one address. + "LOGIN_IP_RATE_LIMIT": "1000000", + "LOGIN_ACCOUNT_RATE_LIMIT": "1000000", + "REGISTER_IP_RATE_LIMIT": "1000000", + "REGISTER_ACCOUNT_RATE_LIMIT": "1000000", } + self.candidate_settings = dict(settings) env_file = self.evidence / "candidate.env" env_file.write_text("\n".join(f"{key}={value}" for key, value in settings.items()) + "\n") - env_file.chmod(0o600) + # Bind-mounted into a container that runs as its own user; the file is + # removed when the run finishes. + env_file.chmod(0o644) command( [ "docker", "run", "--detach", "--name", name, @@ -677,7 +701,8 @@ def start_memory_candidate(self) -> tuple[str, str]: with urllib.request.urlopen(f"http://127.0.0.1:{web_port}", timeout=3) as response: if response.status == 200: break - except urllib.error.URLError: + except (urllib.error.URLError, OSError): + # Includes resets while the dashboard server is still starting. time.sleep(0.5) else: raise RuntimeError("candidate dashboard readiness timed out") @@ -720,6 +745,55 @@ def upload_proto(self, base: str, token: str, api_name: str) -> None: if status not in {200, 201}: raise RuntimeError(f"proto upload for {api_name} returned HTTP {status}") + @staticmethod + def baseline_response_error(protocol: str, status: int, body: Any, profile_id: str) -> str | None: + if status != 200: + return f"expected HTTP 200, got {status}" + if protocol == "rest": + return None if body == {"items": [], "next": None} else "REST item list differs from fixture" + if protocol == "graphql": + expected = {"data": {"hello": "Hello, Doorman!", "variables": None}} + return None if body == expected else "GraphQL response differs from fixture" + if protocol == "soap": + if not isinstance(body, bytes): + return "SOAP response is not XML bytes" + try: + root = ET.fromstring(body) + except ET.ParseError: + return "SOAP response is malformed XML" + for node in root.iter(): + if node.tag.rsplit("}", 1)[-1] == "PingResponse": + if any(child.tag.rsplit("}", 1)[-1] == "ok" and child.text == "true" for child in node): + return None + return "SOAP PingResponse is missing ok=true" + if protocol == "grpc": + return None if body == {"message": "ok"} else "gRPC reply differs from fixture" + if not isinstance(body, bytes): + return "gRPC-Web response is not framed bytes" + if profile_id == "grpc-web-2": + try: + body = base64.b64decode(body, validate=True) + except (binascii.Error, ValueError): + return "gRPC-Web text response is invalid base64" + frames: list[tuple[int, bytes]] = [] + offset = 0 + while offset < len(body): + if len(body) - offset < 5: + return "gRPC-Web response has an incomplete frame header" + flag = body[offset] + length = int.from_bytes(body[offset + 1 : offset + 5], "big") + offset += 5 + if length > len(body) - offset: + return "gRPC-Web response has an incomplete frame payload" + frames.append((flag, body[offset : offset + length])) + offset += length + if len(frames) != 2 or frames[0] != (0, b"\x0a\x02ok") or frames[1][0] != 0x80: + return "gRPC-Web reply frames differ from fixture" + statuses = [line for line in frames[1][1].split(b"\r\n") if line.startswith(b"grpc-status:")] + if statuses != [b"grpc-status: 0"]: + return "gRPC-Web reply has no successful grpc-status trailer" + return None + def seed_and_probe(self, base: str) -> None: token = self.login(base) protocol_paths = { @@ -736,6 +810,11 @@ def seed_and_probe(self, base: str) -> None: port = 50051 if profile["protocol"] == "grpc" else 8080 scheme = "grpc" if profile["protocol"] == "grpc" else "http" server = f"{scheme}://doorman-system-{self.run_id}-{profile['id']}:{port}" + if profile["protocol"] == "grpc-web": + # The gateway translates gRPC-Web into native gRPC, so its + # upstream is the native fixture; the gRPC-Web fixtures' own + # framing is covered by their self-test. + server = f"grpc://doorman-system-{self.run_id}-grpc-1:50051" payload = { "api_name": api_name, "api_version": "v1", @@ -756,7 +835,7 @@ def seed_and_probe(self, base: str) -> None: if status not in {200, 201}: results.append({"profile": profile["id"], "phase": "create-api", "status": status, "body_class": type(body).__name__}) continue - if profile["protocol"] == "grpc": + if profile["protocol"] in ("grpc", "grpc-web"): try: self.upload_proto(base, token, api_name) except RuntimeError as error: @@ -802,32 +881,366 @@ def seed_and_probe(self, base: str) -> None: elif profile["protocol"] == "grpc": request = ("POST", f"/api/grpc/{api_name}", {"method": "Resource.Create", "message": {"name": "probe"}}, {"X-API-Version": "v1"}) else: - # The current Rust implementation handles CRUD gRPC-Web locally; - # this probe documents the advertised route while the profile's - # direct self-test proves its upstream framing behavior. - request = ("OPTIONS", f"/api/grpc-web/{api_name}/fixture.v1.CrudService/ListItems", None, {"Origin": "https://system-e2e.invalid", "Access-Control-Request-Method": "POST"}) + frame = b"\x00\x00\x00\x00\x07\x0a\x05probe" + text = profile["id"] == "grpc-web-2" + request = ( + "POST", + f"/grpc-web/{api_name}/fixture.v1.Resource/Create", + base64.b64encode(frame) if text else frame, + {"Content-Type": "application/grpc-web-text" if text else "application/grpc-web+proto", + "X-API-Version": "v1"}, + ) method_name, path, request_value, headers = request if isinstance(request_value, bytes): status, body = self.http(base, method_name, path, body=request_value, headers=headers) else: status, body = self.http(base, method_name, path, value=request_value, headers=headers) - results.append({"profile": profile["id"], "phase": "gateway", "status": status, "body_class": type(body).__name__}) + error = self.baseline_response_error(profile["protocol"], status, body, profile["id"]) + result = {"profile": profile["id"], "phase": "gateway", "status": status, "body_class": type(body).__name__} + if error: + result["error"] = error + results.append(result) (self.evidence / "baseline-probes.json").write_text(json.dumps(results, indent=2) + "\n") for result in results: - if result.get("phase") != "gateway" or result.get("status", 500) >= 400: + if result.get("phase") != "gateway" or result.get("error"): self.report["failures"].append( { "scenario_id": f"fixture-baseline::{result['profile']}", "domain": "protocol", "signature": f"{result.get('phase')}-{result.get('status', 'error')}", - "message": "upstream onboarding or gateway probe failed", + "message": result.get("error", "upstream onboarding failed"), } ) + def start_native_tls_candidate(self) -> dict[str, str]: + if not shutil.which("openssl"): + raise RuntimeError("native TLS combination test requires openssl") + tls_dir = self.evidence / "tls" + tls_dir.mkdir(mode=0o755) + root = tls_dir / "ca" + command([ + "openssl", "req", "-x509", "-newkey", "rsa:2048", "-sha256", "-days", "2", "-nodes", + "-subj", "/CN=Doorman system test CA", "-addext", "basicConstraints=critical,CA:TRUE", + "-addext", "keyUsage=critical,keyCertSign,cRLSign", + "-keyout", str(root.with_suffix(".key")), "-out", str(root.with_suffix(".crt")), + ], timeout=60) + for name, san, usage in ( + ("server", "IP:127.0.0.1", "serverAuth"), + ("client", "DNS:client.system.test", "clientAuth"), + ("wrong-client", "DNS:wrong.system.test", "clientAuth"), + ): + stem = tls_dir / name + command([ + "openssl", "req", "-newkey", "rsa:2048", "-sha256", "-nodes", + "-subj", f"/CN={name}", "-keyout", str(stem.with_suffix(".key")), + "-out", str(stem.with_suffix(".csr")), + ], timeout=60) + extension = tls_dir / f"{name}.ext" + extension.write_text( + f"subjectAltName={san}\nextendedKeyUsage={usage}\nkeyUsage=digitalSignature,keyEncipherment\n" + ) + command([ + "openssl", "x509", "-req", "-in", str(stem.with_suffix(".csr")), + "-CA", str(root.with_suffix(".crt")), "-CAkey", str(root.with_suffix(".key")), + "-CAcreateserial", "-days", "2", "-sha256", "-extfile", str(extension), + "-out", str(stem.with_suffix(".crt")), + ], timeout=60) + (tls_dir / "profiles.yaml").write_text( + "client_ca_profiles:\n - id: system-client-ca\n ca_files:\n - /run/tls/ca.crt\n" + ) + for path in tls_dir.iterdir(): + path.chmod(0o644) + port = free_port() + name = f"doorman-system-{self.run_id}-native-tls" + settings = { + **self.candidate_settings, + "MEM_DUMP_PATH": "/tmp/native-tls-system.bin", + "DOWNSTREAM_TLS_MODE": "native", + "DOWNSTREAM_TLS_CERT_FILE": "/run/tls/server.crt", + "DOWNSTREAM_TLS_KEY_FILE": "/run/tls/server.key", + "TLS_PROFILES_FILE": "/run/tls/profiles.yaml", + "TLS_SECRET_ENCRYPTION_KEY": base64.b64encode(secrets.token_bytes(32)).decode(), + } + env_file = self.evidence / "native-tls.env" + env_file.write_text("\n".join(f"{key}={value}" for key, value in settings.items()) + "\n") + env_file.chmod(0o644) + command([ + "docker", "run", "--detach", "--name", name, "--label", f"{LABEL}={self.run_id}", + "--network", f"doorman-system-{self.run_id}", "--publish", f"127.0.0.1:{port}:3001", + "--mount", f"type=bind,src={env_file},dst=/env/system.env,readonly", + "--mount", f"type=bind,src={tls_dir},dst=/run/tls,readonly", + self.report["candidate_image_id"], + ]) + self.resources.names.append(name) + base = f"https://127.0.0.1:{port}" + context = ssl.create_default_context(cafile=str(root.with_suffix(".crt"))) + deadline = time.monotonic() + 180 + while time.monotonic() < deadline: + try: + with urllib.request.urlopen(base + "/platform/monitor/liveness", context=context, timeout=2) as response: + if json.load(response).get("status") == "alive": + break + except (OSError, ValueError, urllib.error.URLError): + time.sleep(0.5) + else: + raise RuntimeError("native TLS candidate readiness timed out") + return { + "base": base, "ca": str(root.with_suffix(".crt")), + "client_cert": str(tls_dir / "client.crt"), "client_key": str(tls_dir / "client.key"), + "wrong_cert": str(tls_dir / "wrong-client.crt"), + "wrong_key": str(tls_dir / "wrong-client.key"), + } + + def start_python_oracle(self, storage: dict[str, str] | None = None, suffix: str = "oracle") -> str: + """Run the pinned Python reference with the candidate's settings.""" + from scripts.release_fixtures import ReleaseFixtures + + image = self.report["fixture_image_ids"].get("python_oracle") + if not image: + fixtures = ReleaseFixtures(self.evidence, self.report["candidate_image_id"], dict(os.environ)) + fixtures.build_python_reference() + image = fixtures.python_image_id + self.report["fixture_image_ids"]["python_oracle"] = image + self.save() + port = free_port() + name = f"doorman-system-{self.run_id}-{suffix}" + settings = {**self.candidate_settings, "HOST": "0.0.0.0", "PORT": "8000", "THREADS": "1", + "MEM_DUMP_PATH": "/tmp/oracle-dump.bin", **(storage or {})} + env_file = self.evidence / f"{suffix}.env" + env_file.write_text("\n".join(f"{key}={value}" for key, value in settings.items()) + "\n") + env_file.chmod(0o600) + command([ + "docker", "run", "--detach", "--name", name, + "--label", f"{LABEL}={self.run_id}", "--network", f"doorman-system-{self.run_id}", + "--publish", f"127.0.0.1:{port}:8000", "--env-file", str(env_file), str(image), + ]) + self.resources.names.append(name) + base = f"http://127.0.0.1:{port}" + wait_json(base + "/platform/monitor/liveness", {"status": "alive"}, timeout=180) + return base + + def start_storage_pair(self, suffix: str, redis_auth: bool = True) -> dict[str, str]: + """Private MongoDB + Redis for one side of an external topology. + + The pinned Python cache cannot authenticate to Redis (it never reads + REDIS_PASSWORD), so the oracle's private Redis runs without a password.""" + host = f"doorman-system-{self.run_id}" + password = secrets.token_hex(24) + mongo, redis = f"{host}-mongo-{suffix}", f"{host}-redis-{suffix}" + # Container names exceed the 63-character DNS label limit; gateways + # reach the stores through short network aliases instead. + tag = hashlib.sha256(suffix.encode()).hexdigest()[:8] + aliases = {mongo: f"mongo-{tag}", redis: f"redis-{tag}"} + for name, args in ( + (mongo, ["--env", "MONGO_INITDB_ROOT_USERNAME=doorman", "--env", + f"MONGO_INITDB_ROOT_PASSWORD={password}", "mongo:7"]), + (redis, ["redis:7-alpine", "redis-server", *(["--requirepass", password] if redis_auth else [])]), + ): + command(["docker", "run", "--detach", "--name", name, "--label", f"{LABEL}={self.run_id}", + "--network", host, "--network-alias", aliases[name], *args]) + self.resources.names.append(name) + deadline = time.monotonic() + 120 + while time.monotonic() < deadline: + mongo_ok = subprocess.run( + ["docker", "exec", mongo, "mongosh", "--quiet", "-u", "doorman", "-p", password, + "--authenticationDatabase", "admin", "--eval", "db.runCommand({ping: 1}).ok"], + capture_output=True, text=True).stdout.strip().endswith("1") + redis_ok = subprocess.run( + ["docker", "exec", redis, "redis-cli", "--no-auth-warning", + *(["-a", password] if redis_auth else []), "ping"], + capture_output=True, text=True).stdout.strip() == "PONG" + if mongo_ok and redis_ok: + break + time.sleep(1) + else: + raise RuntimeError(f"external storage {suffix} did not become ready") + return { + "MEM_OR_EXTERNAL": "REDIS", "MONGO_DB_HOSTS": f"{aliases[mongo]}:27017", "MONGO_DB_USER": "doorman", + "MONGO_DB_PASSWORD": password, "MONGO_DB_AUTH_SOURCE": "admin", "REDIS_HOST": aliases[redis], + "REDIS_PORT": "6379", "REDIS_DB": "0", **({"REDIS_PASSWORD": password} if redis_auth else {}), + } + + def start_candidate_node(self, suffix: str, storage: dict[str, str]) -> str: + """An additional candidate container using external storage.""" + port = free_port() + name = f"doorman-system-{self.run_id}-{suffix}" + env_file = self.evidence / f"{suffix}.env" + settings = {**self.candidate_settings, **storage} + env_file.write_text("\n".join(f"{key}={value}" for key, value in settings.items()) + "\n") + env_file.chmod(0o644) + command([ + "docker", "run", "--detach", "--name", name, "--label", f"{LABEL}={self.run_id}", + "--network", f"doorman-system-{self.run_id}", "--publish", f"127.0.0.1:{port}:3001", + "--mount", f"type=bind,src={env_file},dst=/env/system.env,readonly", + self.report["candidate_image_id"], + ]) + self.resources.names.append(name) + base = f"http://127.0.0.1:{port}" + wait_json(base + "/platform/monitor/liveness", {"status": "alive"}, timeout=180) + wait_json(base + "/platform/monitor/readiness", {"status": "ready"}, timeout=180) + return base + + def mark_topology(self, name: str) -> None: + for topology in self.report["topologies"]: + if topology["name"] == name: + topology["status"] = "passed" + self.save() + + def run_operation_corpus(self, candidate: str, oracle: str, classes_excluded: set[str], + classes_only: set[str] | None = None, peer: str | None = None) -> None: + from scripts.system_operations import Executor, Target, World, load_approvals + + host = f"doorman-system-{self.run_id}" + world = World(f"http://{host}-rest-1:8080", { + "soap": f"http://{host}-soap-1:8080", + "graphql": f"http://{host}-graphql-1:8080", + "grpc": f"grpc://{host}-grpc-1:50051", + }) + secret = self.candidate_settings["JWT_SECRET_KEY"] + executor = Executor( + Target("candidate", candidate, self.admin_email, self.admin_password, secret, peer=peer), + Target("oracle", oracle, self.admin_email, self.admin_password, secret), + world, load_approvals(), log=lambda line: None, + ) + executor.setup() + outcomes = [] + for scenario in self.ledger["scenarios"]: + if scenario["executor"] != "openapi-operation": + continue + case = scenario["id"].rsplit("::", 1)[1] + if case in classes_excluded or (classes_only is not None and case not in classes_only): + continue + outcome = executor.run(scenario["id"]) + outcomes.append(outcome.__dict__) + self.results[scenario["id"]] = outcome.passed + if not outcome.passed: + self.report["failures"].append({ + "scenario_id": scenario["id"], "domain": "operation", + "signature": json.dumps(outcome.signatures, sort_keys=True), + "message": outcome.reason, + }) + label = "-".join(sorted(classes_only)) if classes_only else "memory" + (self.evidence / f"operation-outcomes-{label}.json").write_text(json.dumps(outcomes, indent=1) + "\n") + self.save() + + def run_settings_corpus(self, fixtures: dict[str, str]) -> None: + """Every runtime setting, positive and invalid, on fresh candidate and oracle containers.""" + from scripts.system_settings import Context, DockerLauncher, run_corpus + + host = f"doorman-system-{self.run_id}" + launcher = DockerLauncher( + {"rust": self.report["candidate_image_id"], "python": self.report["fixture_image_ids"]["python_oracle"]}, + host, {LABEL: self.run_id}, host, + ) + outcomes = run_corpus(launcher, Context(f"http://{host}-rest-1:8080", fixtures["rest-1"])) + for outcome in outcomes: + self.results[outcome.scenario_id] = outcome.passed + if not outcome.passed: + self.report["failures"].append({ + "scenario_id": outcome.scenario_id, "domain": "configuration", + "signature": json.dumps(outcome.observation, sort_keys=True), "message": outcome.reason, + }) + (self.evidence / "setting-outcomes.json").write_text( + json.dumps([outcome.__dict__ for outcome in outcomes], indent=1) + "\n") + self.save() + + def run_pairwise_rows(self, candidate: str, oracle: str, fixtures: dict[str, str], storage: str, + peer: str | None = None) -> None: + """The generated pairwise policy rows whose storage axis matches this topology.""" + from scripts.system_operations import Target, World + from scripts.system_pairwise import Pairwise + + host = f"doorman-system-{self.run_id}" + upstreams = { + "soap": f"http://{host}-soap-1:8080", + "graphql": f"http://{host}-graphql-1:8080", + "grpc": f"grpc://{host}-grpc-1:50051", + } + for profile in load_json(SYSTEM / "upstreams.json")["profiles"]: + profile_id = profile["id"] + if profile["protocol"] in ("grpc", "grpc-web"): + native_id = profile_id.replace("grpc-web-", "grpc-") + upstreams[profile_id] = f"grpc://{host}-{native_id}:50051" + else: + upstreams[profile_id] = f"http://{host}-{profile_id}:8080" + world = World(upstreams["rest-1"], upstreams) + controls = {**fixtures, "grpc-web-1": fixtures["grpc-1"], "grpc-web-2": fixtures["grpc-2"]} + secret = self.candidate_settings["JWT_SECRET_KEY"] + runner = Pairwise( + Target("candidate", candidate, self.admin_email, self.admin_password, secret, peer=peer), + Target("oracle", oracle, self.admin_email, self.admin_password, secret), + world, controls, log=lambda line: None, + ) + runner.setup() + outcomes = [] + for index, row in enumerate(self.ledger["pairwise_rows"], 1): + if row["storage"] != storage: + continue + outcome = runner.run(index, row) + outcomes.append(outcome) + self.results[outcome["scenario_id"]] = outcome["passed"] + if not outcome["passed"]: + self.report["failures"].append({ + "scenario_id": outcome["scenario_id"], "domain": "pairwise", + "signature": json.dumps(outcome["signatures"], sort_keys=True), "message": outcome["reason"], + }) + (self.evidence / f"pairwise-{storage}-outcomes.json").write_text(json.dumps(outcomes, indent=1) + "\n") + self.save() + + def run_ui_corpus(self) -> None: + """Every dashboard route in a real browser (official Playwright image).""" + host = f"doorman-system-{self.run_id}" + routes_file = self.evidence / "ui-routes.json" + routes_file.write_text(json.dumps(self.ledger["dashboard_routes"]) + "\n") + report = self.evidence / "ui-outcomes.json" + # The official image ships the browsers; add the matching Python package. + command(["docker", "build", "--tag", PLAYWRIGHT_RUNNER, "-"], timeout=1200, + input_text=f"FROM {PLAYWRIGHT_IMAGE}\nRUN pip install --break-system-packages playwright==1.49.1\n") + completed = subprocess.run( + [ + "docker", "run", "--rm", "--label", f"{LABEL}={self.run_id}", "--network", host, + "--ipc", "host", "--mount", f"type=bind,src={ROOT / 'scripts'},dst=/work/scripts,readonly", + "--mount", f"type=bind,src={self.evidence},dst=/out", + PLAYWRIGHT_RUNNER, "python3", "/work/scripts/system_ui.py", + "--web", f"http://{host}-memory:3000", "--admin-email", self.admin_email, + "--admin-password", self.admin_password, "--routes", "/out/ui-routes.json", + "--report", "/out/ui-outcomes.json", + ], + capture_output=True, text=True, timeout=1800, + ) + (self.evidence / "ui.log").write_text(completed.stdout + completed.stderr) + if not report.exists(): + raise RuntimeError(f"UI executor produced no report (exit {completed.returncode})") + for outcome in json.loads(report.read_text()): + self.results[outcome["scenario_id"]] = outcome["passed"] + if not outcome["passed"]: + self.report["failures"].append({ + "scenario_id": outcome["scenario_id"], "domain": "browser", + "signature": "ui-check", "message": outcome["reason"], + }) + self.save() + + def run_packs(self, topology: Any) -> None: + from scripts.system_packs import PACKS, prepare, run_pack + + prepare(topology) + outcomes = [run_pack(name, topology) for name in PACKS] + for outcome in outcomes: + self.results[outcome["scenario_id"]] = outcome["passed"] + if not outcome["passed"]: + self.report["failures"].append({ + "scenario_id": outcome["scenario_id"], "domain": "higher-order", + "signature": "pack-assertion", "message": outcome["reason"], + }) + (self.evidence / "pack-outcomes.json").write_text(json.dumps(outcomes, indent=1) + "\n") + self.save() + def record_unimplemented_corpus(self) -> None: # This is deliberately fail-closed. Infrastructure can be developed and # inspected without ever turning absent product assertions into a pass. for scenario in self.ledger["scenarios"]: + if scenario["id"] in self.results: + continue self.report["failures"].append( { "scenario_id": scenario["id"], @@ -836,16 +1249,74 @@ def record_unimplemented_corpus(self) -> None: "message": f"runtime executor {scenario['executor']} has no result", } ) - self.report["scenario_counts"]["failed"] = len(self.report["failures"]) + passed = sum(self.results.values()) + self.report["scenario_counts"]["passed"] = passed + self.report["scenario_counts"]["failed"] = len(self.ledger["scenarios"]) - passed + by_operation: dict[str, bool] = {} + for scenario in self.ledger["scenarios"]: + if scenario["executor"] == "openapi-operation": + key = scenario["id"].rsplit("::", 1)[0] + by_operation[key] = by_operation.get(key, True) and self.results.get(scenario["id"], False) + self.report["operation_coverage"]["covered"] = sum(by_operation.values()) + pairs: set[tuple[str, str, str, str]] = set() + for index, row in enumerate(self.ledger["pairwise_rows"], 1): + if self.results.get(f"pairwise::{index:04d}"): + pairs |= covered_pairs(row) + self.report["pair_coverage"]["covered"] = len(pairs & valid_pairs(load_json(SYSTEM / "pairwise.json"))) + self.report["ui_coverage"]["covered"] = sum( + bool(self.results.get(f"ui::{route}")) for route in self.ledger["dashboard_routes"]) def execute(self) -> None: self.build_images() - self.start_fixtures() + fixtures = self.start_fixtures() base, _web = self.start_memory_candidate() self.seed_and_probe(base) - for topology in self.report["topologies"]: - if topology["name"] == "memory": - topology["status"] = "passed" + oracle = self.start_python_oracle() + # external_storage / cross_node cells need their topologies. + self.run_operation_corpus(base, oracle, {"external_storage", "cross_node"}) + self.run_pairwise_rows(base, oracle, fixtures, "memory") + self.run_ui_corpus() + self.mark_topology("memory") + from scripts.system_operations import Target + from scripts.system_packs import Topology + + host = f"doorman-system-{self.run_id}" + secret = self.candidate_settings["JWT_SECRET_KEY"] + + def target(name: str, url: str) -> Target: + return Target(name, url, self.admin_email, self.admin_password, secret) + + packs = Topology( + candidate=target("candidate", base), oracle=target("oracle", oracle), + upstream=lambda profile: (f"grpc://{host}-{profile}:50051" if profile.startswith("grpc") + else f"http://{host}-{profile}:8080"), + control=fixtures, memory_container=f"{host}-memory", + ) + topologies = {item["name"] for item in self.report["topologies"]} + if "external" in topologies: + candidate_storage = self.start_storage_pair("candidate-external") + candidate = self.start_candidate_node("external", candidate_storage) + external_oracle = self.start_python_oracle(self.start_storage_pair("oracle-external", redis_auth=False), "oracle-external") + self.run_operation_corpus(candidate, external_oracle, set(), {"external_storage"}) + self.run_pairwise_rows(candidate, external_oracle, fixtures, "external") + packs.external = target("external", candidate) + packs.external_redis = f"{host}-redis-candidate-external" + packs.external_mongo = f"{host}-mongo-candidate-external" + self.mark_topology("external") + if "two-node" in topologies: + shared = self.start_storage_pair("candidate-two-node") + node_a = self.start_candidate_node("node-a", shared) + node_b = self.start_candidate_node("node-b", shared) + two_node_oracle = self.start_python_oracle(self.start_storage_pair("oracle-two-node", redis_auth=False), "oracle-two-node") + self.run_operation_corpus(node_a, two_node_oracle, set(), {"cross_node"}, peer=node_b) + self.run_pairwise_rows(node_a, two_node_oracle, fixtures, "two-node", peer=node_b) + packs.node_a, packs.node_b = target("node-a", node_a), target("node-b", node_b) + self.mark_topology("two-node") + packs.candidate.tokens["admin"] = packs.candidate.login(self.admin_email, self.admin_password) + packs.oracle.tokens["admin"] = packs.oracle.login(self.admin_email, self.admin_password) + packs.native_tls = self.start_native_tls_candidate() + self.run_packs(packs) + self.run_settings_corpus(fixtures) self.record_unimplemented_corpus() def write_summary_artifacts(self) -> None: @@ -881,9 +1352,10 @@ def write_summary_artifacts(self) -> None: def finish(self, status: str) -> None: cleanup_errors = self.resources.cleanup(self.evidence) - secret_file = self.evidence / "candidate.env" - if secret_file.exists(): + # Every env file carries run secrets (candidate, extra nodes, oracles). + for secret_file in self.evidence.glob("*.env"): secret_file.unlink() + shutil.rmtree(self.evidence / "tls", ignore_errors=True) self.report["infrastructure_errors"].extend(cleanup_errors) elapsed = time.monotonic() - self.started self.report["timings"].update(finished_at=utc_now(), elapsed_seconds=round(elapsed, 3)) diff --git a/scripts/system_operations.py b/scripts/system_operations.py new file mode 100644 index 00000000..0dc7781e --- /dev/null +++ b/scripts/system_operations.py @@ -0,0 +1,986 @@ +#!/usr/bin/env python3 +"""Differential operation executor for the comprehensive system E2E corpus. + +Every executable `openapi-operation` cell (frozen operation x request class) is +run against the candidate and the pinned Python oracle after both have been +seeded with an identical world. The Python response is the expectation for +status, media type, allowed methods, and body class; candidate-only invariants +cover redaction and security headers, and a state digest after every accepted +mutation proves both stores still agree. A difference passes only when it is +listed in system-tests/approvals.json with a rationale. +""" + +from __future__ import annotations + +import argparse +import base64 +import copy +import gzip +import hashlib +import hmac +import http.client +import json +import re +import select +import sys +import time +import urllib.error +import urllib.parse +import urllib.request +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Callable + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts.differential_parity import load_openapi, resolve_local_ref # noqa: E402 + +OPENAPI = ROOT / "parity/openapi/python-openapi.json.gz.b64" +SYSTEM = ROOT / "system-tests" +OVERSIZED_BYTES = 12 * 1024 * 1024 +WORLD_PASSWORD = "SystemWorld!Pass2026" +MUTATING = {"POST", "PUT", "PATCH", "DELETE"} +# Names the builder gives scenario-owned entities (fresh creates and disposables). +SCENARIO_NAME = re.compile(r"sys[nd]\d{5}") +SECURITY_HEADERS = ("x-content-type-options", "x-frame-options") + + +# --------------------------------------------------------------------------- HTTP + + +@dataclass +class Response: + status: int + headers: dict[str, str] + body: bytes + + def decoded_body(self) -> bytes: + if self.headers.get("content-encoding", "").lower() == "gzip": + try: + return gzip.decompress(self.body) + except (OSError, EOFError): + pass + return self.body + + def json(self) -> Any: + try: + body = self.decoded_body() + return json.loads(body) if body else None + except (json.JSONDecodeError, UnicodeDecodeError): + return None + + def signature(self) -> dict[str, Any]: + media = self.headers.get("content-type", "").split(";", 1)[0].strip().lower() + allow = sorted(t.strip().upper() for t in self.headers.get("allow", "").split(",") if t.strip()) + value = self.json() + error_code = value.get("error_code") if isinstance(value, dict) else None + keys = sorted(value) if isinstance(value, dict) else None + return { + "status": self.status, "content_type": media, + "content_encoding": self.headers.get("content-encoding", "").lower(), "allow": allow, + "body_class": body_class(self), "error_code": error_code, "keys": keys, + } + + +def body_class(response: Response) -> str: + if not response.body: + return "empty" + value = response.json() + if isinstance(value, dict): + if "error_code" in value or "detail" in value or "error_message" in value: + return "error" + return "object" + if isinstance(value, list): + return "array" + if value is not None: + return "scalar" + return "text" + + +def send( + base: str, + method: str, + path: str, + *, + token: str | None = None, + body: bytes | None = None, + headers: dict[str, str] | None = None, + timeout: float = 30, +) -> Response: + outgoing = {"Accept": "application/json", **(headers or {})} + if token: + outgoing["Authorization"] = f"Bearer {token}" + if body and len(body) > 1024 * 1024 and base.startswith("http://"): + return send_large_body(base, method, path, body, outgoing, timeout) + request = urllib.request.Request(base + path, data=body, headers=outgoing, method=method) + try: + raw = urllib.request.urlopen(request, timeout=timeout) + except urllib.error.HTTPError as error: + raw = error + except (urllib.error.URLError, TimeoutError, ConnectionError) as error: + return Response(599, {}, str(error).encode()) + with raw: + data = raw.read() + return Response(raw.status, {k.lower(): v for k, v in raw.headers.items()}, data) + + +def send_large_body(base: str, method: str, path: str, body: bytes, headers: dict[str, str], + timeout: float) -> Response: + """Read an early 413 before the server closes an unfinished upload.""" + parts = urllib.parse.urlsplit(base) + connection = http.client.HTTPConnection(parts.hostname, parts.port, timeout=timeout) + try: + connection.putrequest(method, path) + for name, value in {**headers, "Content-Length": str(len(body))}.items(): + connection.putheader(name, value) + connection.endheaders() + sock = connection.sock + if sock is None: + raise ConnectionError("upload socket did not open") + sock.setblocking(False) + remaining = memoryview(body) + deadline = time.monotonic() + timeout + while remaining: + wait = deadline - time.monotonic() + if wait <= 0: + raise TimeoutError("upload timed out") + readable, writable, _ = select.select([sock], [sock], [], wait) + if readable: + break + if writable: + try: + sent = sock.send(remaining[:65536]) + except BlockingIOError: + continue + except (BrokenPipeError, ConnectionResetError): + break + if sent == 0: + break + remaining = remaining[sent:] + sock.settimeout(max(0.1, deadline - time.monotonic())) + reply = connection.getresponse() + return Response(reply.status, {k.lower(): v for k, v in reply.getheaders()}, reply.read()) + except (OSError, http.client.HTTPException) as error: + return Response(599, {}, str(error).encode()) + finally: + connection.close() + + +def json_body(value: Any) -> tuple[bytes, dict[str, str]]: + return json.dumps(value, separators=(",", ":")).encode(), {"Content-Type": "application/json"} + + +# --------------------------------------------------------------------------- JWT + + +def _b64(data: bytes) -> str: + return base64.urlsafe_b64encode(data).rstrip(b"=").decode() + + +def _unb64(text: str) -> bytes: + return base64.urlsafe_b64decode(text + "=" * (-len(text) % 4)) + + +def remint(token: str, secret: str, **changes: Any) -> str: + """Re-sign a server-issued HS256 token with modified claims.""" + header_text, payload_text, _ = token.split(".") + payload = json.loads(_unb64(payload_text)) + for key, value in changes.items(): + if value is None: + payload.pop(key, None) + else: + payload[key] = value + signing_input = f"{header_text}.{_b64(json.dumps(payload, separators=(',', ':')).encode())}" + signature = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).digest() + return f"{signing_input}.{_b64(signature)}" + + +# --------------------------------------------------------------------------- targets / world + + +@dataclass +class Target: + name: str + base: str + admin_email: str + admin_password: str + jwt_secret: str + tokens: dict[str, str] = field(default_factory=dict) + ids: dict[str, str] = field(default_factory=dict) + # Two-node topology: a second node sharing storage that serves cross_node requests. + peer: str | None = None + + def login(self, email: str, password: str) -> str: + body, headers = json_body({"email": email, "password": password}) + response = send(self.base, "POST", "/platform/authorization", body=body, headers=headers) + value = response.json() + while isinstance(value, dict) and isinstance(value.get("response"), dict): + value = value["response"] + token = value.get("access_token") if isinstance(value, dict) else None + if response.status != 200 or not isinstance(token, str): + raise RuntimeError(f"{self.name}: login for {email} failed with HTTP {response.status}") + return token + + def call(self, method: str, path: str, value: Any = None, *, token: str | None = None) -> Response: + body, headers = (json_body(value) if value is not None else (None, {})) + response = send(self.base, method, path, token=token or self.tokens["admin"], body=body, headers=headers) + # A scenario (e.g. self-invalidation) may have ended the admin session; + # the pinned server reports that as 500 on routes that swallow the 401. + if response.status in (401, 500) and token is None: + self.tokens["admin"] = self.login(self.admin_email, self.admin_password) + response = send(self.base, method, path, token=self.tokens["admin"], body=body, headers=headers) + return response + + +# Both servers enforce a user limit whenever its count is set (the seeded admin +# allows 1 request/second), whatever the *_enabled flag says, so raise the counts. +UNLIMITED = {"rate_limit_enabled": False, "throttle_enabled": False, "bandwidth_limit_enabled": False, + "rate_limit_duration": 1000000, "rate_limit_duration_type": "second", + "throttle_duration": 1000000, "throttle_duration_type": "second", "throttle_queue_limit": 1000000} +FULL_ROLE = { + "role_description": "system e2e full access", + **{ + name: True + for name in ( + "manage_users", "manage_apis", "manage_endpoints", "manage_groups", "manage_roles", + "manage_routings", "manage_gateway", "manage_subscriptions", "manage_credits", + "manage_auth", "manage_security", "manage_tiers", "manage_rate_limits", + "view_analytics", "view_logs", "export_logs", "ui_access", + ) + }, +} +PRINCIPALS = { + # principal: (role, groups, active) + "sysuser": ("sysrole", ["sysgroup"], True), + "sysadmin": ("sysfull", ["ALL", "sysgroup"], True), + "sysrevoke": ("sysfull", ["ALL"], True), + "sysinval": ("sysfull", ["ALL"], True), + "sysdisabled": ("sysfull", ["ALL"], True), +} + + +FIXTURE_PROTO = ('syntax = "proto3"; package fixture.v1; service Resource { rpc Create (Request) ' + 'returns (Reply); } message Request { string name = 1; } message Reply { string message = 1; }') +# (api_name, api_type, upstream key, endpoint method, endpoint uri) +PROTOCOL_APIS = ( + ("syssoap", "SOAP", "soap", "POST", "/soap"), + ("sysgql", "GRAPHQL", "graphql", "POST", "/graphql"), + ("sysgrpc", "GRPC", "grpc", "POST", "/grpc"), +) +# Gateway operations carry protocol payloads the OpenAPI document does not model. +GATEWAY_BASELINES = { + "/api/rest/{path}": ("sysapi/v1/items", None, {}), + "/api/soap/{path}": ("syssoap/v1/soap", + b'', + {"Content-Type": "text/xml"}), + "/api/graphql/{path}": ("sysgql", b'{"query":"{ hello }"}', + {"Content-Type": "application/json", "X-API-Version": "v1"}), + "/api/grpc/{path}": ("sysgrpc", b'{"method":"Resource.Create","message":{"name":"probe"}}', + {"Content-Type": "application/json", "X-API-Version": "v1"}), +} + + +WORLD_NAMES = { + "sysapi", *(name for name, *_ in PROTOCOL_APIS), "ALL", "admin", "sysgroup", "sysfull", "sysrole", + "sysclient", *PRINCIPALS, +} + + +class World: + """Identical, idempotently restorable state on every target.""" + + def __init__(self, rest_upstream: str, upstreams: dict[str, str] | None = None): + self.rest_upstream = rest_upstream.rstrip("/") + # protocol -> upstream base (soap/graphql over http, grpc as grpc://host:port) + self.upstreams = {key: value.rstrip("/") for key, value in (upstreams or {}).items()} + + def api(self) -> dict[str, Any]: + return { + "api_name": "sysapi", "api_version": "v1", "api_description": "system e2e world", + "api_allowed_roles": ["admin", "sysfull"], "api_allowed_groups": ["ALL"], + "api_servers": [self.rest_upstream], "api_type": "REST", "api_allowed_retry_count": 0, + "active": True, + } + + def ensure(self, target: Target) -> None: + call = target.call + + def exists(path: str) -> bool: + # Look before creating: the pinned server's duplicate checks read a + # cache that a scenario may have flushed. + return call("GET", path).status == 200 + + for role, payload in ( + ("sysrole", {"role_name": "sysrole", "role_description": "system e2e no permissions"}), + ("sysfull", {"role_name": "sysfull", **FULL_ROLE}), + ): + if exists(f"/platform/role/{role}") or call("POST", "/platform/role", payload).status not in (200, 201): + call("PUT", f"/platform/role/{role}", {k: v for k, v in payload.items() if k != "role_name"}) + group = {"group_name": "sysgroup", "group_description": "system e2e", "api_access": []} + if exists("/platform/group/sysgroup") or call("POST", "/platform/group", group).status not in (200, 201): + call("PUT", "/platform/group/sysgroup", {"group_description": "system e2e"}) + for username, (role, groups, active) in PRINCIPALS.items(): + user = { + "username": username, "email": f"{username}@example.test", "password": WORLD_PASSWORD, + "role": role, "groups": groups, "active": active, "ui_access": True, + } + if exists(f"/platform/user/{username}") or call("POST", "/platform/user", user).status not in (200, 201): + call("PUT", f"/platform/user/{username}", {"role": role, "groups": groups, "active": active}) + # Per-user traffic limits would trip at slightly different moments on the + # two servers under corpus volume; the limit behaviour itself is covered + # by the higher-order and pairwise packs. + # The bootstrap admin accepts only operational limit fields (USR020). + call("PUT", "/platform/user/admin", {k: v for k, v in UNLIMITED.items() if k != "bandwidth_limit_enabled"}) + for username in PRINCIPALS: + call("PUT", f"/platform/user/{username}", UNLIMITED) + if call("POST", "/platform/api", self.api()).status not in (200, 201): + update = {k: v for k, v in self.api().items() if k not in ("api_name", "api_version")} + call("PUT", "/platform/api/sysapi/v1", update) + endpoint = { + "api_name": "sysapi", "api_version": "v1", "endpoint_method": "GET", + "endpoint_uri": "/items", "endpoint_description": "system e2e items", + } + call("POST", "/platform/endpoint", endpoint) + for name, kind, key, method, uri in PROTOCOL_APIS: + upstream = self.upstreams.get(key) + if not upstream: + continue + api = {**self.api(), "api_name": name, "api_type": kind, "api_servers": [upstream]} + if kind == "GRPC": + # No package override: the pinned server only resolves protos + # compiled under the default _ module base. + api["api_grpc_web_enabled"] = True + if call("POST", "/platform/api", api).status not in (200, 201): + call("PUT", f"/platform/api/{name}/v1", + {k: v for k, v in api.items() if k not in ("api_name", "api_version")}) + call("POST", "/platform/endpoint", { + "api_name": name, "api_version": "v1", "endpoint_method": method, + "endpoint_uri": uri, "endpoint_description": f"system e2e {key}"}) + if kind == "GRPC": + boundary = "----system-e2e-world" + data = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"fixture.proto\"\r\n" + f"Content-Type: application/octet-stream\r\n\r\n{FIXTURE_PROTO}\r\n--{boundary}--\r\n").encode() + send(target.base, "POST", f"/platform/proto/{name}/v1", token=target.tokens["admin"], body=data, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}) + for principal in ("admin", "sysadmin"): + call("POST", "/platform/subscription/subscribe", + {"username": principal, "api_name": name, "api_version": "v1"}) + found = call("GET", "/platform/endpoint/GET/sysapi/v1/items").json() + while isinstance(found, dict) and isinstance(found.get("response"), dict): + found = found["response"] + if isinstance(found, dict) and found.get("endpoint_id"): + target.ids["endpoint_id"] = str(found["endpoint_id"]) + call("POST", "/platform/subscription/subscribe", + {"username": "admin", "api_name": "sysapi", "api_version": "v1"}) + call("POST", "/platform/subscription/subscribe", + {"username": "sysadmin", "api_name": "sysapi", "api_version": "v1"}) + routing = {"client_key": "sysclient", "routing_name": "system e2e", + "routing_servers": [self.rest_upstream], "routing_description": "system e2e"} + if exists("/platform/routing/sysclient") or call("POST", "/platform/routing", routing).status not in (200, 201): + call("PUT", "/platform/routing/sysclient", {"routing_servers": [self.rest_upstream]}) + credit = {"api_credit_group": "syscredit", "api_key": "sys-credit-secret-key-0001", + "api_key_header": "x-api-key", + "credit_tiers": [{"tier_name": "basic", "credits": 100, "input_limit": 0, + "output_limit": 0, "reset_frequency": "monthly"}]} + if call("POST", "/platform/credit", credit).status not in (200, 201): + call("PUT", "/platform/credit/syscredit", credit) + vault = {"key_name": "syskey", "value": "sys-vault-secret-value-0001", "description": "system e2e"} + if call("POST", "/platform/vault", vault).status not in (200, 201): + call("PUT", "/platform/vault/syskey", {"value": vault["value"]}) + # The pinned TierName enum admits only free/pro/enterprise/custom. + tier = {"tier_id": "systier", "name": "custom", "display_name": "System", + "limits": {"requests_per_minute": 1000000000}} + if call("POST", "/platform/tiers/", tier).status not in (200, 201): + call("PUT", "/platform/tiers/systier", {"display_name": "System", "enabled": True}) + rule = {"rule_id": "sysrule", "rule_type": "per_user", "target_identifier": "sysuser", + "time_window": "minute", "limit": 1000000000, "enabled": True} + if call("POST", "/platform/rate-limits/", rule).status not in (200, 201): + call("PUT", "/platform/rate-limits/sysrule", {"limit": 1000000000, "enabled": True}) + + def refresh_tokens(self, target: Target) -> None: + target.tokens["admin"] = target.login(target.admin_email, target.admin_password) + for username in PRINCIPALS: + target.tokens[username] = self.principal_login(target, username) + + @staticmethod + def principal_login(target: Target, username: str) -> str: + """Log a principal in, restoring its password if a scenario changed it.""" + try: + return target.login(f"{username}@example.test", WORLD_PASSWORD) + except RuntimeError: + target.call("PUT", f"/platform/user/{username}/update-password", {"new_password": WORLD_PASSWORD}) + return target.login(f"{username}@example.test", WORLD_PASSWORD) + + def digest(self, target: Target) -> dict[str, Any]: + """Order-insensitive identity of the durable collections the world uses.""" + result: dict[str, Any] = {} + for name, path, key in ( + ("apis", "/platform/api/all?page_size=200", "api_name"), + ("roles", "/platform/role/all?page_size=200", "role_name"), + ("groups", "/platform/group/all?page_size=200", "group_name"), + ("users", "/platform/user/all?page_size=200", "username"), + ("routings", "/platform/routing/all?page_size=200", "client_key"), + ): + value = target.call("GET", path).json() + while isinstance(value, dict) and not isinstance(value.get(name), list) and isinstance(value.get("response"), (dict, list)): + value = value["response"] + items = value.get(name) if isinstance(value, dict) else value + # Names are compared as a set: after a cache flush the pinned server's + # cache-based duplicate checks let a repeated create insert a second + # record with the same name (a Python bug the candidate does not share). + # Only the world and scenario-owned entities are tracked: the demo + # seeder, for one, creates randomly named records on each server. + result[name] = sorted({ + str(item.get(key)) for item in items or [] + if isinstance(item, dict) and (str(item.get(key)) in WORLD_NAMES or SCENARIO_NAME.fullmatch(str(item.get(key)))) + }) + return result + + +# --------------------------------------------------------------------------- request synthesis + + +PATH_VALUES = { + "api_name": "sysapi", "api_version": "v1", "version": "v1", "username": "sysuser", + "user_id": "sysuser", "group_name": "sysgroup", "role_name": "sysrole", + "client_key": "sysclient", "api_credit_group": "syscredit", "key_name": "syskey", + "tier_id": "systier", "rule_id": "sysrule", "endpoint_method": "GET", + "endpoint_uri": "items", "email": "sysuser@example.test", "path": "sysapi/v1/items", + "service": "fixture.v1.Resource", "method": "Create", +} +FIELD_VALUES = { + "api_name": "sysapi", "api_version": "v1", "username": "sysuser", + "email": "sysuser@example.test", "role": "sysrole", "group_name": "sysgroup", + "role_name": "sysrole", "client_key": "sysclient", "api_credit_group": "syscredit", + "key_name": "syskey", "tier_id": "systier", "rule_id": "sysrule", "user_id": "sysuser", + "endpoint_method": "GET", "endpoint_uri": "/items", "groups": ["sysgroup"], + "password": WORLD_PASSWORD, "new_password": WORLD_PASSWORD + "x", "old_password": WORLD_PASSWORD, + "rule_ids": ["sysrule"], "backend": "redis", "enabled": False, "duration_ms": 0, + "origin": "https://system-e2e.invalid", "method": "GET", +} +# Collection creates get a fresh, deterministic identity per scenario so the +# success class exercises creation rather than colliding with the seeded world. +CREATE_KEYS = { + "/platform/api": ("api_name",), "/platform/user": ("username", "email"), + "/platform/group": ("group_name",), "/platform/role": ("role_name",), + "/platform/routing": ("client_key",), "/platform/credit": ("api_credit_group",), + "/platform/vault": ("key_name",), "/platform/tiers": ("tier_id", "name"), + "/platform/rate-limits": ("rule_id",), +} + + +class Synthesizer: + def __init__(self, document: dict[str, Any], world: World): + self.document = document + self.world = world + + def schema(self, value: Any) -> dict[str, Any]: + value = resolve_local_ref(self.document, value or {}) + if "allOf" in value and len(value["allOf"]) == 1: + merged = {**resolve_local_ref(self.document, value["allOf"][0]), **{k: v for k, v in value.items() if k != "allOf"}} + return merged + return value + + def value(self, name: str, schema: Any, depth: int = 0) -> Any: + schema = self.schema(schema) + if name in FIELD_VALUES: + return copy.deepcopy(FIELD_VALUES[name]) + if name == "api_servers": + return [self.world.rest_upstream] + if "example" in schema: + return copy.deepcopy(schema["example"]) + if "default" in schema: + return copy.deepcopy(schema["default"]) + if schema.get("enum"): + return schema["enum"][0] + kind = schema.get("type") + if kind == "object" or "properties" in schema: + return self.object(schema, depth + 1) if depth < 4 else {} + if kind == "array": + return [self.value(name, schema.get("items", {}), depth + 1)] if depth < 4 else [] + if kind == "integer": + return max(int(schema.get("minimum", 1)), 1) + if kind == "number": + return 1.0 + if kind == "boolean": + return True + text = "sysvalue" + return text.ljust(int(schema.get("minLength", 0)), "x")[: int(schema.get("maxLength", 64))] + + def object(self, schema: Any, depth: int = 0) -> dict[str, Any]: + schema = self.schema(schema) + required = set(schema.get("required", [])) + return { + name: self.value(name, prop, depth) + for name, prop in schema.get("properties", {}).items() + if name in required + } + + +@dataclass +class Operation: + method: str + path: str + parameters: list[dict[str, Any]] + body_schema: dict[str, Any] | None + media: str | None + + +@dataclass +class Prepared: + method: str + path: str + body: bytes | None + headers: dict[str, str] + token_key: str | None = "admin" + raw_token: str | None = None + cross_node: bool = False + prelude: list[tuple[str, str, Any]] = field(default_factory=list) + repeat: int = 1 + + +def operations(document: dict[str, Any]) -> dict[tuple[str, str], Operation]: + result: dict[tuple[str, str], Operation] = {} + for path, item in document["paths"].items(): + item = resolve_local_ref(document, item) + for method, op in item.items(): + if method not in ("get", "post", "put", "patch", "delete", "head", "options", "trace"): + continue + op = resolve_local_ref(document, op) + parameters = [ + resolve_local_ref(document, p) + for p in [*(item.get("parameters") or []), *(op.get("parameters") or [])] + ] + body_schema = media = None + request_body = resolve_local_ref(document, op.get("requestBody") or {}) + for media_type, content in (request_body.get("content") or {}).items(): + media, body_schema = media_type, content.get("schema") or {} + break + result[(method.upper(), path)] = Operation(method.upper(), path, parameters, body_schema, media) + return result + + +# --------------------------------------------------------------------------- classes + + +class Builder: + def __init__(self, synth: Synthesizer): + self.synth = synth + self.counter = 0 + + def render_path(self, op: Operation, target: Target, overrides: dict[str, str] | None = None) -> str: + path = op.path + query: list[tuple[str, str]] = [] + for parameter in op.parameters: + name = str(parameter.get("name")) + if parameter.get("in") == "path": + gateway = GATEWAY_BASELINES.get(op.path) + default = gateway[0] if gateway and name == "path" else PATH_VALUES.get(name, "sysvalue") + if op.path.startswith("/grpc-web/") and name == "api_name": + default = "sysgrpc" + value = (overrides or {}).get(name) or target.ids.get(name) or default + path = path.replace("{" + name + "}", urllib.parse.quote(value, safe="/" if name == "path" else "")) + path = path.replace("{" + name + ":path}", value) + elif parameter.get("in") == "query" and parameter.get("required"): + query.append((name, str(self.synth.value(name, parameter.get("schema", {}))))) + if op.path == "/platform/demo/seed": + # Keep the seeded world small; defaults create dozens of entities. + query += [("users", "1"), ("apis", "1"), ("endpoints", "1"), ("groups", "1"), + ("protos", "0"), ("logs", "1"), ("seed", "7")] + if query: + path += "?" + urllib.parse.urlencode(query) + return path + + def body(self, op: Operation, fresh: str | None) -> tuple[bytes | None, dict[str, str], Any]: + gateway = GATEWAY_BASELINES.get(op.path) + if gateway and op.method not in ("GET", "HEAD", "DELETE"): + return gateway[1], dict(gateway[2]), None + if gateway: + return None, {k: v for k, v in gateway[2].items() if k != "Content-Type"}, None + if op.path.startswith("/grpc-web/"): + return b"\x00\x00\x00\x00\x00", {"Content-Type": "application/grpc-web+proto"}, None + if not op.body_schema: + return None, {}, None + if op.media and op.media.startswith("multipart/"): + boundary = "----system-e2e-boundary" + proto = ('syntax = "proto3"; package fixture.v1; service Resource { rpc Create (Request) ' + 'returns (Reply); } message Request { string name = 1; } message Reply { string message = 1; }') + data = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"sys.proto\"\r\n" + f"Content-Type: application/octet-stream\r\n\r\n{proto}\r\n--{boundary}--\r\n").encode() + return data, {"Content-Type": f"multipart/form-data; boundary={boundary}"}, None + value = self.synth.object(op.body_schema) if self.synth.schema(op.body_schema).get("properties") else self.synth.value("body", op.body_schema) + if fresh and isinstance(value, dict): + for key in CREATE_KEYS.get(op.path, ()): + value[key] = f"{fresh}@example.test" if key == "email" else fresh + data, headers = json_body(value) + return data, headers, value + + def disposable(self, op: Operation) -> tuple[dict[str, str], list[tuple[str, str, Any]]]: + """Path overrides and creation prelude for a uniquely named entity that a + DELETE scenario may destroy, so the shared world is never deleted (the + pinned server keeps a deleted API cached, which would desynchronise the + two worlds for every later scenario).""" + name = f"sysd{self.counter:05d}" + rest = self.synth.world.rest_upstream + api = {"api_name": name, "api_version": "v1", "api_servers": [rest], "api_type": "REST", + "api_allowed_roles": ["admin", "sysfull"], "api_allowed_groups": ["ALL"], "active": True} + endpoint = {"api_name": name, "api_version": "v1", "endpoint_method": "GET", + "endpoint_uri": "/items", "endpoint_description": "disposable"} + kinds: list[tuple[str, dict[str, str], list[tuple[str, str, Any]]]] = [ + ("/platform/api/{api_name}/{api_version}", {"api_name": name}, + [("POST", "/platform/api", api)]), + ("/platform/endpoint/{endpoint_method}/{api_name}/{api_version}/{endpoint_uri}", + {"api_name": name}, [("POST", "/platform/api", api), ("POST", "/platform/endpoint", endpoint)]), + ("/platform/proto/{api_name}/{api_version}", {"api_name": name}, + [("POST", "/platform/api", api), ("PROTO", f"/platform/proto/{name}/v1", None)]), + ("/platform/group/{group_name}", {"group_name": name}, + [("POST", "/platform/group", {"group_name": name, "group_description": "disposable"})]), + ("/platform/role/{role_name}", {"role_name": name}, + [("POST", "/platform/role", {"role_name": name, "role_description": "disposable"})]), + ("/platform/routing/{client_key}", {"client_key": name}, + [("POST", "/platform/routing", {"client_key": name, "routing_name": "disposable", + "routing_servers": [rest], "routing_description": "d"})]), + ("/platform/user/{username}", {"username": name}, + [("POST", "/platform/user", {"username": name, "email": f"{name}@example.test", + "password": WORLD_PASSWORD, "role": "sysrole", "groups": ["sysgroup"]})]), + ("/platform/vault/{key_name}", {"key_name": name}, + [("POST", "/platform/vault", {"key_name": name, "value": "disposable-value", "description": "d"})]), + ("/platform/credit/{api_credit_group}", {"api_credit_group": name}, + [("POST", "/platform/credit", {"api_credit_group": name, "api_key": "disposable-key-0001", + "api_key_header": "x-api-key", "credit_tiers": []})]), + ("/platform/tiers/{tier_id}", {"tier_id": name}, + [("POST", "/platform/tiers/", {"tier_id": name, "name": "custom", "display_name": "D", + "limits": {"requests_per_minute": 1000000000}})]), + ("/platform/rate-limits/{rule_id}", {"rule_id": name}, + [("POST", "/platform/rate-limits/", {"rule_id": name, "rule_type": "per_user", "target_identifier": "sysuser", + "time_window": "minute", "limit": 1000000000})]), + ] + for template, overrides, prelude in kinds: + if op.path == template: + return overrides, prelude + return {}, [] + + def prepare(self, op: Operation, case: str, target: Target) -> Prepared: + fresh = f"sysn{self.counter:05d}" if op.method == "POST" and op.path in CREATE_KEYS else None + body, headers, value = self.body(op, fresh) + overrides, prelude = self.disposable(op) if op.method == "DELETE" else ({}, []) + prepared = Prepared(op.method, self.render_path(op, target, overrides), body, dict(headers)) + prepared.prelude.extend(prelude) + admin = target.tokens["sysadmin"] + if case in ("success", "memory_storage", "external_storage", "cross_node"): + # cross_node: the world was written through one node; read/act through the other. + prepared.cross_node = case == "cross_node" + return prepared + if case == "lifecycle": + prepared.repeat = 2 + return prepared + if case == "idempotent_repeat" or case == "duplicate_resource": + prepared.repeat = 2 + return prepared + if case == "anonymous": + prepared.token_key = None + elif case == "authenticated": + prepared.token_key = "sysadmin" + elif case == "expired_token": + prepared.raw_token = remint(admin, target.jwt_secret, exp=int(time.time()) - 3600, iat=int(time.time()) - 7200) + elif case == "malformed_token": + prepared.raw_token = "not.a.valid-jwt" + elif case == "wrong_issuer": + prepared.raw_token = remint(admin, target.jwt_secret, iss="https://issuer.invalid") + elif case == "wrong_audience": + prepared.raw_token = remint(admin, target.jwt_secret, aud="audience.invalid") + elif case == "revoked_token": + prepared.token_key = "sysrevoke" + prepared.prelude.append(("POST", "/platform/authorization/admin/revoke/sysrevoke", None)) + elif case == "invalidated_token": + prepared.token_key = "sysinval" + prepared.prelude.append(("SELF", "/platform/authorization/invalidate", None)) + elif case == "disabled_user": + prepared.token_key = "sysdisabled" + prepared.prelude.append(("PUT", "/platform/user/sysdisabled", {"active": False})) + elif case == "missing_permission": + prepared.token_key = "sysuser" + elif case == "wrong_group": + prepared.token_key = "sysuser" + elif case == "subscription_present": + prepared.token_key = "sysadmin" + elif case == "subscription_absent": + prepared.token_key = "sysuser" + elif case == "missing_body": + prepared.body, prepared.headers = None, {} + elif case == "malformed_body": + prepared.body, prepared.headers = b'{"unterminated": ', {"Content-Type": "application/json"} + elif case == "wrong_content_type": + prepared.headers["Content-Type"] = "text/plain" + elif case == "wrong_scalar_type": + if isinstance(value, dict) and value: + mutated = dict(value) + key = sorted(mutated)[0] + mutated[key] = {"unexpected": [1, 2]} if not isinstance(mutated[key], dict) else 12345 + prepared.body, prepared.headers = json_body(mutated) + else: + prepared.body, prepared.headers = json_body([{"unexpected": True}]) + elif case == "boundary_value": + if isinstance(value, dict): + schema = self.synth.schema(op.body_schema) + mutated = dict(value) + for key, prop in schema.get("properties", {}).items(): + prop = self.synth.schema(prop) + if key in mutated and "maxLength" in prop and isinstance(mutated[key], str): + mutated[key] = (mutated[key] + "b" * 512)[: int(prop["maxLength"]) + 1] + elif key in mutated and prop.get("type") == "integer": + mutated[key] = -1 + prepared.body, prepared.headers = json_body(mutated) + else: + prepared.path = self.render_path(op, target, {name: "b" * 300 for name in PATH_VALUES}) + elif case == "unknown_field": + if isinstance(value, dict): + prepared.body, prepared.headers = json_body({**value, "system_e2e_unknown": {"x": 1}}) + elif case == "oversized_body": + prepared.body = b'{"pad":"' + b"a" * OVERSIZED_BYTES + b'"}' + prepared.headers = {"Content-Type": "application/json"} + elif case == "missing_resource": + prepared.path = self.render_path(op, target, {name: "sysmissing" for name in PATH_VALUES}) + elif case == "conflict": + if isinstance(value, dict): + mutated = dict(value) + for key in ("api_name", "role_name", "group_name", "username", "client_key", "key_name", "tier_id", "rule_id"): + if key in mutated: + mutated[key] = {"api_name": "sysapi", "username": "sysadmin", "role_name": "sysfull"}.get(key, mutated[key]) + prepared.body, prepared.headers = json_body(mutated) + prepared.repeat = 2 + elif case == "invalid_transition": + if op.method == "DELETE": + prepared.repeat = 2 + else: + prepared.token_key = "sysdisabled" + prepared.prelude.append(("PUT", "/platform/user/sysdisabled", {"active": False})) + elif case == "unsupported_method": + prepared.method = "TRACE" if op.method != "TRACE" else "PATCH" + elif case == "incorrect_path": + prepared.path = prepared.path.split("?", 1)[0].rstrip("/") + "/system-e2e-extra/segment" + elif case == "invalid_query": + separator = "&" if "?" in prepared.path else "?" + prepared.path += separator + "page=-1&page_size=not-a-number&limit=%ZZ" + elif case == "missing_header": + if prepared.body is not None: + prepared.headers.pop("Content-Type", None) + else: + prepared.headers["Accept"] = "" + else: + raise ValueError(f"unknown operation class: {case}") + return prepared + + +# --------------------------------------------------------------------------- execution + + +# Principals whose session a class deliberately destroys. +RELOGIN = {"revoked_token": ("sysrevoke",), "invalidated_token": ("sysinval",), + "disabled_user": ("sysdisabled",), "invalid_transition": ("sysdisabled",)} + + +@dataclass +class Outcome: + scenario_id: str + passed: bool + signatures: dict[str, Any] + reason: str = "" + + +class Executor: + def __init__( + self, + candidate: Target, + oracle: Target, + world: World, + approvals: list[Approval], + log: Callable[[str], None] = print, + ): + self.candidate, self.oracle, self.world = candidate, oracle, world + _, self.document = load_openapi(OPENAPI) + self.ops = operations(self.document) + self.builder = Builder(Synthesizer(self.document, world)) + self.approvals = approvals + self.log = log + self.secrets = [ + candidate.admin_password, candidate.jwt_secret, WORLD_PASSWORD, + "sys-vault-secret-value-0001", + ] + + def setup(self) -> None: + for target in (self.candidate, self.oracle): + target.tokens["admin"] = target.login(target.admin_email, target.admin_password) + self.world.ensure(target) + self.world.refresh_tokens(target) + self.tokens_issued = time.monotonic() + + def restore(self, relogin: tuple[str, ...] = ()) -> None: + for target in (self.candidate, self.oracle): + self.world.ensure(target) + for username in relogin: + target.tokens[username] = self.world.principal_login(target, username) + + def discard_scenario_entities(self, target: Target, owned: str, digest: dict[str, list[str]]) -> None: + paths = {"apis": "/platform/api/{}/v1", "roles": "/platform/role/{}", "groups": "/platform/group/{}", + "users": "/platform/user/{}", "routings": "/platform/routing/{}"} + for collection, names in digest.items(): + for name in names: + if SCENARIO_NAME.fullmatch(name) and name.endswith(owned): + target.call("DELETE", paths[collection].format(name)) + + def perform(self, target: Target, prepared: Prepared) -> Response: + for method, path, value in prepared.prelude: + if method == "PROTO": + boundary = "----system-e2e-disposable" + data = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"d.proto\"\r\n" + f"Content-Type: application/octet-stream\r\n\r\n{FIXTURE_PROTO}\r\n--{boundary}--\r\n").encode() + send(target.base, "POST", path, token=target.tokens["admin"], body=data, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}, timeout=15) + elif method == "SELF": + send(target.base, "POST", path, token=target.tokens[prepared.token_key or "admin"]) + else: + target.call(method, path, value) + token = prepared.raw_token or (target.tokens.get(prepared.token_key) if prepared.token_key else None) + base = target.peer if prepared.cross_node and target.peer else target.base + response = Response(0, {}, b"") + for _ in range(prepared.repeat): + response = send(base, prepared.method, prepared.path, token=token, + body=prepared.body, headers=prepared.headers, timeout=15) + return response + + def redaction_errors(self, response: Response) -> list[str]: + haystack = response.body + json.dumps(response.headers).encode() + return [f"secret #{index} disclosed" for index, secret in enumerate(self.secrets) if secret and secret.encode() in haystack] + + def run(self, scenario_id: str) -> Outcome: + # Sessions expire after 30 minutes and the corpus runs longer than that. + if time.monotonic() - self.tokens_issued > 20 * 60: + for target in (self.candidate, self.oracle): + self.world.refresh_tokens(target) + self.tokens_issued = time.monotonic() + _, method, path, case = scenario_id.split("::") + op = self.ops[(method, path)] + self.builder.counter += 1 + per_target = {} + for target in (self.candidate, self.oracle): + counter = self.builder.counter + prepared = self.builder.prepare(op, case, target) + self.builder.counter = counter + per_target[target.name] = self.perform(target, prepared) + rust, python = per_target[self.candidate.name], per_target[self.oracle.name] + signatures = {"candidate": rust.signature(), "oracle": python.signature()} + reasons: list[str] = [] + if signatures["candidate"] != signatures["oracle"]: + reasons.append("signature differs from pinned oracle") + reasons += self.redaction_errors(rust) + if rust.status < 500 and rust.status != 599 and not all(h in rust.headers for h in SECURITY_HEADERS): + if (method, path) != ("GET", "/api/health") and not path.startswith("/api/"): + reasons.append("missing security headers") + mutated = (method in MUTATING or case in ("revoked_token", "invalidated_token", "disabled_user", "invalid_transition")) and ( + rust.status < 400 or python.status < 400 or case in ("revoked_token", "invalidated_token", "disabled_user", "invalid_transition") + ) + if mutated: + before = (self.world.digest(self.candidate), self.world.digest(self.oracle)) + if before[0] != before[1]: + owned = f"{self.builder.counter:05d}" + + def world_only(digest: dict[str, list[str]]) -> dict[str, list[str]]: + return {key: [name for name in names if not SCENARIO_NAME.fullmatch(name)] + for key, names in digest.items()} + + approved_signature = any(a.covers(scenario_id, signatures) for a in self.approvals) + if world_only(before[0]) != world_only(before[1]) or not approved_signature: + reasons.append("state digest differs from pinned oracle") + signatures["state"] = {"candidate": before[0], "oracle": before[1]} + # Drop what this scenario created so later scenarios (and the + # dashboard lists) see only the world. + owned = f"{self.builder.counter:05d}" + for target, digest in zip((self.candidate, self.oracle), before): + self.discard_scenario_entities(target, owned, digest) + relogin = RELOGIN.get(case, ()) + if path.startswith("/platform/authorization/admin/"): + # These routes act on the shared principal (disable/revoke). + for target in (self.candidate, self.oracle): + for action in ("enable", "unrevoke"): + target.call("POST", f"/platform/authorization/admin/{action}/sysuser") + relogin += ("sysuser",) + self.restore(relogin) + only_signature = reasons == ["signature differs from pinned oracle"] + approved = next((a for a in self.approvals if a.covers(scenario_id, signatures)), None) + if only_signature and approved: + return Outcome(scenario_id, True, signatures, f"approved: {approved.rationale}") + return Outcome(scenario_id, not reasons, signatures, "; ".join(reasons)) + + +@dataclass +class Approval: + """A reviewed divergence: `scenario_id` is an anchored regex and + `signature` pins the exact candidate/oracle fields being approved, so an + approval never silently covers a different failure on the same cell.""" + + pattern: re.Pattern[str] + signature: dict[str, dict[str, Any]] + rationale: str + + def covers(self, scenario_id: str, signatures: dict[str, Any]) -> bool: + if not self.pattern.fullmatch(scenario_id): + return False + for side, expected in self.signature.items(): + observed = signatures.get(side, {}) + for key, value in expected.items(): + # {"one_of": [...]} pins a field to a reviewed set of values. + if isinstance(value, dict) and "one_of" in value: + if observed.get(key) not in value["one_of"]: + return False + elif observed.get(key) != value: + return False + return True + + +def load_approvals() -> list[Approval]: + value = json.loads((SYSTEM / "approvals.json").read_text()) + return [ + Approval(re.compile(item["scenario_id"]), item["signature"], item["rationale"]) + for item in value.get("approvals", []) + ] + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--candidate", required=True) + parser.add_argument("--candidate-peer", help="second candidate node sharing storage (cross_node cells)") + parser.add_argument("--oracle", required=True) + parser.add_argument("--admin-email", required=True) + parser.add_argument("--admin-password", required=True) + parser.add_argument("--jwt-secret", required=True) + parser.add_argument("--rest-upstream", required=True) + parser.add_argument("--upstream", action="append", default=[], help="protocol=url, e.g. soap=http://...") + parser.add_argument("--filter", default="", help="regex over scenario ids") + parser.add_argument("--report", type=Path) + args = parser.parse_args() + from scripts.system_e2e import generated_ledger + + ledger = generated_ledger() + ids = [s["id"] for s in ledger["scenarios"] if s["executor"] == "openapi-operation" and re.search(args.filter, s["id"])] + candidate = Target("candidate", args.candidate.rstrip("/"), args.admin_email, args.admin_password, args.jwt_secret, + peer=args.candidate_peer.rstrip("/") if args.candidate_peer else None) + oracle = Target("oracle", args.oracle.rstrip("/"), args.admin_email, args.admin_password, args.jwt_secret) + executor = Executor(candidate, oracle, World(args.rest_upstream, dict(item.split("=", 1) for item in args.upstream)), load_approvals()) + executor.setup() + outcomes = [] + started = time.monotonic() + for scenario_id in ids: + outcome = executor.run(scenario_id) + outcomes.append(outcome) + if not outcome.passed: + print(f"FAIL {scenario_id}: {outcome.reason} {json.dumps(outcome.signatures, sort_keys=True)}", flush=True) + failed = sum(not o.passed for o in outcomes) + print(f"operation scenarios={len(outcomes)} passed={len(outcomes) - failed} failed={failed} elapsed={time.monotonic() - started:.1f}s") + if args.report: + args.report.write_text(json.dumps([o.__dict__ for o in outcomes], indent=1, sort_keys=True) + "\n") + return 1 if failed else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/system_packs.py b/scripts/system_packs.py new file mode 100644 index 00000000..496353ff --- /dev/null +++ b/scripts/system_packs.py @@ -0,0 +1,734 @@ +#!/usr/bin/env python3 +"""Higher-order feature packs for the system E2E corpus (`pack::`). + +Each pack is a scripted workflow that combines several features the operation +and pairwise corpora exercise one at a time. Packs that the pinned Python +server implements compare the observed sequence against the oracle; packs that +exercise candidate-only topologies (two nodes, external storage outages, +snapshot restarts) assert the documented candidate behaviour directly. A pack +passes only when every assertion holds; the report lists each failed one. +""" + +from __future__ import annotations + +import base64 +import json +import ssl +import subprocess +import sys +import threading +import time +import urllib.error +import urllib.request +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Callable + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from scripts.system_operations import ( # noqa: E402 + FIXTURE_PROTO, FULL_ROLE, UNLIMITED, WORLD_PASSWORD, Response, Target, json_body, send, +) + +SECRET_MARKERS = ("pack-vault-secret-7f3a", "pack-credit-key-91bc", "pack-bearer-marker-55d2") + + +@dataclass +class Topology: + """Everything the packs can drive. Absent entries skip nothing: a pack + whose topology is missing fails with an explicit reason.""" + + candidate: Target + oracle: Target + upstream: Callable[[str], str] # profile -> URL as seen by the gateways + control: dict[str, str] # profile -> fixture control URL from here + memory_container: str | None = None + external: Target | None = None + external_redis: str | None = None + external_mongo: str | None = None + node_a: Target | None = None + node_b: Target | None = None + native_tls: dict[str, str] | None = None + problems: list[str] = field(default_factory=list) + + +def unwrap(value: Any) -> Any: + while isinstance(value, dict) and isinstance(value.get("response"), (dict, list)): + value = value["response"] + return value + + +def fixture(top: Topology, profile: str, path: str, value: Any | None = None) -> Any: + data = json.dumps(value if value is not None else {}).encode() if path != "/__control/counters" else None + request = urllib.request.Request(top.control[profile] + path, data=data, method="POST" if data else "GET", + headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(request, timeout=10) as response: + return json.load(response) + + +def upstream_count(top: Topology, profile: str) -> int: + return sum(fixture(top, profile, "/__control/counters").get("counts", {}).values()) + + +def principal(target: Target, username: str, groups: list[str], **limits: Any) -> str: + user = {"username": username, "email": f"{username}@example.test", "password": WORLD_PASSWORD, + "role": "sysfull", "groups": groups, "active": True, "ui_access": True} + if target.call("POST", "/platform/user", user).status not in (200, 201): + target.call("PUT", f"/platform/user/{username}", {"groups": groups, "active": True}) + target.call("PUT", f"/platform/user/{username}", {**UNLIMITED, **limits}) + return target.login(f"{username}@example.test", WORLD_PASSWORD) + + +def api(target: Target, name: str, servers: list[str], endpoints: list[tuple[str, str]], **fields: Any) -> None: + body = {"api_name": name, "api_version": "v1", "api_description": "pack", "api_allowed_roles": ["sysfull", "admin"], + "api_allowed_groups": ["ALL", "sysgroup"], "api_servers": servers, "api_type": "REST", + "api_allowed_retry_count": 0, "active": True, **fields} + if target.call("POST", "/platform/api", body).status not in (200, 201): + target.call("PUT", f"/platform/api/{name}/v1", {k: v for k, v in body.items() if k not in ("api_name", "api_version")}) + for method, uri in endpoints: + target.call("POST", "/platform/endpoint", {"api_name": name, "api_version": "v1", "endpoint_method": method, + "endpoint_uri": uri, "endpoint_description": "pack"}) + + +def subscribe(target: Target, username: str, name: str) -> None: + target.call("POST", "/platform/subscription/subscribe", {"username": username, "api_name": name, "api_version": "v1"}) + + +def statuses(responses: list[Response]) -> list[int]: + return [response.status for response in responses] + + +def both(top: Topology, action: Callable[[Target], Any]) -> tuple[Any, Any]: + return action(top.candidate), action(top.oracle) + + +def expect(top: Topology, condition: bool, message: str) -> None: + if not condition: + top.problems.append(message) + + +def differential(top: Topology, label: str, action: Callable[[Target], Any]) -> tuple[Any, Any]: + candidate, oracle = both(top, action) + expect(top, candidate == oracle, f"{label}: candidate {candidate} != oracle {oracle}") + return candidate, oracle + + +# --------------------------------------------------------------------------- packs + + +def private_role_group_subscription_credits(top: Topology) -> None: + credit_body = {"username": "pack1u", "users_credits": { + "packc1": {"tier_name": "basic", "available_credits": 2}}} + + def run(target: Target) -> dict[str, Any]: + target.call("POST", "/platform/credit", { + "api_credit_group": "packc1", "api_key": "pack-credit-key-91bc", "api_key_header": "x-api-key", + "credit_tiers": [{"tier_name": "basic", "credits": 2, "input_limit": 0, "output_limit": 0, + "reset_frequency": "monthly"}]}) + api(target, "pack1", [top.upstream("rest-1")], [("GET", "/items")], api_credits_enabled=True, + api_credit_group="packc1", api_allowed_groups=["sysgroup"]) + token = principal(target, "pack1u", ["sysgroup"]) + target.call("POST", "/platform/credit/pack1u", credit_body, token=token) + subscribe(target, "pack1u", "pack1") + + def request(auth: str | None = token) -> int: + return send(target.base, "GET", "/api/rest/pack1/v1/items", token=auth).status + + def credits() -> int | None: + value = unwrap(target.call("GET", "/platform/credit/pack1u").json()) + group = value.get("users_credits", {}).get("packc1", {}) if isinstance(value, dict) else {} + return group.get("available_credits") + + result: dict[str, Any] = { + "anonymous": request(None), "invalid_token": request("not-a-valid-token"), + "credits_after_auth_denials": credits(), + "allowed": [request(), request()], + "credits_after_success": credits(), + "exhausted": request(), + } + target.call("POST", "/platform/credit/pack1u", credit_body, token=token) + target.call("POST", "/platform/subscription/unsubscribe", + {"username": "pack1u", "api_name": "pack1", "api_version": "v1"}) + result["unsubscribed"] = request() + subscribe(target, "pack1u", "pack1") + result["resubscribed"] = request() + target.call("PUT", "/platform/user/pack1u", {"groups": ["other"]}) + result["wrong_group"] = request() + target.call("PUT", "/platform/user/pack1u", {"groups": ["sysgroup"]}) + target.call("PUT", "/platform/api/pack1/v1", {"api_allowed_roles": ["pwnobody"]}) + result["wrong_role"] = request() + target.call("PUT", "/platform/api/pack1/v1", {"api_allowed_roles": ["sysfull", "admin"]}) + result["restored"] = request() + result["credits_after_denials"] = credits() + return result + + candidate, _ = differential(top, "credits/subscription/group sequence", run) + expect(top, candidate["allowed"] == [200, 200] and candidate["resubscribed"] == 200 + and candidate["restored"] == 200, f"authorized requests must succeed: {candidate}") + expect(top, all(400 <= candidate[name] < 500 for name in ( + "anonymous", "invalid_token", "exhausted", "unsubscribed", "wrong_group", "wrong_role" + )), f"auth, credit, subscription, group and role denials must be 4xx: {candidate}") + expect(top, candidate["credits_after_auth_denials"] == 2 + and candidate["credits_after_success"] == 0 + and candidate["credits_after_denials"] == 0, + f"only authorized requests may consume credits: {candidate}") + + +def optional_anonymous_ip_rate_limit(top: Topology) -> None: + def run(target: Target) -> list[int]: + api(target, "pack2", [top.upstream("rest-1")], [("GET", "/items")], api_auth_required=False, + api_ip_mode="blacklist", api_ip_blacklist=["203.0.113.9"], api_trust_x_forwarded_for=True) + token = principal(target, "pack2u", ["sysgroup"], rate_limit_enabled=True, rate_limit_duration=1, + rate_limit_duration_type="minute") + subscribe(target, "pack2u", "pack2") + optional = statuses([ + send(target.base, "GET", "/api/rest/pack2/v1/items"), + send(target.base, "GET", "/api/rest/pack2/v1/items", headers={"X-Forwarded-For": "203.0.113.9"}), + send(target.base, "GET", "/api/rest/pack2/v1/items", token=token), + send(target.base, "GET", "/api/rest/pack2/v1/items", token=token), + ]) + api(target, "pack2private", [top.upstream("rest-1")], [("GET", "/items")], + api_auth_required=True, api_ip_mode="blacklist", api_ip_blacklist=["203.0.113.9"], + api_trust_x_forwarded_for=True) + subscribe(target, "pack2u", "pack2private") + private = statuses([ + send(target.base, "GET", "/api/rest/pack2private/v1/items"), + send(target.base, "GET", "/api/rest/pack2private/v1/items", token=token), + send(target.base, "GET", "/api/rest/pack2private/v1/items", token=token), + ]) + return optional + private + + candidate, _ = differential(top, "optional auth / ip / rate sequence", run) + expect(top, candidate[0] == 200 and candidate[2] == 200, + f"anonymous and first authenticated requests must succeed: {candidate}") + expect(top, 400 <= candidate[1] < 500 and candidate[3] == 200, + f"optional auth must allow valid tokens without a user limit, while blocking the IP: {candidate}") + expect(top, 400 <= candidate[4] < 500 and candidate[5:] == [200, 429], + f"private auth must reject anonymity and enforce the user rate limit: {candidate}") + + +def public_cors_header_response_transform(top: Topology) -> None: + origin = "https://pack3.example" + + def run(target: Target) -> list[Any]: + api(target, "pack3", [top.upstream("rest-1")], [("GET", "/items")], api_public=True, + api_cors_allow_origins=[origin], api_cors_allow_methods=["GET"], api_cors_allow_headers=["X-Custom"], + api_request_transform={"request": {"headers": {"add": {"X-Pack3": "in"}}}}, + api_response_transform={"response": {"headers": {"add": {"X-Pack3-Out": "out"}}}}) + preflight = send(target.base, "OPTIONS", "/api/rest/pack3/v1/items", headers={ + "Origin": origin, "Access-Control-Request-Method": "GET", "Access-Control-Request-Headers": "X-Custom"}) + denied = send(target.base, "OPTIONS", "/api/rest/pack3/v1/items", headers={ + "Origin": "https://evil.example", "Access-Control-Request-Method": "GET"}) + fixture(top, "rest-1", "/__control/reset") + actual = send(target.base, "GET", "/api/rest/pack3/v1/items", headers={"Origin": origin}) + journal = fixture(top, "rest-1", "/__control/journal").get("journal", []) + forwarded = any(entry.get("headers", {}).get("x-pack3") == "in" for entry in journal) + return [preflight.status, preflight.headers.get("access-control-allow-origin"), + denied.headers.get("access-control-allow-origin"), actual.status, + actual.headers.get("access-control-allow-origin"), actual.headers.get("x-pack3-out"), forwarded] + + candidate, _ = differential(top, "public CORS + transforms", run) + expect(top, candidate[1] == origin and candidate[2] is None, f"CORS allow/deny wrong: {candidate}") + + +def routing_override_round_robin_retry_circuit(top: Topology) -> None: + target = top.candidate + one, two = top.upstream("rest-1"), top.upstream("rest-2") + api(target, "pack4", [one, two], [("GET", "/items")]) + subscribe(target, "admin", "pack4") + token = target.tokens["admin"] + for profile in ("rest-1", "rest-2"): + fixture(top, profile, "/__control/reset") + for _ in range(4): + send(target.base, "GET", "/api/rest/pack4/v1/items", token=token) + expect(top, upstream_count(top, "rest-1") >= 1 and upstream_count(top, "rest-2") >= 1, + "round robin did not reach both upstreams") + target.call("POST", "/platform/routing", {"client_key": "pack4r", "routing_name": "pack", "routing_servers": [two], + "routing_description": "pack"}) + for profile in ("rest-1", "rest-2"): + fixture(top, profile, "/__control/reset") + for _ in range(3): + send(target.base, "GET", "/api/rest/pack4/v1/items", token=token, headers={"client-key": "pack4r"}) + expect(top, upstream_count(top, "rest-1") == 0 and upstream_count(top, "rest-2") == 3, + "client routing override not honoured") + api(target, "pack4b", [one], [("GET", "/items")], api_allowed_retry_count=2) + subscribe(target, "admin", "pack4b") + fixture(top, "rest-1", "/__control/reset") + fixture(top, "rest-1", "/__control/faults", {"statuses": [503, 503]}) + retried = send(target.base, "GET", "/api/rest/pack4b/v1/items", token=token) + expect(top, retried.status == 200 and upstream_count(top, "rest-1") == 3, f"retry: {retried.status}") + api(target, "pack4c", [one], [("GET", "/items")]) + subscribe(target, "admin", "pack4c") + fixture(top, "rest-1", "/__control/reset") + fixture(top, "rest-1", "/__control/faults", {"statuses": [500] * 20}) + results = [send(target.base, "GET", "/api/rest/pack4c/v1/items", token=token).status for _ in range(8)] + reached = upstream_count(top, "rest-1") + expect(top, reached < 8, f"circuit never opened: {reached} upstream calls for {results}") + fixture(top, "rest-1", "/__control/reset") + + +def tier_rate_throttle_bandwidth(top: Topology) -> None: + def run(target: Target) -> list[int]: + api(target, "pack5", [top.upstream("rest-1")], [("GET", "/items")]) + token = principal(target, "pack5u", ["sysgroup"]) + subscribe(target, "pack5u", "pack5") + target.call("POST", "/platform/tiers/", {"tier_id": "pack5t", "name": "custom", "display_name": "pack5t", + "limits": {"requests_per_minute": 2}}) + target.call("POST", "/platform/tiers/assignments", {"user_id": "pack5u", "tier_id": "pack5t"}) + tier = [send(target.base, "GET", "/api/rest/pack5/v1/items", token=token) for _ in range(3)] + target.call("DELETE", "/platform/tiers/assignments/pack5u") + target.call("PUT", "/platform/user/pack5u", {"bandwidth_limit_enabled": True, "bandwidth_limit_bytes": 1, + "bandwidth_limit_window": "minute"}) + bandwidth = [send(target.base, "GET", "/api/rest/pack5/v1/items", token=token) for _ in range(2)] + return statuses(tier + bandwidth) + + candidate, _ = differential(top, "tier then bandwidth sequence", run) + expect(top, candidate[:2] == [200, 200] and candidate[2] == 429, + f"tier must allow two requests then rate-limit the third: {candidate}") + expect(top, 429 in candidate[3:], f"bandwidth limit was not enforced: {candidate}") + + +def two_node_policy_membership_revocation(top: Topology) -> None: + if not (top.node_a and top.node_b): + top.problems.append("two-node topology not provided") + return + a, b = top.node_a, top.node_b + a.tokens["admin"] = a.login(a.admin_email, a.admin_password) + a.call("POST", "/platform/group", {"group_name": "pack6g", "group_description": "pack", "api_access": []}) + api(a, "pack6", [top.upstream("rest-1")], [("GET", "/items")], api_allowed_groups=["pack6g"]) + token = principal(a, "pack6u", ["pack6g"]) + subscribe(a, "pack6u", "pack6") + time.sleep(2) + expect(top, send(b.base, "GET", "/platform/user/me", token=token).status == 200, "node B rejects a node A session") + expect(top, send(b.base, "GET", "/api/rest/pack6/v1/items", token=token).status == 200, + "node B does not see node A's API/subscription") + a.call("PUT", "/platform/user/pack6u", {"groups": ["sysgroup"]}) + deadline, refused = time.monotonic() + 15, False + while time.monotonic() < deadline and not refused: + refused = send(b.base, "GET", "/api/rest/pack6/v1/items", token=token).status in (401, 403) + time.sleep(0.5) + expect(top, refused, "group removal on node A never enforced on node B") + a.call("POST", "/platform/authorization/admin/revoke/pack6u") + deadline, revoked = time.monotonic() + 15, False + while time.monotonic() < deadline and not revoked: + revoked = send(b.base, "GET", "/platform/user/me", token=token).status == 401 + time.sleep(0.5) + expect(top, revoked, "revocation on node A never enforced on node B") + + +def openapi_import_mutation_hot_traffic(top: Topology) -> None: + target = top.candidate + api(target, "pack7", [top.upstream("rest-1")], [("GET", "/items")], api_openapi_url="/openapi.json") + subscribe(target, "admin", "pack7") + token = target.tokens["admin"] + stop, seen = threading.Event(), [] + + def traffic() -> None: + while not stop.is_set(): + seen.append(send(target.base, "GET", "/api/rest/pack7/v1/items", token=token).status) + + thread = threading.Thread(target=traffic) + thread.start() + imported = target.call("POST", "/platform/api/pack7/v1/openapi/import") + for index in range(10): + target.call("POST", "/platform/endpoint", {"api_name": "pack7", "api_version": "v1", "endpoint_method": "GET", + "endpoint_uri": f"/hot{index}", "endpoint_description": "hot"}) + target.call("DELETE", f"/platform/endpoint/GET/pack7/v1/hot{index}") + stop.set() + thread.join() + expect(top, imported.status == 200, f"openapi import failed: {imported.status} {imported.body[:200]!r}") + expect(top, seen and all(status < 500 for status in seen), f"5xx during hot mutation: {sorted(set(seen))}") + + +def soap_wssecurity_ip_cors_fault(top: Topology) -> None: + secured = (b'' + b'u' + b'') + bare = b'' + + def run(target: Target) -> list[Any]: + api(target, "pack8", [top.upstream("soap-2")], [("POST", "/soap")], api_type="SOAP", + api_ip_mode="whitelist", api_ip_whitelist=["0.0.0.0/0", "::/0"], + api_cors_allow_origins=["https://pack8.example"], api_cors_allow_methods=["POST"]) + subscribe(target, "admin", "pack8") + token = target.tokens["admin"] + media = {"Content-Type": "application/soap+xml"} + ok = send(target.base, "POST", "/api/soap/pack8/v1/soap", token=token, body=secured, headers=media) + fault = send(target.base, "POST", "/api/soap/pack8/v1/soap", token=token, body=bare, headers=media) + preflight = send(target.base, "OPTIONS", "/api/soap/pack8/v1/soap", headers={ + "Origin": "https://pack8.example", "Access-Control-Request-Method": "POST"}) + return [ok.status, fault.status, preflight.status, preflight.headers.get("access-control-allow-origin")] + + candidate, _ = differential(top, "SOAP 1.2 security/fault/CORS", run) + expect(top, candidate[0] == 200, f"secured SOAP call failed: {candidate}") + + +def graphql_depth_variables_partial_error(top: Topology) -> None: + def run(target: Target) -> list[Any]: + api(target, "pack9", [top.upstream("graphql-2")], [("POST", "/graphql")], api_type="GRAPHQL") + subscribe(target, "admin", "pack9") + token = target.tokens["admin"] + headers = {"Content-Type": "application/json", "X-API-Version": "v1"} + results = [] + for query in ({"query": "{ a { b { c { d { e { f { g } } } } } } }"}, + {"query": "query($x: String) { hello }", "variables": {"x": "value"}}, + {"query": "{ partial }"}): + body, _ = json_body(query) + response = send(target.base, "POST", "/api/graphql/pack9", token=token, body=body, headers=headers) + value = response.json() + results.append([response.status, sorted(value) if isinstance(value, dict) else None]) + return results + + differential(top, "GraphQL depth/variables/partial", run) + + +def grpc_reflection_allowlist_metadata_retry(top: Topology) -> None: + target = top.candidate + api(target, "pack10", [top.upstream("grpc-1")], [("POST", "/grpc")], api_type="GRPC", + api_grpc_allowed_services=["Resource"], api_grpc_allowed_methods=["Resource.Create"], + api_allowed_retry_count=1, api_allowed_headers=["x-pack-meta"]) + boundary = "----pack10" + data = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"f.proto\"\r\n" + f"Content-Type: application/octet-stream\r\n\r\n{FIXTURE_PROTO}\r\n--{boundary}--\r\n").encode() + send(target.base, "POST", "/platform/proto/pack10/v1", token=target.tokens["admin"], body=data, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}) + subscribe(target, "admin", "pack10") + token = target.tokens["admin"] + headers = {"Content-Type": "application/json", "X-API-Version": "v1", "x-pack-meta": "m1"} + good, _ = json_body({"method": "Resource.Create", "message": {"name": "pack"}}) + bad, _ = json_body({"method": "Resource.Delete", "message": {}}) + allowed = send(target.base, "POST", "/api/grpc/pack10", token=token, body=good, headers=headers) + denied = send(target.base, "POST", "/api/grpc/pack10", token=token, body=bad, headers=headers) + fixture(top, "grpc-1", "/__control/reset") + fixture(top, "grpc-1", "/__control/faults", {"statuses": [14]}) + retried = send(target.base, "POST", "/api/grpc/pack10", token=token, body=good, headers=headers) + fixture(top, "grpc-1", "/__control/reset") + expect(top, allowed.status == 200, f"allow-listed method failed: {allowed.status} {allowed.body[:200]!r}") + expect(top, denied.status in (400, 403, 404), f"method outside the allow-list reached upstream: {denied.status}") + expect(top, retried.status == 200, f"UNAVAILABLE was not retried: {retried.status} {retried.body[:200]!r}") + + +def grpc_web_text_cors_trailer_envelope(top: Topology) -> None: + target = top.candidate + api(target, "pack11", [top.upstream("grpc-1")], [("POST", "/grpc")], api_type="GRPC", api_grpc_web_enabled=True, + api_cors_allow_origins=["https://pack11.example"], api_cors_allow_methods=["POST"], + api_cors_allow_headers=["content-type", "x-grpc-web"]) + boundary = "----pack11" + data = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"f.proto\"\r\n" + f"Content-Type: application/octet-stream\r\n\r\n{FIXTURE_PROTO}\r\n--{boundary}--\r\n").encode() + send(target.base, "POST", "/platform/proto/pack11/v1", token=target.tokens["admin"], body=data, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}) + subscribe(target, "admin", "pack11") + frame = b"\x00\x00\x00\x00\x06\x0a\x04pack" + response = send(target.base, "POST", "/grpc-web/pack11/fixture.v1.Resource/Create", token=target.tokens["admin"], + body=base64.b64encode(frame), + headers={"Content-Type": "application/grpc-web-text", "X-API-Version": "v1", + "Origin": "https://pack11.example"}) + preflight = send(target.base, "OPTIONS", "/grpc-web/pack11/fixture.v1.Resource/Create", headers={ + "Origin": "https://pack11.example", "Access-Control-Request-Method": "POST", + "Access-Control-Request-Headers": "content-type,x-grpc-web"}) + expect(top, response.status == 200, f"grpc-web-text call failed: {response.status}") + expect(top, response.headers.get("content-type", "").startswith("application/grpc-web-text"), + f"wrong grpc-web-text media type: {response.headers.get('content-type')}") + try: + raw = base64.b64decode(response.body) + except ValueError: + raw = b"" + top.problems.append("grpc-web-text body is not base64") + offset, flags = 0, [] + while offset + 5 <= len(raw): + flags.append(raw[offset]) + offset += 5 + int.from_bytes(raw[offset + 1:offset + 5], "big") + expect(top, any(flag & 0x80 for flag in flags), f"response carries no trailer frame (flags {flags})") + expect(top, preflight.headers.get("access-control-allow-origin") == "https://pack11.example", + f"grpc-web CORS preflight not allowed: {preflight.status}") + + +def snapshot_concurrent_restart_restore(top: Topology) -> None: + if not top.memory_container: + top.problems.append("memory candidate container not provided") + return + target = top.candidate + names = [f"pack12g{index}" for index in range(20)] + threads = [threading.Thread(target=target.call, args=("POST", "/platform/group", + {"group_name": name, "group_description": "snap", "api_access": []})) + for name in names] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + dumped = target.call("POST", "/platform/memory/dump", {}) + expect(top, dumped.status == 200, f"memory dump failed: {dumped.status}") + subprocess.run(["docker", "restart", top.memory_container], check=True, capture_output=True, timeout=120) + deadline = time.monotonic() + 120 + while time.monotonic() < deadline: + if send(target.base, "GET", "/platform/monitor/liveness", timeout=2).status == 200: + break + time.sleep(1) + target.tokens["admin"] = target.login(target.admin_email, target.admin_password) + missing = [name for name in names if target.call("GET", f"/platform/group/{name}").status != 200] + expect(top, not missing, f"groups lost across restart: {missing}") + + +def redis_outage_cached_auth_recovery(top: Topology) -> None: + if not (top.external and top.external_redis): + top.problems.append("external topology not provided") + return + target = top.external + target.tokens["admin"] = target.login(target.admin_email, target.admin_password) + api(target, "pack13", [top.upstream("rest-1")], [("GET", "/items")]) + subscribe(target, "admin", "pack13") + token = target.tokens["admin"] + subprocess.run(["docker", "stop", top.external_redis], check=True, capture_output=True, timeout=60) + try: + during = [send(target.base, "GET", "/api/rest/pack13/v1/items", token=token, timeout=10).status for _ in range(3)] + expect(top, all(status in (200, 503) for status in during), + f"Redis outage must fail closed (503) or serve cached auth (200): {during}") + finally: + subprocess.run(["docker", "start", top.external_redis], check=True, capture_output=True, timeout=60) + deadline, recovered = time.monotonic() + 60, False + while time.monotonic() < deadline and not recovered: + recovered = send(target.base, "GET", "/api/rest/pack13/v1/items", token=token, timeout=10).status == 200 + time.sleep(1) + expect(top, recovered, "gateway did not recover after Redis returned") + + +def mongo_stepdown_concurrent_writes(top: Topology) -> None: + if not (top.external and top.external_mongo): + top.problems.append("external topology not provided") + return + target = top.external + target.tokens["admin"] = target.login(target.admin_email, target.admin_password) + results: dict[str, int] = {} + + def write(name: str) -> None: + results[name] = target.call("POST", "/platform/group", + {"group_name": name, "group_description": "stepdown", "api_access": []}).status + + names = [f"pack14g{index}" for index in range(30)] + threads = [threading.Thread(target=write, args=(name,)) for name in names] + for index, thread in enumerate(threads): + thread.start() + if index == 10: + subprocess.run(["docker", "restart", top.external_mongo], check=True, capture_output=True, timeout=120) + for thread in threads: + thread.join() + deadline = time.monotonic() + 90 + while time.monotonic() < deadline: + if target.call("GET", "/platform/group/all?page_size=500").status == 200: + break + time.sleep(1) + acknowledged = [name for name, status in results.items() if status in (200, 201)] + lost = [name for name in acknowledged if target.call("GET", f"/platform/group/{name}").status != 200] + crashed = [status for status in results.values() if status == 500] + expect(top, not lost, f"acknowledged writes lost across Mongo restart: {lost}") + expect(top, not crashed, f"writes during the restart answered 500 instead of a retryable status: {len(crashed)}") + + +def secret_data_logging_export_redaction(top: Topology) -> None: + target = top.candidate + target.call("POST", "/platform/vault", {"key_name": "packsecret", "value": SECRET_MARKERS[0], "description": "pack"}) + target.call("POST", "/platform/credit", {"api_credit_group": "packc15", "api_key": SECRET_MARKERS[1], + "api_key_header": "x-api-key", "credit_tiers": []}) + api(target, "pack15", [top.upstream("rest-1")], [("POST", "/items")]) + subscribe(target, "admin", "pack15") + body, headers = json_body({"password": SECRET_MARKERS[2], "token": SECRET_MARKERS[2]}) + send(target.base, "POST", "/api/rest/pack15/v1/items", token=target.tokens["admin"], body=body, + headers={**headers, "X-API-Key": SECRET_MARKERS[1], "X-Debug-Token": SECRET_MARKERS[2]}) + send(target.base, "POST", "/platform/authorization", body=json_body( + {"email": "nobody@example.test", "password": SECRET_MARKERS[2]})[0], headers=headers) + time.sleep(1) + exported = b"" + for path in ("/platform/logging/logs/export?format=json", "/platform/logging/logs/download?format=csv", + "/platform/logging/logs?limit=1000"): + response = target.call("GET", path) + expect(top, response.status == 200, f"{path} failed: {response.status}") + exported += response.body + leaked = [marker for marker in SECRET_MARKERS if marker.encode() in exported] + expect(top, not leaked, f"secrets present in exported logs: {leaked}") + + +def native_mtls_auth_rbac_credits(top: Topology) -> None: + if top.native_tls is None: + top.problems.append("native TLS candidate not provided") + return + native = top.native_tls + + def request(method: str, path: str, value: Any = None, *, token: str | None = None, + identity: str | None = None) -> Response: + body, headers = json_body(value) if value is not None else (None, {}) + if token: + headers["Authorization"] = f"Bearer {token}" + context = ssl.create_default_context(cafile=native["ca"]) + if identity: + context.load_cert_chain(native[f"{identity}_cert"], native[f"{identity}_key"]) + outgoing = urllib.request.Request(native["base"] + path, data=body, headers=headers, method=method) + try: + response = urllib.request.urlopen(outgoing, context=context, timeout=15) + except urllib.error.HTTPError as error: + response = error + with response: + return Response(response.status, {key.lower(): val for key, val in response.headers.items()}, response.read()) + + def required(method: str, path: str, value: Any, token: str | None, + statuses: tuple[int, ...] = (200, 201)) -> Response: + response = request(method, path, value, token=token) + if response.status not in statuses: + raise RuntimeError(f"native TLS setup {method} {path} returned HTTP {response.status}") + return response + + admin_login = required("POST", "/platform/authorization", { + "email": top.candidate.admin_email, "password": top.candidate.admin_password, + }, None, (200,)) + admin = unwrap(admin_login.json())["access_token"] + required("POST", "/platform/role", {"role_name": "tlsfull", **FULL_ROLE}, admin) + required("POST", "/platform/group", {"group_name": "tlsgroup", "group_description": "TLS pack", + "api_access": []}, admin) + required("POST", "/platform/user", {"username": "tlsuser", "email": "tlsuser@example.test", + "password": WORLD_PASSWORD, "role": "tlsfull", "groups": ["tlsgroup"], + "active": True, "ui_access": True}, admin) + user_login = required("POST", "/platform/authorization", { + "email": "tlsuser@example.test", "password": WORLD_PASSWORD, + }, None, (200,)) + user_token = unwrap(user_login.json())["access_token"] + required("POST", "/platform/credit", { + "api_credit_group": "tlsc", "api_key": "tls-credit-key-system-test", "api_key_header": "x-api-key", + "credit_tiers": [{"tier_name": "basic", "credits": 2, "input_limit": 0, "output_limit": 0, + "reset_frequency": "monthly"}], + }, admin) + required("POST", "/platform/api", { + "api_name": "tlspack", "api_version": "v1", "api_description": "native TLS policy pack", + "api_allowed_roles": ["tlsfull"], "api_allowed_groups": ["tlsgroup"], + "api_servers": [top.upstream("rest-1")], "api_type": "REST", "active": True, + "api_auth_required": True, "api_credits_enabled": True, "api_credit_group": "tlsc", + "api_client_tls_policy": {"mode": "required", "ca_profile_id": "system-client-ca", + "allowed_dns_sans": ["client.system.test"]}, + }, admin) + required("POST", "/platform/endpoint", {"api_name": "tlspack", "api_version": "v1", + "endpoint_method": "GET", "endpoint_uri": "/items", + "endpoint_description": "TLS pack"}, admin) + required("POST", "/platform/credit/tlsuser", {"username": "tlsuser", "users_credits": { + "tlsc": {"tier_name": "basic", "available_credits": 2}}}, admin, (200,)) + required("POST", "/platform/subscription/subscribe", { + "username": "tlsuser", "api_name": "tlspack", "api_version": "v1", + }, admin, (200,)) + + def credits() -> int | None: + value = unwrap(request("GET", "/platform/credit/tlsuser", token=admin).json()) + return value.get("users_credits", {}).get("tlsc", {}).get("available_credits") if isinstance(value, dict) else None + + def gateway(*, token: str | None = user_token, identity: str | None = "client") -> Response: + return request("GET", "/api/rest/tlspack/v1/items", token=token, identity=identity) + + fixture(top, "rest-1", "/__control/reset") + missing_cert = gateway(identity=None) + wrong_cert = gateway(identity="wrong") + missing_token = gateway(token=None) + expect(top, missing_cert.status == 401 and missing_cert.json().get("error_code") == "TLS001", + f"missing client certificate was accepted: {missing_cert.status}") + expect(top, wrong_cert.status == 403 and wrong_cert.json().get("error_code") == "TLS002", + f"wrong client SAN was accepted: {wrong_cert.status}") + expect(top, missing_token.status == 401, f"client certificate bypassed authentication: {missing_token.status}") + expect(top, credits() == 2, "certificate or authentication denials consumed credits") + expect(top, gateway().status == 200, "valid certificate and user could not reach upstream") + required("PUT", "/platform/user/tlsuser", {"groups": ["other"]}, admin, (200,)) + wrong_group = gateway() + required("PUT", "/platform/user/tlsuser", {"groups": ["tlsgroup"]}, admin, (200,)) + required("PUT", "/platform/api/tlspack/v1", {"api_allowed_roles": ["other"]}, admin, (200,)) + wrong_role = gateway() + required("PUT", "/platform/api/tlspack/v1", {"api_allowed_roles": ["tlsfull"]}, admin, (200,)) + required("POST", "/platform/subscription/unsubscribe", { + "username": "tlsuser", "api_name": "tlspack", "api_version": "v1", + }, admin, (200,)) + unsubscribed = gateway() + expect(top, all(400 <= response.status < 500 for response in (wrong_group, wrong_role, unsubscribed)), + f"RBAC or subscription bypassed under mTLS: {[r.status for r in (wrong_group, wrong_role, unsubscribed)]}") + expect(top, credits() == 1, "RBAC or subscription denials consumed credits") + required("POST", "/platform/subscription/subscribe", { + "username": "tlsuser", "api_name": "tlspack", "api_version": "v1", + }, admin, (200,)) + expect(top, gateway().status == 200, "restored mTLS user could not use final credit") + exhausted = gateway() + expect(top, 400 <= exhausted.status < 500 and credits() == 0, + f"credit exhaustion was not enforced under mTLS: {exhausted.status}") + expect(top, upstream_count(top, "rest-1") == 2, "denied mTLS requests reached the upstream") + + +PACKS: dict[str, Callable[[Topology], None]] = { + "private-role-group-subscription-credits": private_role_group_subscription_credits, + "optional-anonymous-ip-rate-limit": optional_anonymous_ip_rate_limit, + "public-cors-header-response-transform": public_cors_header_response_transform, + "routing-override-round-robin-retry-circuit": routing_override_round_robin_retry_circuit, + "tier-rate-throttle-bandwidth": tier_rate_throttle_bandwidth, + "two-node-policy-membership-revocation": two_node_policy_membership_revocation, + "openapi-import-mutation-hot-traffic": openapi_import_mutation_hot_traffic, + "soap-wssecurity-ip-cors-fault": soap_wssecurity_ip_cors_fault, + "graphql-depth-variables-partial-error": graphql_depth_variables_partial_error, + "grpc-reflection-allowlist-metadata-retry": grpc_reflection_allowlist_metadata_retry, + "grpc-web-text-cors-trailer-envelope": grpc_web_text_cors_trailer_envelope, + "snapshot-concurrent-restart-restore": snapshot_concurrent_restart_restore, + "redis-outage-cached-auth-recovery": redis_outage_cached_auth_recovery, + "mongo-stepdown-concurrent-writes": mongo_stepdown_concurrent_writes, + "secret-data-logging-export-redaction": secret_data_logging_export_redaction, + "native-mtls-auth-rbac-credits": native_mtls_auth_rbac_credits, +} + + +def prepare(top: Topology) -> None: + """Pack prerequisites on every target, independent of the other corpora: + the shared role and group, and an administrator without the seeded + one-request-per-second limit.""" + for target in (top.candidate, top.oracle, top.external, top.node_a): + if target is None: + continue + target.tokens["admin"] = target.login(target.admin_email, target.admin_password) + target.call("PUT", "/platform/user/admin", + {key: value for key, value in UNLIMITED.items() if key != "bandwidth_limit_enabled"}) + if target.call("GET", "/platform/role/sysfull").status != 200: + target.call("POST", "/platform/role", {"role_name": "sysfull", **FULL_ROLE}) + if target.call("GET", "/platform/group/sysgroup").status != 200: + target.call("POST", "/platform/group", {"group_name": "sysgroup", "group_description": "packs", + "api_access": []}) + + +def run_pack(name: str, top: Topology) -> dict[str, Any]: + top.problems = [] + try: + PACKS[name](top) + except Exception as error: # noqa: BLE001 -- a crashed pack is a failed scenario + top.problems.append(f"{type(error).__name__}: {error}") + return {"scenario_id": f"pack::{name}", "passed": not top.problems, "reason": "; ".join(top.problems)} + + +def main() -> int: + import argparse + import re + + parser = argparse.ArgumentParser(description="Run higher-order packs against a candidate and the pinned oracle.") + parser.add_argument("--candidate", required=True) + parser.add_argument("--oracle", required=True) + parser.add_argument("--admin-email", required=True) + parser.add_argument("--admin-password", required=True) + parser.add_argument("--jwt-secret", required=True) + parser.add_argument("--upstream", action="append", default=[], help="profile=url as seen by the gateways") + parser.add_argument("--control", action="append", default=[], help="profile=fixture control url") + parser.add_argument("--filter", default="") + args = parser.parse_args() + upstreams = dict(item.split("=", 1) for item in args.upstream) + top = Topology( + candidate=Target("candidate", args.candidate, args.admin_email, args.admin_password, args.jwt_secret), + oracle=Target("oracle", args.oracle, args.admin_email, args.admin_password, args.jwt_secret), + upstream=lambda profile: upstreams[profile], control=dict(item.split("=", 1) for item in args.control), + ) + prepare(top) + failed = 0 + for name in PACKS: + if args.filter and not re.search(args.filter, name): + continue + outcome = run_pack(name, top) + failed += not outcome["passed"] + print(("PASS " if outcome["passed"] else "FAIL ") + outcome["scenario_id"] + " " + outcome["reason"], flush=True) + return 1 if failed else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/system_pairwise.py b/scripts/system_pairwise.py new file mode 100644 index 00000000..40482d6e --- /dev/null +++ b/scripts/system_pairwise.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python3 +"""Differential executor for the generated pairwise policy matrix. + +Every `pairwise::NNNN` row of the system E2E ledger fixes one value on each of +the 22 policy axes in system-tests/pairwise.json. The executor turns the row +into a concrete world on both the candidate and the pinned Python oracle -- a +dedicated API, endpoint set, principal, credit group, tier, routing, upstream +fault programme and request -- sends the same request to both, and compares +the response signatures exactly like the operation executor. Differences pass +only through system-tests/approvals.json. + +Rows are executed by the topology named on their `storage` axis, so a memory +run executes the memory rows and the external / two-node runs execute theirs. + +Axis mapping (kept deliberately literal so a failing row is easy to replay): + api_mode public -> api_public; optional -> api_auth_required=false; private -> both false/true + authentication anonymous -> no token; valid/optional -> principal token; invalid -> garbage token + role / group api_allowed_roles / api_allowed_groups include or exclude the principal's + subscription principal subscribed or not + credit api_credits_enabled with a per-row credit group; exhausted = 0 credits + tier_quota per-row tier assigned to the principal; exhausted = 1 req/min after a warm-up + rate_limit user rate limit 1000/min or 1/min after a warm-up + throttle user throttle 1/s with a short wait (delay) or a queue of 1 after two warm-ups + bandwidth user bandwidth large, or 1 byte after a warm-up + ip_mode whitelist/blacklist of every address (0.0.0.0/0, ::/0) + trusted_proxy X-Forwarded-For 203.0.113.7, trusted per API or not + cors Origin header against api_cors_* settings (credentials adds allow-credentials) + routing api servers / endpoint_servers / client-key routing to the row's upstream + resilience retry count 2; circuit-open = three failing warm-ups; half-open = that plus 1.1s + transformation api_request_transform / api_response_transform header additions + compression Accept-Encoding gzip (REST gzip rows read the fixture's /gzip resource) + body_size no body / 1 KiB body / 12 MiB body + upstream fixture faults: 503, 400, 2.5s latency, disconnect, malformed body + active_state API "active": false, or the requested endpoint left unregistered +""" + +from __future__ import annotations + +import argparse +import base64 +import json +import sys +import time +import urllib.request +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Callable + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts.system_operations import ( # noqa: E402 + FIXTURE_PROTO, OVERSIZED_BYTES, UNLIMITED, WORLD_PASSWORD, Response, Target, World, + json_body, load_approvals, send, +) + +PRINCIPAL_ROLE = "sysfull" +PRINCIPAL_GROUP = "sysgroup" +ORIGIN_ALLOWED = "https://pairwise.example.test" +ORIGIN_DENIED = "https://denied.example.test" +SOAP_ENVELOPE = b'' + + +@dataclass +class Plan: + """One row made concrete for a single target.""" + + api: dict[str, Any] + endpoints: list[tuple[str, str]] + user: dict[str, Any] + subscribe: bool + credits: int | None + tier_limit: int | None + faults: dict[str, Any] + warmups: int + pause: float + method: str + path: str + headers: dict[str, str] + body: bytes | None + token: str | None = None + routing: bool = False + extra: dict[str, Any] = field(default_factory=dict) + + +def protocol_of(profile: str) -> str: + return profile.rsplit("-", 1)[0] + + +class Pairwise: + def __init__(self, candidate: Target, oracle: Target, world: World, + controls: dict[str, str], log: Callable[[str], None] = print): + self.candidate = candidate + self.oracle = oracle + self.world = world + # protocol -> fixture control base (grpc is controlled over its HTTP port) + self.controls = controls + self.approvals = load_approvals() + self.log = log + + # ----------------------------------------------------------------- planning + + def upstream(self, profile: str) -> str: + return self.world.upstreams[profile] + + def plan(self, index: int, row: dict[str, str]) -> Plan: + protocol = protocol_of(row["protocol_profile"]) + name = f"pw{index:04d}" + upstream = self.upstream(row["protocol_profile"]) + api_type = {"rest": "REST", "graphql": "GRAPHQL", "soap": "SOAP", "grpc": "GRPC", "grpc-web": "GRPC"}[protocol] + api: dict[str, Any] = { + "api_name": name, "api_version": "v1", "api_description": f"pairwise row {index}", + "api_allowed_roles": [PRINCIPAL_ROLE] if row["role_permission"] == "allowed" else ["pwnobody"], + "api_allowed_groups": [PRINCIPAL_GROUP] if row["group_eligibility"] == "allowed" else ["pwnogroup"], + "api_servers": [upstream], "api_type": api_type, + "api_allowed_retry_count": 2 if row["resilience"] == "retry" else 0, + "api_public": row["api_mode"] == "public", + "api_auth_required": row["api_mode"] == "private", + "active": row["active_state"] != "api-inactive", + } + if protocol == "grpc-web": + api["api_grpc_web_enabled"] = True + if row["credit"] != "disabled": + api.update(api_credits_enabled=True, api_credit_group=f"{name}c") + if row["ip_mode"] != "disabled": + key = "api_ip_whitelist" if row["ip_mode"] == "allow" else "api_ip_blacklist" + api.update(api_ip_mode="whitelist" if row["ip_mode"] == "allow" else "blacklist", + **{key: ["0.0.0.0/0", "::/0"]}) + if row["trusted_proxy"] == "trusted": + api["api_trust_x_forwarded_for"] = True + if row["cors"] != "none": + api.update(api_cors_allow_origins=[ORIGIN_ALLOWED], api_cors_allow_methods=["GET", "POST"], + api_cors_allow_headers=["*"], api_cors_allow_credentials=row["cors"] == "credentials") + if row["transformation"] in ("request", "both"): + api["api_request_transform"] = {"request": {"headers": {"add": {"X-Pairwise": "1"}}}} + if row["transformation"] in ("response", "both"): + api["api_response_transform"] = {"response": {"headers": {"add": {"X-Pairwise-Response": "1"}}}} + + user = dict(UNLIMITED) + if row["rate_limit"] != "disabled": + user.update(rate_limit_enabled=True, rate_limit_duration=1000 if row["rate_limit"] == "available" else 1, + rate_limit_duration_type="minute") + if row["throttle"] != "disabled": + user.update(throttle_enabled=True, throttle_duration=1, throttle_duration_type="second", + throttle_wait_duration=0.2, throttle_wait_duration_type="second", + throttle_queue_limit=10 if row["throttle"] == "delay" else 1) + if row["bandwidth"] != "disabled": + user.update(bandwidth_limit_enabled=True, bandwidth_limit_window="minute", + bandwidth_limit_bytes=10_000_000 if row["bandwidth"] == "available" else 1) + + faults: dict[str, Any] = { + "success": {}, "retryable": {"statuses": [503]}, "nonretryable": {"statuses": [400]}, + "timeout": {"latency_ms": 2500}, "disconnect": {"disconnect": True}, "malformed": {"malformed": True}, + }[row["upstream"]] + warmups = max( + 1 if row["tier_quota"] == "exhausted" else 0, + 1 if row["rate_limit"] == "limited" else 0, + 1 if row["throttle"] == "delay" else 2 if row["throttle"] == "queue-full" else 0, + 1 if row["bandwidth"] == "exhausted" else 0, + 3 if row["resilience"] in ("circuit-open", "half-open") else 0, + ) + if row["resilience"] in ("circuit-open", "half-open"): + faults = {**faults, "statuses": [500, 500, 500, *faults.get("statuses", [])]} + + method, path, headers, body, endpoints = self.request(protocol, row, name) + if row["active_state"] == "endpoint-inactive": + endpoints = [] + if row["cors"] != "none": + headers["Origin"] = ORIGIN_DENIED if row["cors"] == "denied" else ORIGIN_ALLOWED + if row["trusted_proxy"] != "off": + headers["X-Forwarded-For"] = "203.0.113.7" + if row["compression"] != "off": + headers["Accept-Encoding"] = "gzip" + if row["routing"] == "client": + headers["client-key"] = f"{name}r" + return Plan( + api=api, endpoints=endpoints, user=user, subscribe=row["subscription"] == "present", + credits=None if row["credit"] == "disabled" else (100 if row["credit"] == "positive" else 0), + tier_limit=None if row["tier_quota"] == "none" else (1000 if row["tier_quota"] == "available" else 1), + faults=faults, warmups=warmups, pause=1.1 if row["resilience"] == "half-open" else 0.0, + method=method, path=path, headers=headers, body=body, + routing=row["routing"] == "client", + extra={"endpoint_servers": row["routing"] == "endpoint", "auth": row["authentication"], + "protocol": protocol, "profile": row["protocol_profile"], "upstream": upstream}, + ) + + def request(self, protocol: str, row: dict[str, str], name: str + ) -> tuple[str, str, dict[str, str], bytes | None, list[tuple[str, str]]]: + size = row["body_size"] + + def sized(default: bytes) -> bytes | None: + if size == "empty": + return None + if size == "oversized": + return b'{"pad":"' + b"a" * OVERSIZED_BYTES + b'"}' + return default + + if protocol == "rest": + resource = "/gzip" if row["compression"] == "gzip" else "/items" + if size == "empty": + return "GET", f"/api/rest/{name}/v1{resource}", {}, None, [("GET", resource)] + padded = json.dumps({"pad": "a" * 1000}).encode() + return ("POST", f"/api/rest/{name}/v1{resource}", {"Content-Type": "application/json"}, + sized(padded), [("POST", resource)]) + if protocol == "graphql": + return ("POST", f"/api/graphql/{name}", {"Content-Type": "application/json", "X-API-Version": "v1"}, + sized(b'{"query":"{ hello }"}'), [("POST", "/graphql")]) + if protocol == "soap": + media = "application/soap+xml" if row["protocol_profile"] == "soap-2" else "text/xml" + return ("POST", f"/api/soap/{name}/v1/soap", {"Content-Type": media}, + sized(SOAP_ENVELOPE), [("POST", "/soap")]) + if protocol == "grpc": + return ("POST", f"/api/grpc/{name}", {"Content-Type": "application/json", "X-API-Version": "v1"}, + sized(b'{"method":"Resource.Create","message":{"name":"probe"}}'), [("POST", "/grpc")]) + frame = b"\x00\x00\x00\x00\x07\x0a\x05probe" + if row["protocol_profile"] == "grpc-web-2": + return ("POST", f"/grpc-web/{name}/fixture.v1.Resource/Create", + {"Content-Type": "application/grpc-web-text", "X-API-Version": "v1"}, + sized(base64.b64encode(frame)), [("POST", "/grpc")]) + return ("POST", f"/grpc-web/{name}/fixture.v1.Resource/Create", + {"Content-Type": "application/grpc-web+proto", "X-API-Version": "v1"}, + sized(frame), [("POST", "/grpc")]) + + # ----------------------------------------------------------------- world + + def principal(self, target: Target, name: str, plan: Plan) -> str: + username = f"{name}u" + call = target.call + user = {"username": username, "email": f"{username}@example.test", "password": WORLD_PASSWORD, + "role": PRINCIPAL_ROLE, "groups": [PRINCIPAL_GROUP], "active": True, "ui_access": True} + if call("POST", "/platform/user", user).status not in (200, 201): + call("PUT", f"/platform/user/{username}", {"role": PRINCIPAL_ROLE, "groups": [PRINCIPAL_GROUP]}) + call("PUT", f"/platform/user/{username}", plan.user) + return username + + def build(self, target: Target, name: str, plan: Plan) -> None: + call = target.call + username = self.principal(target, name, plan) + token = target.login(f"{username}@example.test", WORLD_PASSWORD) + if plan.credits is not None: + group = plan.api["api_credit_group"] + call("POST", "/platform/credit", { + "api_credit_group": group, "api_key": f"{group}-secret-key-0001", "api_key_header": "x-api-key", + "credit_tiers": [{"tier_name": "basic", "credits": 100, "input_limit": 0, "output_limit": 0, + "reset_frequency": "monthly"}]}) + # The pinned route credits the caller, so the principal credits itself. + call("POST", f"/platform/credit/{username}", { + "username": username, + "users_credits": {group: {"tier_name": "basic", "available_credits": plan.credits}}}, token=token) + if call("POST", "/platform/api", plan.api).status not in (200, 201): + call("PUT", f"/platform/api/{name}/v1", + {k: v for k, v in plan.api.items() if k not in ("api_name", "api_version")}) + for method, uri in plan.endpoints: + endpoint: dict[str, Any] = {"api_name": name, "api_version": "v1", "endpoint_method": method, + "endpoint_uri": uri, "endpoint_description": "pairwise"} + if plan.extra["endpoint_servers"]: + endpoint["endpoint_servers"] = [plan.extra["upstream"]] + call("POST", "/platform/endpoint", endpoint) + if plan.extra["protocol"] in ("grpc", "grpc-web"): + boundary = "----system-e2e-pairwise" + data = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"fixture.proto\"\r\n" + f"Content-Type: application/octet-stream\r\n\r\n{FIXTURE_PROTO}\r\n--{boundary}--\r\n").encode() + send(target.base, "POST", f"/platform/proto/{name}/v1", token=target.tokens["admin"], body=data, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}) + if plan.routing: + routing = {"client_key": f"{name}r", "routing_name": "pairwise", + "routing_servers": [plan.extra["upstream"]], "routing_description": "pairwise"} + call("POST", "/platform/routing", routing) + if plan.subscribe: + call("POST", "/platform/subscription/subscribe", + {"username": username, "api_name": name, "api_version": "v1"}) + if plan.tier_limit is not None: + tier = f"{name}t" + call("POST", "/platform/tiers/", {"tier_id": tier, "name": "custom", "display_name": tier, + "limits": {"requests_per_minute": plan.tier_limit}}) + call("POST", "/platform/tiers/assignments", {"user_id": username, "tier_id": tier}) + auth = plan.extra["auth"] + plan.token = None if auth == "anonymous" else ("not-a-valid-token" if auth == "invalid" else token) + + def program(self, profile: str, faults: dict[str, Any]) -> None: + control = self.controls[profile] + for path, value in (("/__control/reset", {}), ("/__control/faults", faults)): + request = urllib.request.Request(control + path, data=json.dumps(value).encode(), + headers={"Content-Type": "application/json"}, method="POST") + urllib.request.urlopen(request, timeout=5).read() + + def fire(self, target: Target, plan: Plan) -> Response: + profile = plan.extra["profile"] + # Two-node rows are configured through one node and exercised through the other. + base = target.peer or target.base + self.program(profile, plan.faults) + # Tier and user limits count per minute; keep the warm-ups and the + # measured request inside one window so a boundary cannot reset them. + if plan.warmups and time.time() % 60 > 30: + time.sleep(60.5 - time.time() % 60) + for _ in range(plan.warmups): + send(base, plan.method, plan.path, token=plan.token, body=plan.body, + headers=plan.headers, timeout=15) + if plan.pause: + time.sleep(plan.pause) + response = send(base, plan.method, plan.path, token=plan.token, body=plan.body, + headers=plan.headers, timeout=15) + self.program(profile, {}) + return response + + # ----------------------------------------------------------------- run + + def run(self, index: int, row: dict[str, str]) -> dict[str, Any]: + scenario_id = f"pairwise::{index:04d}" + responses: dict[str, Response] = {} + for target in (self.candidate, self.oracle): + plan = self.plan(index, row) + self.build(target, f"pw{index:04d}", plan) + responses[target.name] = self.fire(target, plan) + candidate, oracle = responses[self.candidate.name], responses[self.oracle.name] + signatures = {"candidate": candidate.signature(), "oracle": oracle.signature()} + for side in signatures.values(): + side.pop("allow", None) + reasons = [] + if signatures["candidate"] != signatures["oracle"]: + reasons.append("signature differs from pinned oracle") + if candidate.status == 599 or oracle.status == 599: + reasons.append("gateway request failed at the test client (HTTP 599)") + secrets = (self.candidate.admin_password, self.candidate.jwt_secret, WORLD_PASSWORD) + haystack = candidate.decoded_body() + json.dumps(candidate.headers).encode() + reasons += [f"secret #{i} disclosed" for i, secret in enumerate(secrets) if secret and secret.encode() in haystack] + approved = None + if reasons == ["signature differs from pinned oracle"]: + approved = next((a for a in self.approvals if a.covers(scenario_id, signatures)), None) + passed = not reasons or approved is not None + reason = f"approved: {approved.rationale}" if approved else "; ".join(reasons) + self.log(f"{'PASS' if passed else 'FAIL'} {scenario_id} {json.dumps(row, sort_keys=True)} " + f"{reason} {json.dumps(signatures, sort_keys=True)}") + return {"scenario_id": scenario_id, "passed": passed, "signatures": signatures, "reason": reason, + "inputs": row} + + def setup(self) -> None: + for target in (self.candidate, self.oracle): + target.tokens["admin"] = target.login(target.admin_email, target.admin_password) + self.world.ensure(target) + + +def main() -> int: + from scripts.system_e2e import generated_ledger + + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--candidate", required=True) + parser.add_argument("--oracle", required=True) + parser.add_argument("--admin-email", required=True) + parser.add_argument("--admin-password", required=True) + parser.add_argument("--jwt-secret", required=True) + parser.add_argument("--rest-upstream", required=True) + parser.add_argument("--upstream", action="append", default=[], help="profile=url") + parser.add_argument("--control", action="append", default=[], help="profile=fixture control url") + parser.add_argument("--storage", default="memory") + parser.add_argument("--filter", default="") + parser.add_argument("--report") + args = parser.parse_args() + upstreams = dict(item.split("=", 1) for item in args.upstream) + upstreams.setdefault("rest-1", args.rest_upstream) + controls = dict(item.split("=", 1) for item in args.control) + profiles = {row["protocol_profile"] for row in generated_ledger()["pairwise_rows"]} + if missing := sorted(profiles - upstreams.keys()): + parser.error(f"missing upstream profiles: {', '.join(missing)}") + if missing := sorted(profiles - controls.keys()): + parser.error(f"missing fixture controls: {', '.join(missing)}") + for protocol, profile in (("soap", "soap-1"), ("graphql", "graphql-1"), ("grpc", "grpc-1")): + upstreams.setdefault(protocol, upstreams[profile]) + runner = Pairwise( + Target("candidate", args.candidate, args.admin_email, args.admin_password, args.jwt_secret), + Target("oracle", args.oracle, args.admin_email, args.admin_password, args.jwt_secret), + World(args.rest_upstream, upstreams), controls, + ) + runner.setup() + outcomes = [] + for index, row in enumerate(generated_ledger()["pairwise_rows"], 1): + if row["storage"] != args.storage or (args.filter and args.filter not in f"pairwise::{index:04d}"): + continue + outcomes.append(runner.run(index, row)) + failed = [item for item in outcomes if not item["passed"]] + if args.report: + Path(args.report).write_text(json.dumps(outcomes, indent=1) + "\n") + print(f"pairwise scenarios={len(outcomes)} passed={len(outcomes) - len(failed)} failed={len(failed)}") + return 1 if failed else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/system_settings.py b/scripts/system_settings.py new file mode 100644 index 00000000..9d50e5b3 --- /dev/null +++ b/scripts/system_settings.py @@ -0,0 +1,544 @@ +#!/usr/bin/env python3 +"""Runtime-setting executor for the comprehensive system E2E corpus. + +Each `setting::::` scenario starts the candidate with +that one setting applied and runs the setting's probe. For settings the pinned +Python server also reads, the oracle is started with the identical environment +and the two observations (startup outcome plus probe result) must be equal. +Settings only the candidate reads are compared with the documented expectation +recorded in SPEC. +""" + +from __future__ import annotations + +import argparse +import base64 +import hashlib +import hmac +import json +import os +import shutil +import signal +import socket +import re +import subprocess +import sys +import tempfile +import time +import urllib.parse +import urllib.request +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Callable, Protocol + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from scripts.system_operations import FIXTURE_PROTO, UNLIMITED, Response, json_body, load_approvals, send # noqa: E402 + +ORIGIN = "https://system-e2e.invalid" +ADMIN_EMAIL = "settings-admin@example.test" +ADMIN_PASSWORD = "SettingsAdmin!Pass2026" +JWT_SECRET = "settings-jwt-secret-key-0123456789abcdef" +BASE_ENV = { + "ENV": "development", "HOST": "127.0.0.1", "THREADS": "1", "MEM_OR_EXTERNAL": "MEM", + "MEM_AUTO_SAVE_ENABLED": "false", "MEM_ENCRYPTION_KEY": "settings-mem-encryption-key-0123456789", + "DOORMAN_ADMIN_EMAIL": ADMIN_EMAIL, "DOORMAN_ADMIN_PASSWORD": ADMIN_PASSWORD, + "JWT_SECRET_KEY": JWT_SECRET, "CORS_STRICT": "true", "ALLOWED_ORIGINS": ORIGIN, + "ALLOW_HEADERS": "*", "ALLOW_METHODS": "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS", + "LOCAL_HOST_IP_BYPASS": "false", "HTTPS_ONLY": "false", "DEMO_SEED": "false", + "LOGIN_IP_RATE_LIMIT": "1000000", "LOGIN_ACCOUNT_RATE_LIMIT": "1000000", + "DISCOVERY_ALLOWED_HOSTS": "127.0.0.1,localhost", +} +# Settings only the candidate reads (no pinned oracle behaviour to compare). +RUST_ONLY = {"DISCOVERY_ALLOWED_HOSTS", "JWT_AUDIENCE", "JWT_ISSUER", "METRICS_SAVE_INTERVAL"} + + +@dataclass +class Instance: + base: str + workdir: Path + handle: Any = None + + +class Launcher(Protocol): + def start(self, implementation: str, env: dict[str, str]) -> Instance | None: ... + def stop(self, instance: Instance) -> None: ... + + +@dataclass +class Context: + """Upstream fixture addresses: `rest_upstream` as seen by the gateways and + `rest_control` as reachable from this process (they differ under Docker).""" + + rest_upstream: str + rest_control: str + + +# --------------------------------------------------------------------------- helpers + + +def _login(i: Instance) -> Response: + body, headers = json_body({"email": ADMIN_EMAIL, "password": ADMIN_PASSWORD}) + return send(i.base, "POST", "/platform/authorization", body=body, headers=headers) + + +def _token(i: Instance) -> str: + value = _login(i).json() or {} + while isinstance(value, dict) and isinstance(value.get("response"), dict): + value = value["response"] + return value.get("access_token", "") if isinstance(value, dict) else "" + + +def _call(i: Instance, method: str, path: str, value: Any = None, token: str | None = None) -> Response: + body, headers = json_body(value) if value is not None else (None, {}) + return send(i.base, method, path, token=token or _token(i), body=body, headers=headers, timeout=20) + + +def _claims(token: str) -> dict[str, Any]: + try: + payload = token.split(".")[1] + return json.loads(base64.urlsafe_b64decode(payload + "=" * (-len(payload) % 4))) + except (IndexError, ValueError): + return {} + + +def _preflight(i: Instance, origin: str = ORIGIN, method: str = "PUT", headers: str = "Content-Type") -> dict[str, Any]: + response = send(i.base, "OPTIONS", "/platform/api/all", headers={ + "Origin": origin, "Access-Control-Request-Method": method, + "Access-Control-Request-Headers": headers}) + keep = ("access-control-allow-origin", "access-control-allow-credentials") + return {"status": response.status, **{k: response.headers.get(k) for k in keep}} + + +def _cookie_attributes(response: Response) -> dict[str, Any]: + # urllib folds repeated Set-Cookie headers into one comma-joined value. + raw = response.headers.get("set-cookie", "") + cookies: dict[str, Any] = {} + for part in raw.split(", "): + if "=" not in part.split(";")[0]: + continue + name = part.split("=", 1)[0].strip() + attrs = {} + for attr in part.split(";")[1:]: + key, _, value = attr.strip().partition("=") + key = key.lower() + if key in ("samesite", "domain", "path"): + attrs[key] = value.lower() + elif key in ("secure", "httponly"): + attrs[key] = True + elif key == "max-age": + attrs[key] = round(int(value) / 60) if value.lstrip("-").isdigit() else value + cookies[name] = attrs + return cookies + + +def _ttl_minutes(token: str) -> int | None: + exp = _claims(token).get("exp") + return round((exp - time.time()) / 60) if isinstance(exp, (int, float)) else None + + +def _fixture(ctx: Context, faults: dict[str, Any] | None = None) -> None: + urllib.request.urlopen(urllib.request.Request(ctx.rest_control + "/__control/reset", data=b"{}", method="POST"), timeout=5).read() + if faults: + urllib.request.urlopen(urllib.request.Request( + ctx.rest_control + "/__control/faults", data=json.dumps(faults).encode(), method="POST"), timeout=5).read() + + +def _fixture_count(ctx: Context) -> int: + with urllib.request.urlopen(ctx.rest_control + "/__control/counters", timeout=5) as response: + return sum(json.load(response).get("counts", {}).values()) + + +def _gateway(i: Instance, ctx: Context, *, faults: dict[str, Any] | None = None, calls: int = 1, + servers: list[str] | None = None, retry: int = 0, method: str = "GET", body: bytes | None = None, + kind: str = "REST", subscribe: bool = True) -> dict[str, Any]: + token = _token(i) + api = {"api_name": "setapi", "api_version": "v1", "api_servers": servers or [ctx.rest_upstream], + "api_type": kind, "api_allowed_roles": ["admin"], "api_allowed_groups": ["ALL"], + "api_allowed_retry_count": retry, "active": True} + # The seeded admin allows one request per second; lift it so the probe + # observes the setting, not the user rate limit. + _call(i, "PUT", "/platform/user/admin", {k: v for k, v in UNLIMITED.items() if k != "bandwidth_limit_enabled"}, token) + _call(i, "POST", "/platform/api", api, token) + _call(i, "POST", "/platform/endpoint", {"api_name": "setapi", "api_version": "v1", "endpoint_method": method, + "endpoint_uri": "/items", "endpoint_description": "settings probe"}, token) + if subscribe: + _call(i, "POST", "/platform/subscription/subscribe", + {"username": "admin", "api_name": "setapi", "api_version": "v1"}, token) + _fixture(ctx, faults) + statuses = [] + for _ in range(calls): + headers = {"Content-Type": "application/json"} if body is not None else {} + statuses.append(send(i.base, method, "/api/rest/setapi/v1/items", token=token, body=body, + headers=headers, timeout=20).status) + return {"statuses": statuses, "upstream_requests": _fixture_count(ctx)} + + +def _proto_upload(i: Instance, size: int) -> int: + token = _token(i) + _call(i, "POST", "/platform/api", {"api_name": "setproto", "api_version": "v1", "api_type": "GRPC", + "api_servers": ["grpc://127.0.0.1:9"], "api_allowed_roles": ["admin"], + "api_allowed_groups": ["ALL"]}, token) + proto = FIXTURE_PROTO + "\n//" + "x" * max(0, size - len(FIXTURE_PROTO)) + boundary = "----settings-proto" + data = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"s.proto\"\r\n" + f"Content-Type: application/octet-stream\r\n\r\n{proto}\r\n--{boundary}--\r\n").encode() + return send(i.base, "POST", "/platform/proto/setproto/v1", token=token, body=data, + headers={"Content-Type": f"multipart/form-data; boundary={boundary}"}, timeout=20).status + + +def _localhost_bypass(i: Instance) -> int: + token = _token(i) + _call(i, "PUT", "/platform/security/settings", {"ip_whitelist": ["203.0.113.10"]}, token) + return send(i.base, "GET", "/platform/user/me", token=token).status + + +def _autosave(i: Instance) -> bool: + deadline = time.monotonic() + 4 + while time.monotonic() < deadline: + if any(path.suffix == ".bin" for path in i.workdir.rglob("*")): + return True + time.sleep(0.25) + return False + + +def _token_without(i: Instance, claim: str) -> int: + """Present a correctly signed admin token that lacks one claim.""" + token = _token(i) + claims = {key: value for key, value in _claims(token).items() if key != claim} + header = json.loads(base64.urlsafe_b64decode(token.split(".")[0] + "===")) + encode = lambda value: base64.urlsafe_b64encode(json.dumps(value, separators=(",", ":")).encode()).rstrip(b"=") + signing_input = encode(header) + b"." + encode(claims) + signature = base64.urlsafe_b64encode(hmac.new(JWT_SECRET.encode(), signing_input, hashlib.sha256).digest()).rstrip(b"=") + return send(i.base, "GET", "/platform/user/me", token=(signing_input + b"." + signature).decode()).status + + +def _discovery(i: Instance, ctx: Context) -> int: + """Fetch a server-relative WSDL from the local fixture (subject to the discovery allow-list).""" + _fixture(ctx) + token = _token(i) + _call(i, "POST", "/platform/api", { + "api_name": "setdisc", "api_version": "v1", "api_description": "settings discovery probe", + "api_allowed_roles": ["admin"], "api_allowed_groups": ["ALL"], "api_servers": [ctx.rest_upstream], + "api_type": "REST", "api_allowed_retry_count": 0, "api_wsdl_url": "/wsdl"}, token) + return send(i.base, "GET", "/platform/api/setdisc/v1/wsdl", token=token).status + + +def _metrics_saved(i: Instance) -> bool: + deadline = time.monotonic() + 4 + while time.monotonic() < deadline: + if any(path.name == "metrics.json" for path in i.workdir.rglob("*")): + return True + time.sleep(0.25) + return False + + +PROBES: dict[str, Callable[[Instance, Context], Any]] = { + "preflight": lambda i, c: _preflight(i), + "preflight_foreign": lambda i, c: _preflight(i, origin="https://foreign.invalid"), + "preflight_header": lambda i, c: _preflight(i, headers="X-Custom-Probe"), + "token_ttl": lambda i, c: _ttl_minutes(_token(i)), + "login_cookies": lambda i, c: _cookie_attributes(_login(i)), + "login": lambda i, c: _login(i).status, + "authed": lambda i, c: send(i.base, "GET", "/platform/user/me", token=_token(i)).status, + "large_auth": lambda i, c: send(i.base, "POST", "/platform/authorization", body=b'{"pad":"' + b"a" * 4096 + b'"}', + headers={"Content-Type": "application/json"}).status, + "large_api": lambda i, c: send(i.base, "POST", "/platform/api", token=_token(i), + body=b'{"pad":"' + b"a" * 4096 + b'"}', headers={"Content-Type": "application/json"}).status, + "page_size": lambda i, c: send(i.base, "GET", "/platform/role/all?page_size=5", token=_token(i)).status, + "options": lambda i, c: send(i.base, "OPTIONS", "/platform/api/all").status, + "memory_dump": lambda i, c: send(i.base, "POST", "/platform/memory/dump", token=_token(i), body=b"{}", + headers={"Content-Type": "application/json"}).status, + "vault": lambda i, c: _call(i, "POST", "/platform/vault", {"key_name": "settingprobe", "value": "v", + "description": "d"}).status, + "breaker": lambda i, c: _gateway(i, c, faults={"statuses": [500] * 6}, calls=6), + "slow_upstream": lambda i, c: _gateway(i, c, faults={"latency_ms": 2500}), + "unroutable": lambda i, c: _gateway(i, c, servers=["http://10.255.255.1:81"])["statuses"], + "retry": lambda i, c: _gateway(i, c, faults={"statuses": [503, 503]}, retry=2), + "rest_body": lambda i, c: _gateway(i, c, method="POST", body=b'{"pad":"' + b"a" * 1024 + b'"}')["statuses"], + "unsubscribed": lambda i, c: _gateway(i, c, subscribe=False)["statuses"], + "proto_size": lambda i, c: _proto_upload(i, 400), + "localhost_bypass": _localhost_bypass, + "autosave": lambda i, c: _autosave(i), + "openapi_discovery": lambda i, c: _call(i, "POST", "/platform/openapi/parse", + {"url": c.rest_upstream + "/openapi.json"}).status, + "discovery": _discovery, + "no_aud": lambda i, c: _token_without(i, "aud"), + "no_iss": lambda i, c: _token_without(i, "iss"), + "metrics_saved": lambda i, c: _metrics_saved(i), + "health": lambda i, c: send(i.base, "GET", "/platform/monitor/liveness").status, +} + + +@dataclass +class Setting: + name: str + positive: str + invalid: str + probe: str + extra: dict[str, str] = field(default_factory=dict) + # Documented candidate observations for settings the oracle does not read. + expect: dict[str, Any] | None = None + + +KEYSET = json.dumps({"keys": [{"kid": "settings-k1", "algorithm": "HS256", "secret": JWT_SECRET + "-k1", "active": True}]}) +SPEC: list[Setting] = [ + Setting("ALLOWED_ORIGINS", "https://system-e2e.invalid,https://other.invalid", "::not-a-url::", "preflight"), + Setting("ALLOW_CREDENTIALS", "false", "sometimes", "preflight"), + Setting("ALLOW_HEADERS", "Content-Type,X-Custom-Probe", "", "preflight_header"), + Setting("ALLOW_METHODS", "GET,POST", "NOT A METHOD", "preflight"), + Setting("AUTH_EXPIRE_TIME", "5", "-3", "token_ttl"), + Setting("AUTH_EXPIRE_TIME_FREQ", "hours", "fortnights", "token_ttl"), + Setting("AUTH_REFRESH_EXPIRE_TIME", "2", "abc", "login_cookies"), + Setting("AUTH_REFRESH_EXPIRE_FREQ", "hours", "bogus", "login_cookies"), + Setting("BODY_LIMIT_EXCLUDE_PATHS", "/platform/authorization", ",,,", "large_auth", {"MAX_BODY_SIZE_BYTES": "1024"}), + Setting("CIRCUIT_BREAKER_ENABLED", "false", "maybe", "breaker", {"CIRCUIT_BREAKER_THRESHOLD": "2"}), + Setting("CIRCUIT_BREAKER_THRESHOLD", "2", "-1", "breaker"), + Setting("CIRCUIT_BREAKER_TIMEOUT", "1", "x", "breaker", {"CIRCUIT_BREAKER_THRESHOLD": "2"}), + Setting("COOKIE_DOMAIN", "example.test", "", "login_cookies"), + Setting("COOKIE_SAMESITE", "Lax", "Sideways", "login_cookies"), + Setting("COOKIE_SECURE", "true", "perhaps", "login_cookies"), + Setting("CORS_STRICT", "false", "maybe", "preflight_foreign"), + Setting("DISABLE_BODY_SIZE_LIMIT", "true", "maybe", "large_api", {"MAX_BODY_SIZE_BYTES": "1024"}), + Setting("DISCOVERY_ALLOWED_HOSTS", "{upstream_host}", "", "discovery", + expect={"positive": {"started": True, "probe": 200}, "invalid": {"started": True, "probe": 400}}), + Setting("DOORMAN_ENABLE_GRPC_REFLECTION", "true", "maybe", "health"), + Setting("ENFORCE_ADMIN_SUBSCRIPTION", "true", "maybe", "unsubscribed"), + Setting("GATEWAY_TIMEOUT", "1", "abc", "slow_upstream"), + Setting("GRPC_RETRY_BASE_MS", "5", "x", "health"), + Setting("GRPC_RETRY_MAX_MS", "20", "x", "health"), + Setting("HTTP_CONNECT_TIMEOUT", "0.5", "fast", "unroutable"), + Setting("HTTP_READ_TIMEOUT", "1", "x", "slow_upstream"), + Setting("HTTP_RETRY_BASE_DELAY", "0.01", "x", "retry"), + Setting("HTTP_RETRY_MAX_DELAY", "0.05", "x", "retry"), + Setting("JWT_AUDIENCE", "settings-audience", "", "no_aud", + expect={"positive": {"started": True, "probe": 401}, "invalid": {"started": True, "probe": 401}}), + Setting("JWT_ISSUER", "settings-issuer", "", "no_iss", + expect={"positive": {"started": True, "probe": 401}, "invalid": {"started": True, "probe": 401}}), + Setting("JWT_KEYS", KEYSET, "{not json", "authed"), + Setting("JWT_SECRET_KEY", JWT_SECRET + "-alt", "", "authed"), + Setting("LOCAL_HOST_IP_BYPASS", "true", "maybe", "localhost_bypass"), + Setting("MAX_BODY_SIZE_BYTES", "2048", "-5", "large_api"), + Setting("MAX_BODY_SIZE_BYTES_GRAPHQL", "512", "-5", "rest_body"), + Setting("MAX_BODY_SIZE_BYTES_GRPC", "512", "-5", "rest_body"), + Setting("MAX_BODY_SIZE_BYTES_REST", "512", "-5", "rest_body"), + Setting("MAX_BODY_SIZE_BYTES_SOAP", "512", "-5", "rest_body"), + Setting("MAX_PAGE_SIZE", "1", "zero", "page_size"), + Setting("MAX_PROTO_SIZE_BYTES", "100", "x", "proto_size"), + Setting("MEM_AUTO_SAVE_ENABLED", "true", "maybe", "autosave", {"MEM_AUTO_SAVE_FREQ": "1"}), + Setting("MEM_AUTO_SAVE_FREQ", "1", "x", "autosave", {"MEM_AUTO_SAVE_ENABLED": "true"}), + Setting("MEM_DUMP_PATH", "{workdir}/custom-dump.bin", "/proc/forbidden/dump.bin", "memory_dump"), + Setting("MEM_ENCRYPTION_KEY", "settings-probe-key-0123456789abcd", "short", "memory_dump"), + Setting("MEM_OR_EXTERNAL", "MEM", "BOGUS", "login"), + Setting("METRICS_SAVE_INTERVAL", "1", "x", "metrics_saved", + expect={"positive": {"started": True, "probe": True}, "invalid": {"started": True, "probe": False}}), + Setting("RETRY_BACKOFF", "0.01", "x", "retry"), + Setting("RETRY_ENABLED", "false", "maybe", "retry"), + Setting("RETRY_MAX_ATTEMPTS", "1", "-2", "retry"), + Setting("STRICT_OPTIONS_405", "true", "maybe", "options"), + Setting("THREADS", "1", "4", "login"), + Setting("VAULT_KEY", "settings-vault-key-0123456789abcdef", "", "vault"), +] + + +@dataclass +class SettingOutcome: + scenario_id: str + passed: bool + observation: dict[str, Any] + reason: str = "" + + +def observe(launcher: Launcher, ctx: Context, implementation: str, env: dict[str, str], probe: str) -> dict[str, Any]: + instance = launcher.start(implementation, env) + if instance is None: + return {"started": False} + try: + return {"started": True, "probe": PROBES[probe](instance, ctx)} + except Exception as error: # noqa: BLE001 -- a probe failure is an observation + return {"started": True, "probe_error": type(error).__name__} + finally: + launcher.stop(instance) + + +def run_setting(launcher: Launcher, ctx: Context, setting: Setting, kind: str) -> SettingOutcome: + scenario_id = f"setting::{setting.name}::{kind}" + host = urllib.parse.urlsplit(ctx.rest_upstream).hostname or "" + env = {key: value.replace("{upstream_host}", host) for key, value in + {**setting.extra, setting.name: setting.positive if kind == "positive" else setting.invalid}.items()} + candidate = observe(launcher, ctx, "rust", env, setting.probe) + if setting.name in RUST_ONLY: + expected = (setting.expect or {}).get(kind) + passed = expected is not None and candidate == expected + return SettingOutcome(scenario_id, passed, {"candidate": candidate, "expected": expected}, + "" if passed else "candidate does not match the documented expectation") + oracle = observe(launcher, ctx, "python", env, setting.probe) + observation = {"candidate": candidate, "oracle": oracle} + if candidate == oracle: + return SettingOutcome(scenario_id, True, observation) + approved = next((a for a in load_approvals() if a.covers(scenario_id, observation)), None) + if approved: + return SettingOutcome(scenario_id, True, observation, f"approved: {approved.rationale}") + return SettingOutcome(scenario_id, False, observation, "setting behaviour differs from pinned oracle") + + +# --------------------------------------------------------------------------- native launcher + + +def free_port() -> int: + with socket.socket() as listener: + listener.bind(("127.0.0.1", 0)) + return int(listener.getsockname()[1]) + + +class NativeLauncher: + """Starts the Rust binary and the pinned Python checkout as local processes.""" + + def __init__(self, rust_binary: Path, python_dir: Path, python: Path): + self.rust_binary, self.python_dir, self.python = rust_binary, python_dir, python + + def start(self, implementation: str, env: dict[str, str]) -> Instance | None: + workdir = Path(tempfile.mkdtemp(prefix=f"doorman-setting-{implementation}-")) + port = free_port() + values = {**BASE_ENV, "PORT": str(port), "MEM_DUMP_PATH": str(workdir / "dump.bin"), + "SECURITY_SETTINGS_FILE": str(workdir / "security_settings.json"), "LOGS_DIR": str(workdir / "logs")} + values.update({key: value.replace("{workdir}", str(workdir)) for key, value in env.items()}) + environment = {key: value for key, value in os.environ.items() if key not in values} + environment.update(values) + if implementation == "python": + command, cwd = [str(self.python), "doorman.py", "run"], self.python_dir + else: + command, cwd = [str(self.rust_binary)], workdir + process = subprocess.Popen(command, cwd=cwd, env=environment, stdout=(workdir / "out.log").open("w"), + stderr=subprocess.STDOUT, start_new_session=True) + base = f"http://127.0.0.1:{port}" + deadline = time.monotonic() + 30 + while time.monotonic() < deadline: + if process.poll() is not None: + shutil.rmtree(workdir, ignore_errors=True) + return None + if send(base, "GET", "/platform/monitor/liveness", timeout=2).status == 200: + return Instance(base, workdir, process) + time.sleep(0.3) + self.stop(Instance(base, workdir, process)) + return None + + def stop(self, instance: Instance) -> None: + process: subprocess.Popen[bytes] = instance.handle + if process.poll() is None: + os.killpg(process.pid, signal.SIGTERM) + try: + process.wait(timeout=15) + except subprocess.TimeoutExpired: + os.killpg(process.pid, signal.SIGKILL) + process.wait() + shutil.rmtree(instance.workdir, ignore_errors=True) + + +class DockerLauncher: + """Starts the candidate and pinned oracle images on the system E2E network. + + Each instance gets a host work directory mounted at /work for dumps, logs + and security settings, so file-based probes read the same paths as the + native launcher. + """ + + PORTS = {"rust": 3001, "python": 8000} + + def __init__(self, images: dict[str, str], network: str, labels: dict[str, str], prefix: str): + self.images, self.network, self.labels, self.prefix = images, network, labels, prefix + self.counter = 0 + + def start(self, implementation: str, env: dict[str, str]) -> Instance | None: + self.counter += 1 + workdir = Path(tempfile.mkdtemp(prefix=f"doorman-setting-{implementation}-")) + workdir.chmod(0o777) + port = free_port() + name = f"{self.prefix}-setting-{implementation}-{self.counter}" + values = {**BASE_ENV, "HOST": "0.0.0.0", "PORT": str(self.PORTS[implementation]), + "WEB_HOST": "0.0.0.0", "WEB_PORT": "3000", "MEM_DUMP_PATH": "/work/dump.bin", + "SECURITY_SETTINGS_FILE": "/work/security_settings.json", "LOGS_DIR": "/work/logs"} + values.update({key: value.replace("{workdir}", "/work") for key, value in env.items()}) + env_file = Path(tempfile.mkdtemp(prefix="doorman-setting-env-")) / "system.env" + env_file.write_text("".join(f"{key}={value}\n" for key, value in values.items())) + env_file.chmod(0o644) + command = ["docker", "run", "--detach", "--name", name, "--network", self.network, + "--publish", f"127.0.0.1:{port}:{self.PORTS[implementation]}", + "--mount", f"type=bind,src={workdir},dst=/work"] + for key, value in self.labels.items(): + command += ["--label", f"{key}={value}"] + if implementation == "rust": + command += ["--mount", f"type=bind,src={env_file},dst=/env/system.env,readonly"] + else: + command += ["--env-file", str(env_file)] + started = subprocess.run([*command, self.images[implementation]], capture_output=True, text=True) + if implementation == "python": + # --env-file is read at creation; the bind-mounted candidate file must stay. + shutil.rmtree(env_file.parent, ignore_errors=True) + instance = Instance(f"http://127.0.0.1:{port}", workdir, (name, env_file)) + if started.returncode != 0: + self.stop(instance) + return None + deadline = time.monotonic() + 90 + while time.monotonic() < deadline: + state = subprocess.run(["docker", "inspect", "-f", "{{.State.Running}}", name], + capture_output=True, text=True).stdout.strip() + if state != "true": + self.stop(instance) + return None + if send(instance.base, "GET", "/platform/monitor/liveness", timeout=2).status == 200: + return instance + time.sleep(0.5) + self.stop(instance) + return None + + def stop(self, instance: Instance) -> None: + name, env_file = instance.handle + subprocess.run(["docker", "rm", "--force", name], capture_output=True) + shutil.rmtree(env_file.parent, ignore_errors=True) + # Files written by the container user may not be removable here. + subprocess.run(["docker", "run", "--rm", "--mount", f"type=bind,src={instance.workdir},dst=/work", + "--entrypoint", "sh", self.images["rust"], "-c", "rm -rf /work/* /work/.[!.]*"], + capture_output=True) + shutil.rmtree(instance.workdir, ignore_errors=True) + + +def run_corpus(launcher: Launcher, ctx: Context, pattern: str = "") -> list[SettingOutcome]: + outcomes = [] + for setting in SPEC: + for kind in ("positive", "invalid"): + if pattern and not re.search(pattern, f"{setting.name}::{kind}"): + continue + outcomes.append(run_setting(launcher, ctx, setting, kind)) + return outcomes + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--rust-binary", type=Path, required=True) + parser.add_argument("--python-dir", type=Path, required=True) + parser.add_argument("--python", type=Path, required=True) + parser.add_argument("--rest-upstream", required=True) + parser.add_argument("--filter", default="") + parser.add_argument("--report", type=Path) + args = parser.parse_args() + launcher = NativeLauncher(args.rust_binary.resolve(), args.python_dir.resolve(), args.python.absolute()) + ctx = Context(args.rest_upstream, args.rest_upstream) + outcomes = [] + for setting in SPEC: + for kind in ("positive", "invalid"): + if args.filter and not re.search(args.filter, f"{setting.name}::{kind}"): + continue + outcome = run_setting(launcher, ctx, setting, kind) + outcomes.append(outcome) + print(("PASS " if outcome.passed else "FAIL ") + outcome.scenario_id + " " + + json.dumps(outcome.observation, sort_keys=True), flush=True) + failed = sum(not o.passed for o in outcomes) + print(f"setting scenarios={len(outcomes)} passed={len(outcomes) - failed} failed={failed}") + if args.report: + args.report.write_text(json.dumps([o.__dict__ for o in outcomes], indent=1) + "\n") + return 1 if failed else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/system_ui.py b/scripts/system_ui.py new file mode 100644 index 00000000..cf628bc7 --- /dev/null +++ b/scripts/system_ui.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +"""Playwright executor for the `ui::` scenarios of the system E2E corpus. + +Runs inside the official Playwright image on the system E2E network against the +candidate dashboard after the operation corpus has seeded its world. Every +dashboard route is opened as the administrator, the way a user reaches it (detail +pages receive the selected entity through sessionStorage exactly as their list +pages hand it over), and checked for: + + route the page is reached (no redirect to /login or /403), document < 400 + backend_effect the seeded world entity the page is about is rendered + console no console errors, no uncaught page errors, no framework error overlay + network no gateway call (/platform, /api) answered with a 5xx or 401 + accessibility a non-empty , an html lang attribute, and one main/landmark region +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Any + +from playwright.sync_api import Page, sync_playwright + +PUBLIC_ROUTES = {"/login", "/403", "/public"} +# Pages that are about a seeded world entity: (entity kind, sessionStorage key or None) +LIST_TEXT = { + "/apis": "sysapi", "/groups": "sysgroup", "/roles": "sysrole", + # Routing client keys are masked in the list; its name is shown instead. + "/routings": "system e2e", + "/users": "sysuser", "/credit-defs": "syscredit", "/tiers": "System", # tier display name +} + + +def unwrap(value: Any) -> Any: + while isinstance(value, dict) and isinstance(value.get("response"), (dict, list)): + value = value["response"] + return value + + +class World: + def __init__(self, page: Page, web: str): + self.page, self.web = page, web + + def get(self, path: str) -> Any: + try: + response = self.page.request.get(self.web + path) + return unwrap(response.json()) if response.ok else None + except Exception: # noqa: BLE001 -- a missing lookup only weakens that route's check + return None + + def resolve(self) -> dict[str, Any]: + api = self.get("/platform/api/sysapi/v1") or {} + tiers = self.get("/platform/tiers/") or {} + return { + "api": api, + "group": self.get("/platform/group/sysgroup") or {"group_name": "sysgroup"}, + "role": self.get("/platform/role/sysrole") or {"role_name": "sysrole"}, + "routing": self.get("/platform/routing/sysclient") or {"client_key": "sysclient"}, + "user": self.get("/platform/user/sysuser") or {"username": "sysuser"}, + "tier": "systier" if "systier" in json.dumps(tiers) else None, + } + + +def plan(route: str, world: dict[str, Any]) -> tuple[str, dict[str, Any], str | None]: + """URL, sessionStorage items, and the text the page must show.""" + api = world["api"] + api_id = str(api.get("api_id") or "sysapi") + substitutions = { + "[apiId]": api_id, "[username]": "sysuser", "[group]": "syscredit", "[groupName]": "sysgroup", + "[roleName]": "sysrole", "[clientKey]": "sysclient", "[id]": "systier", + } + url = route + for placeholder, value in substitutions.items(): + url = url.replace(placeholder, value) + storage: dict[str, Any] = {} + expected = LIST_TEXT.get(route) + if route.startswith("/apis/[apiId]"): + storage["selectedApi"] = api + expected = "sysapi" + elif route == "/groups/[groupName]": + storage["selectedGroup"], expected = world["group"], "sysgroup" + elif route == "/roles/[roleName]": + storage["selectedRole"], expected = world["role"], "sysrole" + elif route == "/routings/[clientKey]": + storage["selectedRouting"], expected = world["routing"], "sysclient" + elif route == "/users/[username]": + storage["selectedUser"], expected = world["user"], "sysuser" + elif route in ("/authorizations/[username]", "/credits/[username]"): + expected = "sysuser" + elif route == "/credit-defs/[group]": + expected = "syscredit" + elif route.startswith("/tiers/[id]"): + expected = "System" + return url, storage, expected + + +def check(page: Page, web: str, route: str, url: str, storage: dict[str, Any], expected: str | None) -> list[str]: + console: list[str] = [] + failed: list[str] = [] + page.on("console", lambda message: console.append(message.text) if message.type == "error" else None) + page.on("pageerror", lambda error: console.append(f"pageerror: {error}")) + + def record(response: Any) -> None: + path = re.sub(r"^https?://[^/]+", "", response.url) + if (path.startswith("/platform") or path.startswith("/api/")) and ( + response.status >= 500 or response.status == 401 + ): + failed.append(f"{response.request.method} {path} -> {response.status}") + + page.on("response", record) + if storage: + page.goto(web + "/403") + page.evaluate("items => { for (const [k, v] of Object.entries(items)) sessionStorage.setItem(k, JSON.stringify(v)) }", + storage) + document = page.goto(web + url, wait_until="domcontentloaded") + page.wait_for_load_state("networkidle", timeout=20000) + problems: list[str] = [] + final = re.sub(r"^https?://[^/]+", "", page.url).split("?", 1)[0] + if document is None or document.status >= 400: + problems.append(f"document status {document.status if document else 'none'}") + if route not in PUBLIC_ROUTES and (final.startswith("/login") or final.startswith("/403")): + problems.append(f"redirected to {final}") + # Form pages show the entity in input values rather than text nodes. + text = page.inner_text("body") + " " + " ".join( + page.eval_on_selector_all("input, textarea, select", "items => items.map(item => item.value)")) + if "Application error" in text or "Unhandled Runtime Error" in text: + problems.append("framework error overlay") + if expected and expected not in text: + # Search filters only the loaded page; page forward like a user would. + next_button = page.get_by_role("button", name="Next", exact=True) + for _ in range(25): + if expected in text or not next_button.count() or next_button.first.is_disabled(): + break + next_button.first.click() + page.wait_for_load_state("networkidle", timeout=20000) + page.wait_for_timeout(300) + text = page.inner_text("body") + if expected and expected not in text: + problems.append(f"backend entity {expected!r} not rendered") + # Next.js dev noise and aborted navigations are not product errors. + # The browser ignores COOP on the plain-HTTP test network (a transport + # artifact, not an application error). + errors = [line for line in console + if ("Failed to load resource" not in line or "401" in line or "500" in line) + and "Cross-Origin-Opener-Policy header has been ignored" not in line + and "Origin-Agent-Cluster" not in line] + if errors: + problems.append("console errors: " + "; ".join(errors[:3])) + if failed: + problems.append("failed gateway calls: " + "; ".join(failed[:3])) + if not (page.title() or "").strip(): + problems.append("missing document title") + if not page.evaluate("document.documentElement.lang"): + problems.append("missing html lang") + if route not in PUBLIC_ROUTES and page.locator("main, [role=main], nav, [role=navigation]").count() == 0: + problems.append("no landmark region") + return problems + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--web", required=True) + parser.add_argument("--admin-email", required=True) + parser.add_argument("--admin-password", required=True) + parser.add_argument("--routes", type=Path, required=True, help="JSON list of dashboard routes") + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + routes = json.loads(args.routes.read_text()) + outcomes = [] + with sync_playwright() as playwright: + browser = playwright.chromium.launch() + context = browser.new_context() + login_page = context.new_page() + login = login_page.request.post(args.web + "/platform/authorization", + data={"email": args.admin_email, "password": args.admin_password}) + if not login.ok: + print(f"administrator login through the dashboard origin failed: HTTP {login.status}", file=sys.stderr) + return 2 + # Give the administrator the seeded tier so quota pages show real data. + login_page.request.post(args.web + "/platform/tiers/assignments", data={"user_id": "admin", "tier_id": "systier"}) + world = World(login_page, args.web).resolve() + login_page.close() + for route in routes: + url, storage, expected = plan(route, world) + page = context.new_page() + try: + problems = check(page, args.web, route, url, storage, expected) + except Exception as error: # noqa: BLE001 -- a crashed check is a failed scenario + problems = [f"{type(error).__name__}: {error}"] + finally: + page.close() + outcomes.append({"scenario_id": f"ui::{route}", "passed": not problems, "url": url, + "reason": "; ".join(problems)}) + print(("PASS " if not problems else "FAIL ") + f"ui::{route} {'; '.join(problems)}", flush=True) + browser.close() + args.report.write_text(json.dumps(outcomes, indent=1) + "\n") + failed = sum(not item["passed"] for item in outcomes) + print(f"ui scenarios={len(outcomes)} passed={len(outcomes) - failed} failed={failed}") + return 1 if failed else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/test_release_check.py b/scripts/test_release_check.py index a9c86f17..44289a05 100644 --- a/scripts/test_release_check.py +++ b/scripts/test_release_check.py @@ -26,6 +26,8 @@ def base_environment(self, evidence: Path) -> dict[str, str]: scenario_names = [case["name"] for case in json.loads(scenario_path.read_text())] openapi_path = REPO_ROOT / "parity" / "openapi" / "python-openapi.json.gz.b64" openapi_bytes, openapi_document = load_openapi(openapi_path) + approvals_path = REPO_ROOT / "parity" / "differential" / "operation_approvals.json" + approvals = json.loads(approvals_path.read_text()) operation_results = [ { "name": case["name"], @@ -33,12 +35,11 @@ def base_environment(self, evidence: Path) -> dict[str, str]: "path_template": case["path_template"], "operation_id": case["operation_id"], "probe_depth": "authenticated_synthetic_boundary", - "match": True, - "approved_divergence": None, + "match": case["name"] not in approvals, + "approved_divergence": approvals.get(case["name"]), } for case in operation_cases(openapi_document) ] - approvals_path = REPO_ROOT / "parity" / "differential" / "operation_approvals.json" differential.write_text( json.dumps( { @@ -97,6 +98,7 @@ def base_environment(self, evidence: Path) -> dict[str, str]: json.dumps( { "schema_version": 1, + "image_id": "sha256:" + "a" * 64, "image_smoke": {"passed": True}, "restore_rehearsal": {"passed": True}, "cutover": {"passed": True}, @@ -158,6 +160,7 @@ def base_environment(self, evidence: Path) -> dict[str, str]: "EXTERNAL_STORAGE_LOG": str(external_log), "RELEASE_OPERATIONS_REPORT": str(operations), "SYSTEM_E2E_REPORT": str(system_e2e), + "RELEASE_IMAGE_ID": "sha256:" + "a" * 64, } def run_check(self, environment: dict[str, str]) -> subprocess.CompletedProcess[str]: @@ -252,6 +255,23 @@ def test_rejects_incomplete_system_e2e_evidence(self) -> None: self.assertNotEqual(completed.returncode, 0) self.assertIn("without skips", completed.stderr) + def test_rejects_evidence_for_a_different_image(self) -> None: + with tempfile.TemporaryDirectory() as directory: + environment = self.base_environment(Path(directory)) + environment["RELEASE_IMAGE_ID"] = "sha256:" + "b" * 64 + completed = self.run_check(environment) + self.assertNotEqual(completed.returncode, 0) + self.assertIn("RELEASE_IMAGE_ID", completed.stderr) + + environment = self.base_environment(Path(directory)) + path = Path(environment["SYSTEM_E2E_REPORT"]) + report = json.loads(path.read_text()) + report["candidate_image_id"] = "sha256:" + "c" * 64 + path.write_text(json.dumps(report)) + completed = self.run_check(environment) + self.assertNotEqual(completed.returncode, 0) + self.assertIn("SYSTEM_E2E_REPORT was not generated for RELEASE_IMAGE_ID", completed.stderr) + if __name__ == "__main__": unittest.main() diff --git a/scripts/test_system_e2e.py b/scripts/test_system_e2e.py index f2418c12..45a110f9 100644 --- a/scripts/test_system_e2e.py +++ b/scripts/test_system_e2e.py @@ -4,6 +4,7 @@ from __future__ import annotations import unittest +from types import SimpleNamespace from scripts import system_e2e @@ -16,7 +17,7 @@ def setUpClass(cls) -> None: def test_union_inventory_and_every_cell_has_a_disposition(self) -> None: contract = system_e2e.load_json(system_e2e.SYSTEM / "contract.json") self.assertEqual(len(self.ledger["operations"]), 178) - self.assertEqual(len(self.ledger["dashboard_routes"]), 50) + self.assertEqual(len(self.ledger["dashboard_routes"]), 51) self.assertEqual( len(self.ledger["operation_cells"]), 178 * len(contract["operation_classes"]), @@ -36,6 +37,31 @@ def test_generated_rows_cover_every_valid_pair(self) -> None: self.assertEqual(system_e2e.valid_pairs(pairwise) - covered, set()) self.assertGreaterEqual(len(self.ledger["pairwise_rows"]), 40) + def test_pairwise_profile_selection_and_transport_failure(self) -> None: + from scripts.system_operations import Response + from scripts.system_pairwise import Pairwise + + runner = Pairwise.__new__(Pairwise) + runner.world = SimpleNamespace(upstreams={ + profile: f"http://fixture-{profile}" + for profile in system_e2e.load_json(system_e2e.SYSTEM / "pairwise.json")["axes"]["protocol_profile"] + }) + for profile in ("rest-2", "graphql-2", "soap-2", "grpc-2", "grpc-web-2"): + index, row = next((index, row) for index, row in enumerate(self.ledger["pairwise_rows"], 1) + if row["protocol_profile"] == profile) + planned = runner.plan(index, {**row, "body_size": "empty"}) + self.assertEqual(planned.api["api_servers"], [f"http://fixture-{profile}"]) + self.assertEqual(planned.extra["profile"], profile) + + runner.candidate = SimpleNamespace(name="candidate", admin_password="secret", jwt_secret="secret") + runner.oracle = SimpleNamespace(name="oracle") + runner.plan = lambda _index, _row: object() + runner.build = lambda _target, _name, _plan: None + runner.fire = lambda _target, _plan: Response(599, {}, b"transport failure") + runner.approvals = [] + runner.log = lambda _line: None + self.assertFalse(runner.run(1, {})["passed"]) + def test_every_scenario_maps_features_and_expected_evidence(self) -> None: feature_ids = set(self.ledger["features"]) scenario_ids: set[str] = set() diff --git a/system-tests/approvals.json b/system-tests/approvals.json index 8a90291f..b65ff2f7 100644 --- a/system-tests/approvals.json +++ b/system-tests/approvals.json @@ -1,4 +1,517 @@ { "schema_version": 1, - "approvals": [] + "approvals": [ + { + "scenario_id": "operation::[A-Z]+::/platform/.*::(anonymous|expired_token|malformed_token|wrong_audience|revoked_token|invalidated_token|disabled_user|invalid_transition)", + "signature": { + "candidate": { + "status": 401 + }, + "oracle": { + "status": 500 + } + }, + "rationale": "The pinned routes call auth_required inside a broad `except Exception`, so its 401 HTTPException is swallowed and rendered as a 500 (GTW999, ISE001, or a route-specific *999 code). The candidate returns the intended 401. Verified by anonymous requests to every frozen operation against the pinned server.", + "issue": "MIG-101", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::[A-Z]+::[^:]+::wrong_issuer", + "signature": { + "candidate": { + "status": 401 + } + }, + "rationale": "The pinned server has no JWT_ISSUER support and accepts a correctly signed token from any issuer; the candidate enforces the configured issuer claim (JWT_ISSUER, a release-required setting).", + "issue": "MIG-102", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::POST::/platform/demo/seed::.*", + "signature": { + "candidate": { + "status": { + "one_of": [ + 200, + 400 + ] + } + }, + "oracle": { + "status": 500, + "error_code": "DEMO999" + } + }, + "rationale": "The pinned seeder crashes on every call: seed_metrics calls MinuteBucket.add(dur, status) without the required username/api_key arguments (TypeError, confirmed by live traceback). The candidate seeder succeeds in memory mode and refuses with 400 DEMO002 in external mode, where seeding shared storage with demo data is not allowed.", + "issue": "MIG-092", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::[A-Z]+::/api/(rest|soap|graphql)/\\{path\\}::(boundary_value|missing_resource|incorrect_path)", + "signature": { + "candidate": { + "status": 404 + }, + "oracle": { + "status": 500 + } + }, + "rationale": "For an unknown API the pinned gateway raises inside api_util.get_api (unpacking a path without a version, confirmed by live traceback) and returns 500 GTW006; the candidate returns 404 for the unmatched API.", + "issue": "MIG-090", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::(GET|POST)::/platform/config/(current|reload|reloadable-keys)::.*", + "signature": { + "candidate": { + "status": 200 + }, + "oracle": { + "status": 200, + "keys": [ + "error_code", + "error_message", + "message", + "response", + "response_headers", + "status_code" + ] + } + }, + "rationale": "The pinned hot-reload routes build ResponseModel(data=...), but ResponseModel has no `data` field, so Pydantic drops the payload and every call returns an empty envelope with `response: null`. The candidate returns the configuration data the routes were written to return.", + "issue": "MIG-103", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::POST::/platform/config/rollback::.*", + "signature": { + "candidate": { + "status": 200 + }, + "oracle": { + "status": 500 + } + }, + "rationale": "Once a snapshot exists the pinned rollback always fails in memory mode: its InMemoryCollection has no delete_many ('InMemoryCollection' object has no attribute 'delete_many', from the live log). The candidate restores the snapshot.", + "issue": "MIG-104", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::POST::/platform/credit/\\{username\\}::.*", + "signature": { + "candidate": { + "status": 200 + }, + "oracle": { + "status": 400, + "error_code": "CRD014" + } + }, + "rationale": "The pinned add_user_credits handler overwrites the path `username` with the caller's token subject, so credits are written to the caller and the body/path check rejects any request that names another user (CRD014). The candidate credits the user named in the path.", + "issue": "MIG-105", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::POST::/platform/openapi/parse::.*", + "signature": { + "oracle": { + "status": 500, + "error_code": "OPENAPI999" + } + }, + "rationale": "The pinned parser calls request.json() without guarding a body-less, non-JSON or malformed request, so it raises inside its broad except and returns 500 OPENAPI999; the candidate answers the request (200, or 400 OPENAPI004 for an unparseable spec). With a valid JSON body both servers agree.", + "issue": "MIG-093", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::(PUT|POST)::/platform/proto/\\{api_name\\}/\\{api_version\\}::.*", + "signature": { + "oracle": { + "status": 599 + } + }, + "rationale": "The pinned server never answers a multipart proto PUT, or a POST whose Content-Type header is missing: its body-size middleware consumes the request stream and the handler then fails with 'Stream consumed' while the connection hangs (the executor records a 599 timeout). There is no oracle response to compare against.", + "issue": "MIG-106", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::POST::/platform/rate-limits/bulk/(enable|disable)::.*", + "signature": { + "oracle": { + "status": 404, + "keys": [ + "detail" + ] + } + }, + "rationale": "The pinned router registers /rate-limits/{rule_id}/enable|disable before the bulk routes, so every bulk call is captured as rule 'bulk' and answered 404 'Rule bulk not found'; the bulk endpoints are unreachable in the pinned server. The candidate serves them.", + "issue": "MIG-107", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::DELETE::/platform/(group|role|routing|api)/[^:]+::(lifecycle|duplicate_resource|idempotent_repeat|conflict|invalid_transition)", + "signature": { + "candidate": { + "status": 400 + }, + "oracle": { + "status": 200 + } + }, + "rationale": "After a delete the pinned server keeps the entity in its cache, so a second delete of the same entity reports success again. The candidate reports that the entity no longer exists (the same 400 code Python uses for a missing entity).", + "issue": "MIG-108", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::(POST|GET)::/platform/tiers/(compare|statistics/all)::.*", + "signature": { + "candidate": { + "status": 200, + "body_class": "array" + }, + "oracle": { + "status": 200, + "keys": [] + } + }, + "rationale": "The pinned TierService returns a list, but these routes declare response_model=Dict[str, Any]; Pydantic v1 coerces the list through dict(), so the pinned server always answers {} (observed even with matching tiers present). The candidate returns the comparison/statistics list the service computes.", + "issue": "MIG-110", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::GET::/platform/monitor/(readiness|metrics)::.*", + "signature": { + "candidate": { + "status": 200 + }, + "oracle": { + "status": 200 + } + }, + "rationale": "Additive observability: the candidate adds tls_certificates_valid to readiness and active_requests/total_errors/uptime_seconds to metrics, and does not disclose backend details (mongodb/redis/mode) in anonymous readiness responses. All pinned keys are present for authorised callers.", + "issue": "MIG-109", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::POST::/grpc-web/\\{api_name\\}/\\{service\\}/\\{method\\}::(anonymous|expired_token|malformed_token|wrong_audience|wrong_issuer|revoked_token|invalidated_token|disabled_user|invalid_transition|missing_permission|wrong_group|subscription_absent|lifecycle|duplicate_resource|conflict|idempotent_repeat)", + "signature": { + "oracle": { + "status": 200 + } + }, + "rationale": "The pinned gRPC-Web route is an unauthenticated stub: it performs no authentication, subscription, group or rate-limit checks and answers grpc-status 0 without calling the upstream. The candidate applies the same policy chain as every other gateway route (401/403/429), so unauthorised gRPC-Web calls are refused.", + "issue": "MIG-111", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::GET::/platform/analytics/(api|user)/.*::boundary_value", + "signature": { + "candidate": { + "status": 404 + }, + "oracle": { + "status": 500, + "error_code": "ANALYTICS999" + } + }, + "rationale": "For an oversized unknown API/user name the pinned analytics routes raise inside their broad except and answer 500 ANALYTICS999; the candidate reports the resource as not found.", + "issue": "MIG-112", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::POST::/platform/authorization(/register)?::wrong_scalar_type", + "signature": { + "candidate": { + "status": 400, + "error_code": "AUTH001" + }, + "oracle": { + "status": 500, + "error_code": "GTW999" + } + }, + "rationale": "The pinned login/register handlers call data.get() on the decoded body without checking it is an object, so a JSON array body crashes with AttributeError and 500 GTW999 (confirmed by live traceback); the candidate rejects it with 400 AUTH001.", + "issue": "MIG-113", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::(GET|POST)::/api/grpc/\\{path\\}::.*", + "signature": { + "candidate": { + "status": 200 + }, + "oracle": { + "status": 501, + "error_code": "GTW006" + } + }, + "rationale": "The pinned JSON gRPC gateway calls /<api>_<version>.<Service>/<Method> (its module base) instead of the package declared in the uploaded proto, so an upstream serving fixture.v1.Resource answers UNIMPLEMENTED ('Method not found!', confirmed in the oracle log). The candidate resolves the method's full name from the uploaded descriptor and reaches the upstream; api_grpc_package still overrides the package.", + "issue": "MIG-114", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::POST::/platform/credit/rotate-key::.*", + "signature": { + "oracle": { + "status": 422, + "error_code": "VAL001" + } + }, + "rationale": "The pinned router registers POST /credit/{username} before /credit/rotate-key, so every rotate-key call is captured as add_user_credits for user 'rotate-key' and validated against UserCreditModel (422 VAL001 for any rotate-key body, confirmed live). The rotate-key endpoint is unreachable in the pinned server; the candidate serves it.", + "issue": "MIG-115", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::DELETE::/platform/proto/\\{api_name\\}/\\{api_version\\}::boundary_value", + "signature": { + "candidate": { + "status": 400, + "error_code": "GTW013" + }, + "oracle": { + "status": 500, + "error_code": "GTW999" + } + }, + "rationale": "For an over-long API name the pinned delete handler lets the 400 path-validation HTTPException fall into its broad except and answers 500 GTW999; its upload/get/update handlers answer 400 GTW013 for the same name. The candidate answers 400 GTW013 consistently.", + "issue": "MIG-116", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::POST::/platform/authorization/admin/(disable|enable|revoke|unrevoke)/\\{username\\}::boundary_value", + "signature": { + "candidate": { + "status": 200 + }, + "oracle": { + "status": 500, + "error_code": "GTW999" + } + }, + "rationale": "The pinned admin handlers embed the username in their success message; for a 300-character username ResponseModel.message exceeds its 255-character limit and the pydantic ValidationError becomes 500 GTW999 (confirmed by live traceback) after the action has already been applied. The candidate completes the same action and reports success.", + "issue": "MIG-117", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::(GET|POST)::/api/grpc/\\{path\\}::boundary_value", + "signature": { + "candidate": { + "status": 404, + "error_code": "GTW012" + }, + "oracle": { + "status": 500, + "error_code": "GTW006" + } + }, + "rationale": "For a 300-character API name the pinned gRPC gateway builds 'Proto file not found for API: <name>/<version>' and ResponseModel.error_message exceeds its 255-character limit, so the pydantic ValidationError becomes 500 GTW006 (confirmed by live traceback). The candidate returns the intended 404 GTW012.", + "issue": "MIG-118", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "setting::(GATEWAY_TIMEOUT|RETRY_ENABLED|RETRY_MAX_ATTEMPTS)::positive", + "signature": {}, + "rationale": "The pinned server only lists GATEWAY_TIMEOUT, RETRY_ENABLED and RETRY_MAX_ATTEMPTS in its hot-reload configuration; no request path reads them (utils/hot_reload_config.py), so they have no effect. The candidate applies them as documented in user-docs/02-configuration.md.", + "issue": "MIG-119", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "setting::(CIRCUIT_BREAKER_TIMEOUT|HTTP_RETRY_BASE_DELAY|HTTP_RETRY_MAX_DELAY)::invalid", + "signature": {}, + "rationale": "The pinned server calls float() on these values while handling each request (utils/http_client.py), so an unparseable value turns every upstream call into a 500. The candidate ignores the unparseable value and uses the documented default.", + "issue": "MIG-120", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "setting::JWT_KEYS::invalid", + "signature": { + "candidate": { + "started": false + } + }, + "rationale": "With unparseable JWT_KEYS the pinned server silently falls back to JWT_SECRET_KEY. The candidate refuses to start so a broken key-rotation configuration cannot go unnoticed (fail closed).", + "issue": "MIG-121", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "setting::MEM_AUTO_SAVE_ENABLED::invalid", + "signature": { + "candidate": { + "probe": false + }, + "oracle": { + "probe": true + } + }, + "rationale": "The pinned auto-save loop ignores enable_auto_save and dumps memory whenever MEM_AUTO_SAVE_FREQ > 0, so memory is saved even when auto-save is off or the flag is unparseable. The candidate saves only when MEM_AUTO_SAVE_ENABLED is true. Memory-mode deployments that relied on the implicit dumps must set MEM_AUTO_SAVE_ENABLED=true (user-docs/02-configuration.md).", + "issue": "MIG-122", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "pairwise::[0-9]{4}", + "signature": { + "candidate": { + "status": { + "one_of": [ + 401, + 403, + 404, + 429 + ] + } + }, + "oracle": { + "status": 200, + "content_type": { + "one_of": [ + "application/grpc-web", + "application/grpc-web+proto", + "application/grpc-web-text" + ] + } + } + }, + "rationale": "Pairwise rows on the gRPC-Web profiles: the pinned gRPC-Web route is an unauthenticated stub that answers grpc-status 0 without applying authentication, subscription, group, tier or rate-limit policy (see MIG-111). The candidate applies the same policy chain as every other gateway route, so it refuses these rows.", + "issue": "MIG-111", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::[A-Z]+::/platform/(tiers|rate-limits)/.*::(anonymous|expired_token|malformed_token|wrong_audience|wrong_issuer|revoked_token|invalidated_token|disabled_user|missing_permission|wrong_group|invalid_transition)", + "signature": { + "candidate": { + "status": { + "one_of": [ + 401, + 403 + ] + } + } + }, + "rationale": "Security fix. The pinned tier and rate-limit-rule routers declare no authentication, so any anonymous caller can create, delete or reassign tiers and rate-limit rules (including granting itself an unlimited tier). The candidate requires an authenticated caller with manage_tiers / manage_rate_limits (TIER001 / RATE002); /rate-limits/status stays available to any authenticated caller.", + "issue": "MIG-123", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "operation::GET::/platform/tiers/\\{tier_id\\}/users::.*", + "signature": { + "candidate": { + "status": 200, + "body_class": "array" + }, + "oracle": { + "status": 500 + } + }, + "rationale": "The pinned list_users_in_tier builds UserTierAssignment(**document) from the raw store document, which carries '_id', so it raises TypeError and answers 500 'Failed to list users' whenever the tier has an assignment (confirmed in the oracle log). The candidate returns the assignments.", + "issue": "MIG-124", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "pairwise::[0-9]{4}", + "signature": { + "candidate": { + "status": 413 + }, + "oracle": { + "status": 400, + "content_type": "" + } + }, + "rationale": "Oversized gRPC-Web bodies: the pinned gRPC-Web route has no body-size limit and only rejects the 12 MiB payload later as invalid framing/base64 (bare 400). The candidate applies MAX_BODY_SIZE_BYTES_GRPC to gRPC-Web as to every gRPC request and answers 413 before buffering the body.", + "issue": "MIG-125", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "pairwise::0014", + "signature": { + "candidate": { + "status": 413, + "error_code": "REQ001" + }, + "oracle": { + "status": 200, + "content_type": "application/grpc-web" + } + }, + "rationale": "The pinned gRPC-Web route accepts this oversized binary body without checking its size and emits a stub response. The candidate enforces MAX_BODY_SIZE_BYTES_GRPC before buffering the body, consistent with MIG-125.", + "issue": "MIG-125", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "pairwise::[0-9]{4}", + "signature": { + "candidate": { + "status": 400, + "content_type": "text/plain" + }, + "oracle": { + "status": 200, + "content_type": { + "one_of": [ + "application/grpc-web", + "application/grpc-web+proto", + "application/grpc-web-text" + ] + } + } + }, + "rationale": "gRPC-Web rows with an empty or unframed body: the pinned gRPC-Web route is a stub that never parses the request and always answers grpc-status 0 (MIG-111). The candidate proxies the call and rejects a body without a gRPC-Web data frame with 400.", + "issue": "MIG-111", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + }, + { + "scenario_id": "pairwise::[0-9]{4}", + "signature": { + "candidate": { + "status": { + "one_of": [ + 401, + 403, + 404, + 429 + ] + } + }, + "oracle": { + "status": 400, + "content_type": "" + } + }, + "rationale": "gRPC-Web rows whose body the pinned stub rejects (bare 400 'Invalid base64 body') while the candidate first applies the gateway policy chain (IP policy, authentication, subscription, limits) that the pinned gRPC-Web route skips entirely (MIG-111).", + "issue": "MIG-111", + "owner": "doorman-maintainers", + "expires": "2027-03-31" + } + ] } diff --git a/system-tests/contract.json b/system-tests/contract.json index c721e800..e9abbc16 100644 --- a/system-tests/contract.json +++ b/system-tests/contract.json @@ -85,9 +85,10 @@ "snapshot-concurrent-restart-restore", "redis-outage-cached-auth-recovery", "mongo-stepdown-concurrent-writes", - "secret-data-logging-export-redaction" + "secret-data-logging-export-redaction", + "native-mtls-auth-rbac-credits" ], - "expected_dashboard_routes": 50, - "dashboard_routes_sha256": "5ed913fa0571809cfad5facf00c809ca773c7f9c89e99d519ef2b3b034a06be3", + "expected_dashboard_routes": 51, + "dashboard_routes_sha256": "06a4e914ebad825abcc108f62dfcfd58fa92d34c547a2e68508d8da62371dc5f", "approved_differences": [] } diff --git a/user-docs/02-configuration.md b/user-docs/02-configuration.md index 0d57062a..f6d9ea2c 100644 --- a/user-docs/02-configuration.md +++ b/user-docs/02-configuration.md @@ -8,7 +8,7 @@ | `PORT` | `3001` | Backend API port | | `WEB_PORT` | `3000` | Frontend port (Docker only) | | `THREADS` | `4` | Worker threads. Must be `1` in MEM mode | -| `DEV_RELOAD` | `false` | Auto-reload on code changes | +| `DEV_RELOAD` | `false` | Legacy Python auto-reload; ignored by the Rust gateway | | `PID_FILE` | `doorman.pid` | Process ID file path | ## Frontend Configuration @@ -42,6 +42,8 @@ localStorage.setItem('API_URL', 'https://api.doorman.example.com') | `MEM_OR_EXTERNAL` | `MEM` | `MEM` (in-memory) or `REDIS` (production) | | `MEM_ENCRYPTION_KEY` | - | 32+ char secret for memory dumps (required for dumps) | | `MEM_DUMP_PATH` | `generated/memory_dump.bin` | Memory dump file path. Relative paths are resolved from the service working directory; the container default maps to the `/app/data` volume. | +| `MEM_AUTO_SAVE_ENABLED` | `false` | Periodically dump memory-mode data to `MEM_DUMP_PATH`. **Migration note:** the Python gateway dumped periodically even when this was unset or `false`; set it to `true` to keep that behaviour. | +| `MEM_AUTO_SAVE_FREQ` | `900` | Seconds between memory auto-saves (minimum 60). | | `SECURITY_SETTINGS_FILE` | `generated/security_settings.json` | Best-effort JSON mirror of security settings. Memory-mode startup loads it only when no settings document exists; external mode always uses MongoDB. Compose defaults to `/app/data/security_settings.json` on its persistent volume. | | `PYTHONINTMAXSTRDIGITS` | `4300` | Digit limit for security-settings interval strings and autosave environment integers. Use `0` for unlimited digits or a value from `640` to `2147483647`. Captured at startup; invalid values prevent startup. | | `REDIS_HOST` | `localhost` | Redis hostname | @@ -57,12 +59,19 @@ localStorage.setItem('API_URL', 'https://api.doorman.example.com') | Variable | Default | Description | |----------|---------|-------------| | `JWT_SECRET_KEY` | - | **Required.** JWT signing secret (32+ chars) | +| `LOGIN_ACCOUNT_RATE_LIMIT` / `LOGIN_ACCOUNT_RATE_WINDOW` | `10` / `900` | Login attempts allowed per account per window (seconds); complements the per-IP limit | +| `REGISTER_ACCOUNT_RATE_LIMIT` / `REGISTER_ACCOUNT_RATE_WINDOW` | `5` / `3600` | Registration attempts per account per window (seconds) | | `AUTH_EXPIRE_TIME` | `30` | Access token expiration (numeric) | | `AUTH_EXPIRE_TIME_FREQ` | `minutes` | `seconds`, `minutes`, `hours`, `days` | | `AUTH_REFRESH_EXPIRE_TIME` | `7` | Refresh token expiration (numeric) | | `AUTH_REFRESH_EXPIRE_FREQ` | `days` | `seconds`, `minutes`, `hours`, `days` | | `TOKEN_ENCRYPTION_KEY` | - | Encrypt API keys at rest (recommended) | | `HTTPS_ONLY` | `false` | **Required in production.** Enforces HTTPS, secure cookies, CSRF | +| `DOWNSTREAM_TLS_MODE` | `proxy` | `proxy` keeps external TLS termination; `native` terminates TLS in the Rust listener. | +| `DOWNSTREAM_TLS_CERT_FILE` | unset | PEM certificate chain for native TLS. Required when mode is `native`. | +| `DOWNSTREAM_TLS_KEY_FILE` | unset | PEM private key for native TLS. Required when mode is `native`. | +| `TLS_PROFILES_FILE` | unset | YAML file defining file-managed client-CA and upstream TLS profiles. | +| `TLS_SECRET_ENCRYPTION_KEY` | unset | Base64 of exactly 32 random bytes, shared across nodes. Required for admin-managed TLS material; without it uploads are rejected. | ## IP Access Control @@ -96,6 +105,7 @@ Policy matching trims surrounding whitespace. IPv4 networks accept prefix length | Variable | Default | Description | |----------|---------|-------------| +| `LOGS_ENABLED` | `true` | Set to `false` to disable stored request/audit logs, logging APIs, and the Logs page. Metrics and analytics remain enabled. | | `LOG_FORMAT` | `plain` | `plain` or `json` (use json in production) | | `LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` | @@ -105,8 +115,13 @@ Policy matching trims surrounding whitespace. IPv4 networks accept prefix length |----------|---------|-------------| | `HTTP_CONNECT_TIMEOUT` | `5.0` | TCP connection timeout (seconds) | | `HTTP_READ_TIMEOUT` | `30.0` | Response read timeout | -| `HTTP_WRITE_TIMEOUT` | `30.0` | Request write timeout | -| `HTTP_TIMEOUT` | `30.0` | Pool acquire timeout | +| `HTTP_WRITE_TIMEOUT` | `30.0` | Legacy Python write timeout; ignored by the Rust gateway | +| `HTTP_TIMEOUT` | `30.0` | Legacy Python pool-acquire timeout; ignored by the Rust gateway | +| `HTTP_MAX_KEEPALIVE` | `50` | Idle pooled upstream connections (per upstream host) | +| `HTTP_KEEPALIVE_EXPIRY` | `30.0` | Seconds an idle upstream connection is kept | +| `ENABLE_HTTPX_CLIENT_CACHE` | `true` | `false` disables upstream connection reuse | +| `HTTP_ENABLE_HTTP2` | `false` | `true` allows HTTP/2 to upstreams; otherwise HTTP/1.1 only | +| `HTTP_MAX_CONNECTIONS` | `100` | Legacy Python total connection cap; ignored by the Rust gateway | | `HTTP_RETRY_BASE_DELAY` | `0.25` | Retry base delay (seconds) | | `HTTP_RETRY_MAX_DELAY` | `2.0` | Max retry backoff | | `CIRCUIT_BREAKER_ENABLED` | `true` | Enable circuit breaker | diff --git a/user-docs/03-security.md b/user-docs/03-security.md index b9c5f842..42531472 100644 --- a/user-docs/03-security.md +++ b/user-docs/03-security.md @@ -88,6 +88,12 @@ Users are assigned roles that grant specific permissions: - `manage_users`: User administration - `view_logs`: Access audit logs - `manage_security`: Security settings and IP policies +- `manage_tiers`: Create, update, delete and assign rate-limit tiers (`/platform/tiers/*`) +- `manage_rate_limits`: Manage rate-limit rules (`/platform/rate-limits/*`; any signed-in user may read their own `/platform/rate-limits/status`) + +> **Upgrade note:** the Python gateway exposed the tier and rate-limit-rule endpoints without authentication. Doorman now requires a signed-in user with the permission above; grant it to any automation that manages tiers or rules. + +Only roles flagged `platform_admin`, or named `admin`, bypass API subscriptions. ### Groups and Subscriptions diff --git a/user-docs/TESTS.md b/user-docs/TESTS.md index b81434a0..2874f5b8 100644 --- a/user-docs/TESTS.md +++ b/user-docs/TESTS.md @@ -19,7 +19,8 @@ make local-e2e `make test-e2e` remains an alias for `make local-e2e`. No separately running Doorman server is needed. This sequential, fail-fast runner: -1. Tests the Python verification scripts and checks the pinned reference/600-entry ledger. +1. Tests the Python verification scripts and checks the pinned reference plus the + test and production-source coverage ledgers. 2. Runs Rust formatting, Clippy, and the full Cargo suite (including protocol, platform, persistence, and process-lifecycle tests). 3. Installs frontend dependencies and builds the dashboard. diff --git a/web-client/next.config.mjs b/web-client/next.config.mjs index 3187163b..139cb594 100644 --- a/web-client/next.config.mjs +++ b/web-client/next.config.mjs @@ -45,6 +45,10 @@ function buildRemotePatterns() { const nextConfig = { poweredByHeader: false, reactStrictMode: true, + // Gateway paths are proxied verbatim: some platform routes are registered + // with a trailing slash (e.g. /platform/tiers/) and the gateway redirects + // the unslashed form, so stripping the slash here would loop forever. + skipTrailingSlashRedirect: true, // Harden Next/Image to mitigate known issues around the optimization route images: { // Allow remote image hosts via env: NEXT_IMAGE_DOMAINS=cdn.example.com,images.example.org @@ -66,7 +70,10 @@ const nextConfig = { }, async rewrites() { return [ + // Keep a trailing slash: the gateway distinguishes /platform/tiers/. + { source: '/platform/:path*/', destination: `${gatewayTarget}/platform/:path*/` }, { source: '/platform/:path*', destination: `${gatewayTarget}/platform/:path*` }, + { source: '/api/:path*/', destination: `${gatewayTarget}/api/:path*/` }, { source: '/api/:path*', destination: `${gatewayTarget}/api/:path*` }, ] }, diff --git a/web-client/package-lock.json b/web-client/package-lock.json index ddfa2341..ce017dc9 100644 --- a/web-client/package-lock.json +++ b/web-client/package-lock.json @@ -21,7 +21,7 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@types/js-yaml": "^4.0.9", - "@types/node": "^22", + "@types/node": "20.17.6", "@types/react": "^19", "@types/react-dom": "^19", "dotenv-cli": "^11.0.0", @@ -29,7 +29,7 @@ "eslint-config-next": "15.5.25", "postcss": "8.5.28", "tailwindcss": "^3.4.1", - "typescript": "5.8.3" + "typescript": "5.8.2" }, "engines": { "node": ">=22 <23", @@ -1262,13 +1262,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.20.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.4.tgz", - "integrity": "sha512-zJRE40jpHtKqE/C4fgHrAKQLJuSpzEnP9ff9Y7YtoR3Wd2pwqzlekDeEuUQXjRd+QCYnVnNwuJYmhdk9XV8gvA==", + "version": "20.17.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz", + "integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~6.19.2" } }, "node_modules/@types/react": { @@ -2669,9 +2669,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz", - "integrity": "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==", + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", + "integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -6478,9 +6478,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6511,9 +6511,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true, "license": "MIT" }, diff --git a/web-client/package.json b/web-client/package.json index 4bab268f..1145e3fb 100644 --- a/web-client/package.json +++ b/web-client/package.json @@ -26,7 +26,7 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@types/js-yaml": "^4.0.9", - "@types/node": "^22", + "@types/node": "20.17.6", "@types/react": "^19", "@types/react-dom": "^19", "dotenv-cli": "^11.0.0", @@ -34,7 +34,7 @@ "eslint-config-next": "15.5.25", "postcss": "8.5.28", "tailwindcss": "^3.4.1", - "typescript": "5.8.3" + "typescript": "5.8.2" }, "overrides": { "next": { diff --git a/web-client/src/app/apis/[apiId]/page.tsx b/web-client/src/app/apis/[apiId]/page.tsx index 4a5c8e1e..2922291f 100644 --- a/web-client/src/app/apis/[apiId]/page.tsx +++ b/web-client/src/app/apis/[apiId]/page.tsx @@ -232,7 +232,7 @@ const ApiDetailPage = () => { } else { (async () => { try { - const data = await fetchJson(`${SERVER_URL}/platform/api/all?page=1&page_size=1000`) + const data = await fetchJson(`${SERVER_URL}/platform/api/all?page=1&page_size=200`) const list = Array.isArray(data) ? data : (data as any).apis || (data as any).response?.apis || [] const found = (list as any[]).find((a: any) => String(a.api_id) === String(apiId)) if (found) { @@ -590,7 +590,7 @@ const ApiDetailPage = () => { let version = (api as any)?.api_version as string | undefined if (!name || !version) { try { - const data = await fetchJson(`${SERVER_URL}/platform/api/all?page=1&page_size=1000`) + const data = await fetchJson(`${SERVER_URL}/platform/api/all?page=1&page_size=200`) const list = Array.isArray(data) ? data : (data as any).apis || (data as any).response?.apis || [] const found = (list as any[]).find((a: any) => String(a.api_id) === String(apiId)) if (found) { diff --git a/web-client/src/app/apis/import-swagger/page.tsx b/web-client/src/app/apis/import-swagger/page.tsx index b97e998c..159f1526 100644 --- a/web-client/src/app/apis/import-swagger/page.tsx +++ b/web-client/src/app/apis/import-swagger/page.tsx @@ -36,7 +36,7 @@ export default function ImportSwaggerPage() { useEffect(() => { const fetchApis = async () => { try { - const data = await getJson<any>(`${SERVER_URL}/platform/api/all?page=1&page_size=1000`) + const data = await getJson<any>(`${SERVER_URL}/platform/api/all?page=1&page_size=200`) const apisList = Array.isArray(data) ? data : (data.apis || data.response?.apis || []) setExistingApis(apisList.map((a: any) => ({ api_name: a.api_name || a.name, api_version: a.api_version || a.version }))) } catch (err) { diff --git a/web-client/src/app/logging/page.tsx b/web-client/src/app/logging/page.tsx index 22ed70d5..c1bd6457 100644 --- a/web-client/src/app/logging/page.tsx +++ b/web-client/src/app/logging/page.tsx @@ -1,6 +1,7 @@ 'use client' import React, { useState, useEffect, useCallback, useMemo } from 'react' +import { useRouter } from 'next/navigation' import Pagination from '@/components/Pagination' import { getCookie } from '@/utils/http' import { SERVER_URL } from '@/utils/config' @@ -102,7 +103,8 @@ const logsFromResponse = (data: any): Log[] => { type OverrideKey = string export default function LogsPage() { - const { permissions } = useAuth() + const { permissions, authResolved, logsEnabled } = useAuth() + const router = useRouter() const canExport = !!permissions?.export_logs const [logs, setLogs] = useState<Log[]>([]) const [groupedLogs, setGroupedLogs] = useState<GroupedLogs[]>([]) @@ -123,6 +125,10 @@ export default function LogsPage() { const [hidePlatformLogs, setHidePlatformLogs] = useState(false) const [autoRefreshInterval, setAutoRefreshInterval] = useState<number | null>(null) const [securityAuditActive, setSecurityAuditActive] = useState(false) + + useEffect(() => { + if (authResolved && !logsEnabled) router.replace('/dashboard') + }, [authResolved, logsEnabled, router]) const [filters, setFilters] = useState<FilterState>(() => { const now = new Date() const today = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') @@ -546,6 +552,8 @@ export default function LogsPage() { } } + if (authResolved && !logsEnabled) return null + return ( <ProtectedRoute requiredPermission="view_logs"> <Layout> diff --git a/web-client/src/app/test/page.tsx b/web-client/src/app/test/page.tsx index aa41fc0b..bfb67d25 100644 --- a/web-client/src/app/test/page.tsx +++ b/web-client/src/app/test/page.tsx @@ -37,7 +37,7 @@ export default function TestPage() { }, []) return ( - <div className="p-8"> + <main className="p-8"> <h1 className="text-2xl font-bold mb-4">Authentication Test</h1> <div className="mb-4"> @@ -89,6 +89,6 @@ export default function TestPage() { {cookies || 'No cookies found'} </pre> </div> - </div> + </main> ) } diff --git a/web-client/src/app/tiers/add/page.tsx b/web-client/src/app/tiers/add/page.tsx index 0f87538e..dbb29723 100644 --- a/web-client/src/app/tiers/add/page.tsx +++ b/web-client/src/app/tiers/add/page.tsx @@ -66,7 +66,7 @@ export default function AddTierPage() { } } - await postJson(`${SERVER_URL}/platform/tiers`, payload) + await postJson(`${SERVER_URL}/platform/tiers/`, payload) router.push('/tiers') } catch (err: any) { console.error('Failed to create tier:', err) diff --git a/web-client/src/app/tiers/page.tsx b/web-client/src/app/tiers/page.tsx index 4636ea4f..7f087340 100644 --- a/web-client/src/app/tiers/page.tsx +++ b/web-client/src/app/tiers/page.tsx @@ -74,9 +74,9 @@ export default function TiersPage() { params.append('enabled_only', 'true') } const query = params.toString() - const data = await getJson(`${SERVER_URL}/platform/tiers${query ? `?${query}` : ''}`) - // Ensure data is an array - let nextTiers = Array.isArray(data) ? data : [] + const data: any = await getJson(`${SERVER_URL}/platform/tiers/${query ? `?${query}` : ''}`) + // The listing is paginated: {tiers: [...], page, page_size, ...} + let nextTiers: Tier[] = Array.isArray(data) ? data : (Array.isArray(data?.tiers) ? data.tiers : []) if (status === 'disabled') { nextTiers = nextTiers.filter(t => !t.enabled) diff --git a/web-client/src/app/tls/page.tsx b/web-client/src/app/tls/page.tsx new file mode 100644 index 00000000..1aa71b2d --- /dev/null +++ b/web-client/src/app/tls/page.tsx @@ -0,0 +1,258 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' +import Layout from '@/components/Layout' +import { ProtectedRoute } from '@/components/ProtectedRoute' +import { useAuth } from '@/contexts/AuthContext' +import { getJson, postJson, putJson, delJson } from '@/utils/api' +import { SERVER_URL } from '@/utils/config' + +type ProfileKind = 'client_ca' | 'upstream' +type Profile = { id: string; kind: ProfileKind; source: 'file' | 'admin'; revision?: number; read_only: boolean } +type Listener = { source: 'file' | 'admin'; revision: number | null; has_override: boolean } +type ProfileForm = { + id: string + kind: ProfileKind + ca_pem: string + cert_pem: string + key_pem: string + server_name: string +} +type BindingForm = { + target: 'api' | 'endpoint' + setting: 'client_policy' | 'upstream_profile' + api_name: string + api_version: string + endpoint_method: string + endpoint_uri: string + mode: 'inherit' | 'off' | 'optional' | 'required' + ca_profile_id: string + dns_sans: string + uri_sans: string + upstream_profile_id: string +} + +const emptyProfile: ProfileForm = { + id: '', kind: 'client_ca', ca_pem: '', cert_pem: '', key_pem: '', server_name: '' +} +const emptyBinding: BindingForm = { + target: 'api', setting: 'client_policy', api_name: '', api_version: '', endpoint_method: 'GET', + endpoint_uri: '', mode: 'inherit', ca_profile_id: '', dns_sans: '', uri_sans: '', upstream_profile_id: '' +} + +const fieldClass = 'w-full rounded border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 dark:border-gray-600 dark:bg-gray-800 dark:text-white' +const buttonClass = 'rounded bg-primary-600 px-4 py-2 text-sm font-medium text-white disabled:opacity-50' + +export default function TlsPage() { + const { authResolved, permissions } = useAuth() + const [profiles, setProfiles] = useState<Profile[]>([]) + const [listener, setListener] = useState<Listener | null>(null) + const [profile, setProfile] = useState<ProfileForm>(emptyProfile) + const [binding, setBinding] = useState<BindingForm>(emptyBinding) + const [replacing, setReplacing] = useState(false) + const [listenerCert, setListenerCert] = useState('') + const [listenerKey, setListenerKey] = useState('') + const [busy, setBusy] = useState(false) + const [loading, setLoading] = useState(true) + const [error, setError] = useState('') + const [notice, setNotice] = useState('') + + const refresh = useCallback(async () => { + const [profileData, listenerData] = await Promise.all([ + getJson<{ profiles: Profile[] }>(`${SERVER_URL}/platform/tls/profiles`), + getJson<Listener>(`${SERVER_URL}/platform/tls/listener`) + ]) + setProfiles(profileData.profiles || []) + setListener(listenerData) + }, []) + + useEffect(() => { + if (!authResolved || !permissions?.manage_security) return + refresh().catch(err => setError(String(err?.message || err))).finally(() => setLoading(false)) + }, [authResolved, permissions?.manage_security, refresh]) + + const run = async (action: () => Promise<unknown>, message: string): Promise<boolean> => { + setBusy(true) + setError('') + setNotice('') + try { + await action() + await refresh() + setNotice(message) + return true + } catch (err: any) { + setError(String(err?.message || err)) + return false + } finally { + setBusy(false) + } + } + + const saveProfile = async (event: React.FormEvent) => { + event.preventDefault() + const id = profile.id.trim() + if (!id || (profile.kind === 'client_ca' && !profile.ca_pem.trim()) || + (!!profile.cert_pem !== !!profile.key_pem)) { + setError('Provide an ID, a CA bundle for client CA profiles, and both client certificate and key when using upstream mTLS.') + return + } + const body = { + id, kind: profile.kind, + ...(profile.ca_pem ? { ca_pem: profile.ca_pem } : {}), + ...(profile.kind === 'upstream' && profile.cert_pem ? { cert_pem: profile.cert_pem, key_pem: profile.key_pem } : {}), + ...(profile.kind === 'upstream' && profile.server_name.trim() ? { server_name: profile.server_name.trim() } : {}) + } + const saved = await run( + () => replacing + ? putJson(`${SERVER_URL}/platform/tls/profiles/${encodeURIComponent(id)}`, body) + : postJson(`${SERVER_URL}/platform/tls/profiles`, body), + replacing ? 'TLS profile replaced.' : 'TLS profile created.' + ) + if (saved) { + setProfile(emptyProfile) + setReplacing(false) + } + } + + const removeProfile = async (id: string) => { + if (!window.confirm(`Delete TLS profile ${id}?`)) return + await run(() => delJson(`${SERVER_URL}/platform/tls/profiles/${encodeURIComponent(id)}`), 'TLS profile deleted.') + } + + const saveListener = async (event: React.FormEvent) => { + event.preventDefault() + if (!listenerCert.trim() || !listenerKey.trim()) { + setError('Provide both the listener certificate and private key.') + return + } + const saved = await run( + () => putJson(`${SERVER_URL}/platform/tls/listener`, { cert_pem: listenerCert, key_pem: listenerKey }), + 'Listener certificate activated.' + ) + if (saved) { + setListenerCert('') + setListenerKey('') + } + } + + const resetListener = async () => { + if (!window.confirm('Remove the stored listener certificate and use the mounted certificate?')) return + await run(() => delJson(`${SERVER_URL}/platform/tls/listener`), 'Mounted listener certificate activated.') + } + + const saveBinding = async (event: React.FormEvent) => { + event.preventDefault() + const name = binding.api_name.trim() + const version = binding.api_version.trim() + const uri = binding.endpoint_uri.trim().replace(/^\/+/, '') + if (!name || !version || (binding.target === 'endpoint' && !uri)) { + setError('Enter the API name and version, and an endpoint URI when targeting an endpoint.') + return + } + const prefix = binding.target === 'api' ? 'api' : 'endpoint' + const path = binding.target === 'api' + ? `/platform/api/${encodeURIComponent(name)}/${encodeURIComponent(version)}` + : `/platform/endpoint/${encodeURIComponent(binding.endpoint_method)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}/${uri.split('/').map(encodeURIComponent).join('/')}` + let value: unknown + if (binding.setting === 'upstream_profile') { + value = binding.upstream_profile_id || null + } else if (binding.mode === 'inherit') { + value = null + } else if (binding.mode === 'off') { + value = { mode: 'off' } + } else { + const dns = binding.dns_sans.split(/[,\n]/).map(item => item.trim()).filter(Boolean) + const uris = binding.uri_sans.split(/[,\n]/).map(item => item.trim()).filter(Boolean) + if (!binding.ca_profile_id || (dns.length === 0 && uris.length === 0)) { + setError('Select a client CA and enter at least one permitted DNS or URI SAN.') + return + } + value = { mode: binding.mode, ca_profile_id: binding.ca_profile_id, allowed_dns_sans: dns, allowed_uri_sans: uris } + } + const field = binding.setting === 'client_policy' ? `${prefix}_client_tls_policy` : `${prefix}_upstream_tls_profile` + await run(() => putJson(path, { [field]: value }), 'TLS binding saved.') + } + + return ( + <ProtectedRoute requiredPermission="manage_security"> + <Layout> + <div className="mx-auto max-w-5xl space-y-6 p-6 text-gray-900 dark:text-white"> + <div> + <h1 className="text-2xl font-semibold">TLS certificates and profiles</h1> + <p className="mt-1 text-sm text-gray-600 dark:text-gray-300">Manage client trust, upstream TLS identities, and the native listener certificate.</p> + </div> + {error && <p role="alert" className="rounded bg-red-100 p-3 text-sm text-red-800">{error}</p>} + {notice && <p role="status" className="rounded bg-green-100 p-3 text-sm text-green-800">{notice}</p>} + + <section className="rounded border border-gray-200 p-5 dark:border-gray-700"> + <h2 className="text-lg font-semibold">Profiles</h2> + {loading ? <p className="mt-3 text-sm">Loading profiles…</p> : ( + <div className="mt-3 space-y-2"> + {profiles.length === 0 && <p className="text-sm text-gray-600 dark:text-gray-300">No TLS profiles configured.</p>} + {profiles.map(item => ( + <div key={`${item.source}:${item.id}`} className="flex flex-wrap items-center justify-between gap-3 rounded border border-gray-200 p-3 dark:border-gray-700"> + <div> + <strong>{item.id}</strong> + <span className="ml-2 text-sm text-gray-600 dark:text-gray-300">{item.kind === 'client_ca' ? 'Client CA' : 'Upstream'} · {item.source}{item.revision ? ` · revision ${item.revision}` : ''}</span> + </div> + {!item.read_only && <div className="flex gap-2"> + <button type="button" disabled={busy} className="text-sm text-primary-600" onClick={() => { setProfile({ ...emptyProfile, id: item.id, kind: item.kind }); setReplacing(true) }}>Replace</button> + <button type="button" disabled={busy} className="text-sm text-red-600" onClick={() => removeProfile(item.id)}>Delete</button> + </div>} + </div> + ))} + </div> + )} + </section> + + <form onSubmit={saveProfile} className="space-y-4 rounded border border-gray-200 p-5 dark:border-gray-700"> + <h2 className="text-lg font-semibold">{replacing ? `Replace ${profile.id}` : 'Add profile'}</h2> + {replacing && <p className="text-sm text-gray-600 dark:text-gray-300">Enter the complete certificate material again. Existing private material cannot be displayed.</p>} + <div className="grid gap-4 sm:grid-cols-2"> + <label className="text-sm">Profile ID<input required disabled={replacing} className={`mt-1 ${fieldClass}`} value={profile.id} onChange={event => setProfile({ ...profile, id: event.target.value })} /></label> + <label className="text-sm">Type<select disabled={replacing} className={`mt-1 ${fieldClass}`} value={profile.kind} onChange={event => setProfile({ ...profile, kind: event.target.value as ProfileKind })}><option value="client_ca">Client CA for inbound mTLS</option><option value="upstream">Upstream TLS profile</option></select></label> + </div> + <label className="block text-sm">CA bundle PEM<textarea className={`mt-1 h-32 font-mono ${fieldClass}`} value={profile.ca_pem} onChange={event => setProfile({ ...profile, ca_pem: event.target.value })} /></label> + {profile.kind === 'upstream' && <> + <label className="block text-sm">Client certificate PEM (optional)<textarea className={`mt-1 h-32 font-mono ${fieldClass}`} value={profile.cert_pem} onChange={event => setProfile({ ...profile, cert_pem: event.target.value })} /></label> + <label className="block text-sm">Client private key PEM (required with certificate)<textarea className={`mt-1 h-32 font-mono ${fieldClass}`} value={profile.key_pem} onChange={event => setProfile({ ...profile, key_pem: event.target.value })} /></label> + <label className="block text-sm">Server name override (gRPC only, optional)<input className={`mt-1 ${fieldClass}`} value={profile.server_name} onChange={event => setProfile({ ...profile, server_name: event.target.value })} /></label> + </>} + <div className="flex gap-3"><button disabled={busy} className={buttonClass}>{replacing ? 'Replace profile' : 'Create profile'}</button>{replacing && <button type="button" onClick={() => { setProfile(emptyProfile); setReplacing(false) }}>Cancel</button>}</div> + </form> + + <form onSubmit={saveBinding} className="space-y-4 rounded border border-gray-200 p-5 dark:border-gray-700"> + <div><h2 className="text-lg font-semibold">API and endpoint TLS bindings</h2><p className="text-sm text-gray-600 dark:text-gray-300">Set client certificate policy or choose an upstream TLS profile on an existing API or endpoint. API or endpoint management permission is also required.</p></div> + <div className="grid gap-4 sm:grid-cols-2"> + <label className="text-sm">Target<select className={`mt-1 ${fieldClass}`} value={binding.target} onChange={event => setBinding({ ...binding, target: event.target.value as BindingForm['target'] })}><option value="api">API</option><option value="endpoint">Endpoint</option></select></label> + <label className="text-sm">Setting<select className={`mt-1 ${fieldClass}`} value={binding.setting} onChange={event => setBinding({ ...binding, setting: event.target.value as BindingForm['setting'] })}><option value="client_policy">Inbound client certificate policy</option><option value="upstream_profile">Outbound TLS profile</option></select></label> + <label className="text-sm">API name<input required className={`mt-1 ${fieldClass}`} value={binding.api_name} onChange={event => setBinding({ ...binding, api_name: event.target.value })} /></label> + <label className="text-sm">API version<input required className={`mt-1 ${fieldClass}`} value={binding.api_version} onChange={event => setBinding({ ...binding, api_version: event.target.value })} /></label> + {binding.target === 'endpoint' && <> + <label className="text-sm">Endpoint method<input required className={`mt-1 ${fieldClass}`} value={binding.endpoint_method} onChange={event => setBinding({ ...binding, endpoint_method: event.target.value.toUpperCase() })} /></label> + <label className="text-sm">Endpoint URI<input required placeholder="/items" className={`mt-1 ${fieldClass}`} value={binding.endpoint_uri} onChange={event => setBinding({ ...binding, endpoint_uri: event.target.value })} /></label> + </>} + </div> + {binding.setting === 'client_policy' ? <> + <label className="block text-sm">Policy<select className={`mt-1 ${fieldClass}`} value={binding.mode} onChange={event => setBinding({ ...binding, mode: event.target.value as BindingForm['mode'] })}><option value="inherit">Clear override</option><option value="off">Off</option><option value="optional">Optional</option><option value="required">Required</option></select></label> + {(binding.mode === 'optional' || binding.mode === 'required') && <> + <label className="block text-sm">Client CA profile<select required className={`mt-1 ${fieldClass}`} value={binding.ca_profile_id} onChange={event => setBinding({ ...binding, ca_profile_id: event.target.value })}><option value="">Select a CA profile</option>{profiles.filter(item => item.kind === 'client_ca').map(item => <option key={item.id} value={item.id}>{item.id}</option>)}</select></label> + <label className="block text-sm">Allowed DNS SANs (comma or newline separated)<textarea className={`mt-1 h-20 ${fieldClass}`} value={binding.dns_sans} onChange={event => setBinding({ ...binding, dns_sans: event.target.value })} /></label> + <label className="block text-sm">Allowed URI SANs (comma or newline separated)<textarea className={`mt-1 h-20 ${fieldClass}`} value={binding.uri_sans} onChange={event => setBinding({ ...binding, uri_sans: event.target.value })} /></label> + </>} + </> : <label className="block text-sm">Upstream TLS profile<select className={`mt-1 ${fieldClass}`} value={binding.upstream_profile_id} onChange={event => setBinding({ ...binding, upstream_profile_id: event.target.value })}><option value="">Clear binding</option>{profiles.filter(item => item.kind === 'upstream').map(item => <option key={item.id} value={item.id}>{item.id}</option>)}</select></label>} + <button disabled={busy} className={buttonClass}>Save binding</button> + </form> + + <form onSubmit={saveListener} className="space-y-4 rounded border border-gray-200 p-5 dark:border-gray-700"> + <div><h2 className="text-lg font-semibold">Native listener certificate</h2><p className="text-sm text-gray-600 dark:text-gray-300">Active source: {listener?.source || 'unknown'}{listener?.revision ? ` · revision ${listener.revision}` : ''}. Requires native TLS mode.</p></div> + <label className="block text-sm">Certificate chain PEM<textarea className={`mt-1 h-32 font-mono ${fieldClass}`} value={listenerCert} onChange={event => setListenerCert(event.target.value)} /></label> + <label className="block text-sm">Private key PEM<textarea className={`mt-1 h-32 font-mono ${fieldClass}`} value={listenerKey} onChange={event => setListenerKey(event.target.value)} /></label> + <button disabled={busy} className={buttonClass}>Activate certificate</button> + {listener?.has_override && <button type="button" disabled={busy} className="ml-3 text-sm text-primary-600" onClick={resetListener}>Use mounted certificate</button>} + </form> + </div> + </Layout> + </ProtectedRoute> + ) +} diff --git a/web-client/src/app/users/[username]/page.tsx b/web-client/src/app/users/[username]/page.tsx index e87eb48b..1e1af01d 100644 --- a/web-client/src/app/users/[username]/page.tsx +++ b/web-client/src/app/users/[username]/page.tsx @@ -108,7 +108,7 @@ const UserDetailPage = () => { // Fetch available tiers const fetchTiers = async () => { try { - const tiers = await fetchJson(`${SERVER_URL}/platform/tiers`) + const tiers = await fetchJson(`${SERVER_URL}/platform/tiers/`) // Ensure tiers is an array setAvailableTiers(Array.isArray(tiers) ? tiers : []) } catch (err) { diff --git a/web-client/src/components/Layout.tsx b/web-client/src/components/Layout.tsx index d36b2c96..498fde82 100644 --- a/web-client/src/components/Layout.tsx +++ b/web-client/src/components/Layout.tsx @@ -10,7 +10,7 @@ interface LayoutProps { children: React.ReactNode } interface MenuItem { label: string; href: string; permission?: string } const menuItems: MenuItem[] = [ - { label: 'Dashboard', href: '/dashboard' }, { label: 'Analytics', href: '/analytics', permission: 'view_analytics' }, { label: 'Logs', href: '/logging', permission: 'view_logs' }, { label: 'APIs', href: '/apis', permission: 'manage_apis' }, { label: 'Documentation', href: '/documentation' }, { label: 'Users', href: '/users', permission: 'manage_users' }, { label: 'Groups', href: '/groups', permission: 'manage_groups' }, { label: 'Roles', href: '/roles', permission: 'manage_roles' }, { label: 'Subscriptions', href: '/authorizations', permission: 'manage_subscriptions' }, { label: 'Credits', href: '/credits', permission: 'manage_credits' }, { label: 'Tiers', href: '/tiers', permission: 'manage_tiers' }, { label: 'Routings', href: '/routings', permission: 'manage_routings' }, { label: 'Auth Control', href: '/auth-admin', permission: 'manage_auth' }, { label: 'Security', href: '/security', permission: 'manage_security' }, { label: 'Tools', href: '/tools', permission: 'manage_security' }, { label: 'Import / Export', href: '/import-export', permission: 'manage_gateway' }, { label: 'Settings', href: '/settings' }, + { label: 'Dashboard', href: '/dashboard' }, { label: 'Analytics', href: '/analytics', permission: 'view_analytics' }, { label: 'Logs', href: '/logging', permission: 'view_logs' }, { label: 'APIs', href: '/apis', permission: 'manage_apis' }, { label: 'Documentation', href: '/documentation' }, { label: 'Users', href: '/users', permission: 'manage_users' }, { label: 'Groups', href: '/groups', permission: 'manage_groups' }, { label: 'Roles', href: '/roles', permission: 'manage_roles' }, { label: 'Subscriptions', href: '/authorizations', permission: 'manage_subscriptions' }, { label: 'Credits', href: '/credits', permission: 'manage_credits' }, { label: 'Tiers', href: '/tiers', permission: 'manage_tiers' }, { label: 'Routings', href: '/routings', permission: 'manage_routings' }, { label: 'Auth Control', href: '/auth-admin', permission: 'manage_auth' }, { label: 'Security', href: '/security', permission: 'manage_security' }, { label: 'TLS', href: '/tls', permission: 'manage_security' }, { label: 'Tools', href: '/tools', permission: 'manage_security' }, { label: 'Import / Export', href: '/import-export', permission: 'manage_gateway' }, { label: 'Settings', href: '/settings' }, ] export default function Layout({ children }: LayoutProps) { @@ -18,7 +18,7 @@ export default function Layout({ children }: LayoutProps) { const [sidebarOpen, setSidebarOpen] = useState(false) const pathname = usePathname() const router = useRouter() - const { isAuthenticated, authResolved, hasUIAccess, user, permissions, logout } = useAuth() + const { isAuthenticated, authResolved, hasUIAccess, user, permissions, logsEnabled, logout } = useAuth() useEffect(() => { if (pathname === '/login' || pathname === '/403') return @@ -36,7 +36,10 @@ export default function Layout({ children }: LayoutProps) { useEffect(() => { document.documentElement.classList.remove('dark') }, []) - const filteredMenuItems = menuItems.filter(item => !item.permission || permissions?.[item.permission]) + const filteredMenuItems = menuItems.filter(item => { + if (item.href === '/logging' && !logsEnabled) return false + return !item.permission || permissions?.[item.permission] + }) const isActive = (href: string) => pathname === href || (href !== '/dashboard' && pathname.startsWith(`${href}/`)) if (!authResolved && pathname !== '/login' && pathname !== '/403') { diff --git a/web-client/src/contexts/AuthContext.tsx b/web-client/src/contexts/AuthContext.tsx index b9c7251c..b72b6ea7 100644 --- a/web-client/src/contexts/AuthContext.tsx +++ b/web-client/src/contexts/AuthContext.tsx @@ -24,6 +24,7 @@ interface AuthContextType { hasUIAccess: boolean user: { username: string; role: string } | null permissions: any + logsEnabled: boolean canAccessPage: (permission: string) => boolean logout: () => void checkAuth: () => Promise<void> @@ -38,7 +39,8 @@ export function AuthProvider({ children }: { children: ReactNode }) { authResolved: false, hasUIAccess: false, user: null as { username: string; role: string } | null, - permissions: null as any + permissions: null as any, + logsEnabled: false }) const isAuthenticatedRef = useRef(false) const router = useRouter() @@ -47,6 +49,8 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (DEBUG) console.log('=== AUTH CONTEXT DEBUG ===') try { await fetchJson(`${SERVER_URL}/platform/authorization/status`) + const features: any = await fetchJson(`${SERVER_URL}/api/features`).catch(() => ({ logs_enabled: true })) + const logsEnabled = (features?.response?.logs_enabled ?? features?.logs_enabled) !== false let user = null as any let permissions: any = null @@ -65,7 +69,8 @@ export function AuthProvider({ children }: { children: ReactNode }) { authResolved: true, hasUIAccess: !!(user && user.ui_access === true), user, - permissions + permissions, + logsEnabled }) } catch (error) { if (DEBUG) console.warn('AuthContext - Not authenticated or status check failed:', error) @@ -74,7 +79,8 @@ export function AuthProvider({ children }: { children: ReactNode }) { authResolved: true, hasUIAccess: false, user: null, - permissions: null + permissions: null, + logsEnabled: false }) } } @@ -107,7 +113,8 @@ export function AuthProvider({ children }: { children: ReactNode }) { authResolved: true, hasUIAccess: false, user: null, - permissions: null + permissions: null, + logsEnabled: false }) router.push('/login') } @@ -156,6 +163,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { hasUIAccess: authState.hasUIAccess, user: authState.user, permissions: authState.permissions, + logsEnabled: authState.logsEnabled, canAccessPage: canAccessPagePermission, logout, checkAuth,