- Move ASPICE docs to chanoraapp/docs submodule at docs/ - Move development docs to dev-docs/ (superpowers, offline-knowledge, impl-mapping) - Add AGENTS.md with project conventions for AI agents - Add impl-mapping.md (SAD component → source file mapping) - Archive completed plans to dev-docs/superpowers/plans/_archived/ - Remove AGENTS.md from .gitignore (now tracked)
117 lines
4.6 KiB
Markdown
117 lines
4.6 KiB
Markdown
# Chanora Server Prefetch Crate Design
|
|
|
|
Date: 2026-05-28
|
|
|
|
## Goal
|
|
|
|
Move server-resolution prefetch policy out of `chanora_core` into a focused Rust crate named `chanora_prefetch`, without changing connection behavior, Flutter APIs, or protocol dialing semantics.
|
|
|
|
## Non-Goals
|
|
|
|
- Do not change resolver behavior or DNS/SRV/TSDNS ordering.
|
|
- Do not change `ConnectConfig.resolved_address` semantics in `chanora_protocol`.
|
|
- Do not expose prefetch state in the UI.
|
|
- Do not prefetch bookmarks or additional hosts.
|
|
- Do not persist prefetched addresses.
|
|
|
|
## Architecture
|
|
|
|
Add a workspace member at `crates/chanora_prefetch`.
|
|
|
|
Responsibilities:
|
|
|
|
- Normalize server host keys by trimming and lowercasing.
|
|
- Track one active prefetch generation.
|
|
- Store at most one successful prefetched socket address.
|
|
- Reject stale async completions by generation.
|
|
- Return a prefetched address only for an exact normalized host match.
|
|
- Enforce the 2-minute freshness TTL.
|
|
- Resolve server addresses by calling `chanora_resolver::ChanoraResolver::resolve_client_address`.
|
|
- Log prefetch start, success, miss/stale, and failure diagnostics.
|
|
|
|
Dependencies:
|
|
|
|
- `chanora_resolver` for actual server address resolution.
|
|
- `tokio` for `Mutex` and spawned prefetch tasks.
|
|
- `tracing` for diagnostics.
|
|
- `thiserror` for a narrow `ServerPrefetchError` public error type.
|
|
|
|
## Public API
|
|
|
|
The crate exposes this small async owner type:
|
|
|
|
```rust
|
|
pub struct ServerPrefetcher { ... }
|
|
|
|
impl ServerPrefetcher {
|
|
pub fn new() -> Self;
|
|
pub async fn prefetch(&self, host: String) -> Result<(), ServerPrefetchError>;
|
|
pub async fn fresh_match(&self, host: &str) -> Option<std::net::SocketAddr>;
|
|
}
|
|
```
|
|
|
|
`prefetch` returns after scheduling work, preserving the current invisible, non-blocking behavior. Empty normalized hosts are ignored successfully. Resolution failures are stored only as diagnostics and do not affect connect semantics.
|
|
|
|
## Core Integration
|
|
|
|
`chanora_core` replaces its private prefetch cache fields and helpers with `ServerPrefetcher`.
|
|
|
|
Core remains the trust boundary for connection config:
|
|
|
|
- It clears any caller-provided `ConnectConfig.resolved_address` before lookup.
|
|
- It asks `ServerPrefetcher::fresh_match` for the current host.
|
|
- It sets `dial_cfg.resolved_address` only from a fresh exact cache hit.
|
|
- It stores supervisor/reconnect config with `resolved_address: None`.
|
|
|
|
The Flutter bridge keeps calling the same core API, `prefetch_server_resolution(host)`. No Dart API change is intended.
|
|
|
|
## Data Flow
|
|
|
|
1. Flutter host editing schedules `ChanoraSession::prefetch_server_resolution(host)`.
|
|
2. Core delegates to `ServerPrefetcher::prefetch(host)`.
|
|
3. The prefetcher normalizes the host, increments generation, and spawns resolver work.
|
|
4. On success, the prefetcher stores the resolved socket address if the generation is still current.
|
|
5. On connect, core prepares `(stored_cfg, dial_cfg)`.
|
|
6. Core clears untrusted `resolved_address`, asks the prefetcher for a fresh exact match, and applies the result only to `dial_cfg`.
|
|
7. Protocol uses `dial_cfg.resolved_address` if present; otherwise it resolves normally.
|
|
|
|
## Error Handling
|
|
|
|
- Prefetch failures remain invisible to users.
|
|
- Prefetch failures are logged through `tracing`.
|
|
- If prefetch misses, is stale, or fails, connect falls back to normal protocol resolution.
|
|
- A caller-supplied `resolved_address` is never trusted by core.
|
|
|
|
## Testing
|
|
|
|
Move cache-policy tests from `chanora_core` into `chanora_prefetch`:
|
|
|
|
- fresh exact match returns the socket address.
|
|
- stale entries are ignored.
|
|
- different hosts are ignored.
|
|
- stale generation completions are ignored.
|
|
- blank normalized hosts return `Ok(())` and do not update generation or spawn resolver work.
|
|
|
|
Keep core tests for connection trust-boundary behavior:
|
|
|
|
- untrusted `resolved_address` is cleared on cache miss.
|
|
- prepared stored/supervisor config has `resolved_address: None`.
|
|
- prepared dial config can receive a fresh prefetched address.
|
|
|
|
Run at minimum:
|
|
|
|
- `cargo test -p chanora_prefetch --lib`
|
|
- `cargo test -p chanora_core --lib`
|
|
- `cargo test -p chanora_protocol --lib`
|
|
|
|
For final confidence, rerun the Android server connect smoke path that verifies prefetch logs and connected UI.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- Workspace builds with the new crate member.
|
|
- `chanora_core` no longer owns the prefetch cache implementation.
|
|
- `chanora_prefetch` owns prefetch normalization, TTL, generation, storage, and resolver-backed warming.
|
|
- Public Flutter and Rust protocol behavior is unchanged.
|
|
- Existing server connect and reconnect safety tests pass.
|
|
- Android connect still reaches the connected server UI and does not get stuck in `Connecting` or `Synchronizing`.
|