# RustProxy 0.26.1 implementation note ## Scope Version 0.26.1 is a focused follow-up to the persistent protocol diagnostics and Stored Logs explorer introduced in 0.26.0. It does not change SQLite schema 8 or the saved proxy configuration format. The release has two goals: 1. Attribute persistent `protocol_error` records to a configured vhost when that attribution is safe and deterministic. 2. Make the Stored Logs explorer practical on installations with many vhosts and useful for protocol diagnostics. Live Logs remains a separate bounded in-memory request view. Low-level `protocol_error` records are persistence-only and must not be published to its history/broadcast path. ## One shared vhost-resolution function All protocol adapters use the same resolver; TLS, HTTP/1, HTTP/2 and HTTP/3 must not implement independent hostname matching. ```rust resolve_logging_vhost( local, tls, routed_vhost, authority, host, sni, ) -> Option ``` Resolution priority is: 1. a vhost already selected by normal routing; 2. HTTP `:authority`; 3. HTTP `Host`; 4. TLS/QUIC SNI; 5. no match, which means `_unmatched.jsonl` in per-vhost file mode. The helper normalizes authority/Host/SNI with the existing host parser and delegates selection to the same `Snapshot::frontend` exact/wildcard and listener-binding logic used by normal request routing. It returns the configured internal vhost ID and `matched_by`; client input is never turned directly into a filename. ## Persistent protocol records A protocol record keeps the compatibility fields from 0.26.0 and also exposes canonical diagnostics when available: - `event = protocol_error`; - `reason` and compatibility `protocol_error`; - `error` and compatibility `error_detail`; - `client_ip` and `local_addr`; - `transport` and `http_version`; - `vhost` when safely resolved; - `sni` and compatibility `tls_sni`; - `authority`; - `matched_by = router | authority | host | sni | none`. Diagnostic identity strings are control-character stripped and length-bounded before persistence. Existing secret redaction and error-detail limits remain in force. In per-vhost file mode, a resolved record uses the existing configured vhost sink. Invalid internal vhost identifiers fall back to `_unmatched` instead of being sanitized into a new filename. In combined-file mode, sink behavior is unchanged. ## Protocol integration TLS reads SNI from ClientHello before completing the handshake. A later handshake failure/timeout carries that SNI into the shared resolver. If ClientHello itself cannot be parsed, attribution remains unavailable and the record is unmatched. HTTP/2 retains the TLS SNI for post-handshake connection/drain diagnostics. Request paths that have an HTTP request object can additionally provide authority/Host. HTTP/3 obtains SNI from Quinn/Rustls handshake data after a successful QUIC handshake and uses it for later request, stream and connection protocol failures. Failures that happen before usable handshake identity exists remain unmatched. No protocol path infers a vhost solely from a shared listener or local port. ## Stored Logs explorer changes The `/logs` workspace now: - highlights the selected log row; - expands Record details inline directly below that row; - removes the separate details panel below the table; - keeps the table header sticky while the log list scrolls; - lets each administrator choose visible Stored Logs columns and persists the choice in existing account preferences; - offers protocol columns for transport, match source, SNI and authority; - keeps the configured-file inventory collapsed by default; - canonicalizes fields through the active structured-log field mapping so `client_ip` remains visible when the JSON output key was renamed; - searches record values case-insensitively and includes `reason`, `error`, `sni`, `authority` and other stored values; - refreshes keyword results with a short debounce while typing; - retains the existing bounded source/byte/line/page limits and reports truncation when an `all` query contains more sources than can be scanned safely. ## Security invariants The browser still accepts only opaque configured source IDs; it never accepts arbitrary filesystem paths. File reads remain regular-file-only and retain Unix `O_NOFOLLOW` protection. SNI, Host and authority are lookup inputs only. They cannot select a filesystem path directly. Only a configured vhost resolved through the existing routing model can select a per-vhost log sink. ## Regression coverage Source tests cover: - exact and wildcard hostname precedence using the shared resolver; - router priority over authority/Host/SNI; - Host with port and case-insensitive normalization; - unknown and missing SNI returning no vhost; - mapped `client_ip` normalization in Stored Logs; - keyword matching against protocol diagnostic values; - safe per-vhost filename fallback; - bounded protocol identity fields; - protocol errors remaining outside Live Logs; - preference validation for selectable Stored Logs columns. A Rust-enabled release host must still run `cargo fmt --check`, Clippy with warnings denied, all-target/all-feature tests and the complete `dev.sh` release build before the package is considered compiler-verified.