chore: restore product scaffold to rollback baseline

This commit is contained in:
Edison Jwa
2026-05-29 14:02:04 +09:00
parent 2896f14ec9
commit fe6e07353e
434 changed files with 27278 additions and 63230 deletions
@@ -0,0 +1,689 @@
# Chanora Server Prefetch Crate Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Move server-resolution prefetch policy from `chanora_core` into a focused crate named `chanora_server_prefetch` without changing Flutter APIs, resolver behavior, protocol dialing behavior, or Android connect UX.
**Architecture:** Add `crates/chanora_server_prefetch` as a workspace member. The new crate owns normalization, single-entry TTL cache, generation rejection, resolver-backed async warming, and fresh exact-match lookup. `chanora_core` keeps the trust boundary for `ConnectConfig.resolved_address`, using the prefetcher only to populate one-time dial config while keeping stored/reconnect config sanitized.
**Tech Stack:** Rust workspace, `tokio`, `tracing`, `thiserror`, `chanora_resolver`, `chanora_core`, `chanora_protocol`, Flutter Android smoke via ADB.
---
## File Structure
- Create `crates/chanora_server_prefetch/Cargo.toml`: package metadata and dependencies.
- Create `crates/chanora_server_prefetch/src/lib.rs`: public `ServerPrefetcher`, public `ServerPrefetchError`, internal cache entry/state, resolver-backed prefetch logic, and unit tests.
- Modify `Cargo.toml`: add `crates/chanora_server_prefetch` to workspace members and update the workspace layout comment.
- Modify `core/chanora_core/Cargo.toml`: replace the direct `chanora_resolver` dependency with `chanora_server_prefetch`.
- Modify `core/chanora_core/src/lib.rs`: remove private prefetch cache/resolver helpers, add `ServerPrefetcher`, delegate `prefetch_server_resolution`, and keep connect config sanitization tests.
- Modify `Cargo.lock`: generated by `cargo test`/`cargo check` after adding the crate.
---
### Task 1: Add `chanora_server_prefetch` Crate With Cache Policy Tests
**Files:**
- Modify: `Cargo.toml`
- Create: `crates/chanora_server_prefetch/Cargo.toml`
- Create: `crates/chanora_server_prefetch/src/lib.rs`
- [ ] **Step 1: Add the workspace member and package files**
In top-level `Cargo.toml`, update the layout comment and members:
```toml
# crates/chanora_server_prefetch — server-resolution prefetch cache/policy
# crates/chanora_bridge/ — Flutter/Rust typed DTOs + glue
```
```toml
members = [
"core/chanora_core",
"crates/chanora_protocol",
"crates/chanora_state",
"crates/chanora_audio",
"crates/chanora_storage",
"crates/chanora_diagnostics",
"crates/chanora_server_prefetch",
"crates/chanora_bridge",
"crates/chanora_resolver",
]
```
Create `crates/chanora_server_prefetch/Cargo.toml`:
```toml
[package]
name = "chanora_server_prefetch"
description = "Chanora — server-address prefetch cache and policy built on chanora_resolver."
version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
publish.workspace = true
[dependencies]
chanora_resolver = { path = "../chanora_resolver" }
thiserror.workspace = true
tracing.workspace = true
tokio = { version = "1", features = ["sync", "rt", "macros"] }
[features]
test-support = []
```
Create `crates/chanora_server_prefetch/src/lib.rs` with the initial crate implementation and tests:
```rust
//! Server-address prefetch cache and policy for Chanora.
//!
//! This crate owns speculative server-resolution warming. It does not
//! decide whether a connection should use a prefetched address; callers
//! must still apply their own trust boundary before dialing.
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use thiserror::Error;
use tokio::sync::Mutex;
use tracing::{info, warn};
const SERVER_PREFETCH_TTL: Duration = Duration::from_secs(120);
#[derive(Debug, Error)]
pub enum ServerPrefetchError {
#[error("resolver initialization failed: {0}")]
ResolverInit(String),
}
#[derive(Debug, Clone)]
struct ServerPrefetchEntry {
normalized_host: String,
resolved_address: SocketAddr,
completed_at: Instant,
generation: u64,
}
#[derive(Debug, Default)]
struct ServerPrefetchCache {
latest_generation: u64,
entry: Option<ServerPrefetchEntry>,
last_failure: Option<String>,
}
impl ServerPrefetchCache {
fn begin(&mut self, host: &str) -> u64 {
if normalize_host(host).is_empty() {
return self.latest_generation;
}
self.latest_generation = self.latest_generation.saturating_add(1);
self.last_failure = None;
self.latest_generation
}
fn store_success(
&mut self,
generation: u64,
host: &str,
resolved_address: SocketAddr,
completed_at: Instant,
) {
if generation != self.latest_generation {
return;
}
self.entry = Some(ServerPrefetchEntry {
normalized_host: normalize_host(host),
resolved_address,
completed_at,
generation,
});
self.last_failure = None;
}
fn store_failure(&mut self, generation: u64, error: String) {
if generation != self.latest_generation {
return;
}
self.last_failure = Some(error);
}
fn fresh_match(&self, host: &str, now: Instant) -> Option<SocketAddr> {
let normalized = normalize_host(host);
let entry = self.entry.as_ref()?;
if entry.normalized_host != normalized {
return None;
}
if entry.generation != self.latest_generation {
return None;
}
if now.duration_since(entry.completed_at) > SERVER_PREFETCH_TTL {
return None;
}
Some(entry.resolved_address)
}
}
#[derive(Debug, Clone, Default)]
pub struct ServerPrefetcher {
cache: Arc<Mutex<ServerPrefetchCache>>,
}
impl ServerPrefetcher {
pub fn new() -> Self {
Self::default()
}
pub async fn prefetch(&self, host: String) -> Result<(), ServerPrefetchError> {
let normalized = normalize_host(&host);
if normalized.is_empty() {
return Ok(());
}
let generation = {
let mut cache = self.cache.lock().await;
cache.begin(&normalized)
};
let cache = self.cache.clone();
tokio::spawn(async move {
info!(target: "chanora_server_prefetch", host = %normalized, "resolution prefetch started");
let result = resolve_socket(&normalized).await;
let mut guard = cache.lock().await;
match result {
Ok(addr) => {
info!(
target: "chanora_server_prefetch",
host = %normalized,
resolved = %addr,
"resolution prefetch result"
);
guard.store_success(generation, &normalized, addr, Instant::now());
}
Err(err) => {
warn!(
target: "chanora_server_prefetch",
host = %normalized,
error = %err,
"resolution prefetch failed"
);
guard.store_failure(generation, err.to_string());
}
}
});
Ok(())
}
pub async fn fresh_match(&self, host: &str) -> Option<SocketAddr> {
let resolved = {
let cache = self.cache.lock().await;
cache.fresh_match(host, Instant::now())
};
match resolved {
Some(addr) => {
info!(
target: "chanora_server_prefetch",
host = %host,
resolved = %addr,
"connect using prefetched resolution"
);
Some(addr)
}
None => {
info!(target: "chanora_server_prefetch", host = %host, "connect prefetch miss or stale");
None
}
}
}
#[cfg(any(test, feature = "test-support"))]
pub async fn begin_for_test(&self, host: &str) -> u64 {
let mut cache = self.cache.lock().await;
cache.begin(host)
}
#[cfg(any(test, feature = "test-support"))]
pub async fn store_success_for_test(
&self,
generation: u64,
host: &str,
resolved_address: SocketAddr,
completed_at: Instant,
) {
let mut cache = self.cache.lock().await;
cache.store_success(generation, host, resolved_address, completed_at);
}
#[cfg(any(test, feature = "test-support"))]
pub async fn latest_generation_for_test(&self) -> u64 {
let cache = self.cache.lock().await;
cache.latest_generation
}
}
fn normalize_host(host: &str) -> String {
host.trim().to_lowercase()
}
async fn resolve_socket(host: &str) -> Result<SocketAddr, ServerPrefetchError> {
let resolver = chanora_resolver::ChanoraResolver::new()
.map_err(|err| ServerPrefetchError::ResolverInit(err.to_string()))?;
let resolved = resolver
.resolve_client_address(host)
.await
.map_err(|err| ServerPrefetchError::ResolverInit(err.to_string()))?;
Ok(resolved.connection_addr())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn fresh_exact_match_returns_socket_address() {
let prefetcher = ServerPrefetcher::new();
let generation = prefetcher.begin_for_test(" Example.COM ").await;
let addr = "127.0.0.1:9987".parse().unwrap();
prefetcher
.store_success_for_test(generation, "example.com", addr, Instant::now())
.await;
assert_eq!(prefetcher.fresh_match("example.com").await, Some(addr));
assert_eq!(prefetcher.fresh_match(" EXAMPLE.com ").await, Some(addr));
}
#[tokio::test]
async fn stale_entries_are_ignored() {
let prefetcher = ServerPrefetcher::new();
let generation = prefetcher.begin_for_test("example.com").await;
let addr = "127.0.0.1:9987".parse().unwrap();
prefetcher
.store_success_for_test(
generation,
"example.com",
addr,
Instant::now() - SERVER_PREFETCH_TTL - Duration::from_secs(1),
)
.await;
assert_eq!(prefetcher.fresh_match("example.com").await, None);
}
#[tokio::test]
async fn different_hosts_are_ignored() {
let prefetcher = ServerPrefetcher::new();
let generation = prefetcher.begin_for_test("example.com").await;
let addr = "127.0.0.1:9987".parse().unwrap();
prefetcher
.store_success_for_test(generation, "example.com", addr, Instant::now())
.await;
assert_eq!(prefetcher.fresh_match("other.example.com").await, None);
}
#[tokio::test]
async fn stale_generation_completions_are_ignored() {
let prefetcher = ServerPrefetcher::new();
let old_generation = prefetcher.begin_for_test("old.example.com").await;
let _new_generation = prefetcher.begin_for_test("new.example.com").await;
let old_addr = "127.0.0.1:9987".parse().unwrap();
prefetcher
.store_success_for_test(old_generation, "old.example.com", old_addr, Instant::now())
.await;
assert_eq!(prefetcher.fresh_match("old.example.com").await, None);
}
#[tokio::test]
async fn blank_hosts_do_not_update_generation() {
let prefetcher = ServerPrefetcher::new();
let before = prefetcher.latest_generation_for_test().await;
prefetcher.prefetch(" ".to_string()).await.unwrap();
assert_eq!(prefetcher.latest_generation_for_test().await, before);
}
}
```
- [ ] **Step 2: Run new crate tests**
Run: `cargo test -p chanora_server_prefetch --lib`
Expected: the new crate compiles and all 5 tests pass.
- [ ] **Step 3: Commit Task 1**
```bash
git add Cargo.toml Cargo.lock crates/chanora_server_prefetch
git commit -m "feat(prefetch): add server prefetch crate"
```
---
### Task 2: Wire Core to `ServerPrefetcher`
**Files:**
- Modify: `core/chanora_core/Cargo.toml`
- Modify: `core/chanora_core/src/lib.rs`
- [ ] **Step 1: Replace the core dependency**
In `core/chanora_core/Cargo.toml`, replace:
```toml
chanora_resolver = { path = "../../crates/chanora_resolver" }
```
with:
```toml
chanora_server_prefetch = { path = "../../crates/chanora_server_prefetch" }
```
- [ ] **Step 2: Remove private prefetch cache implementation from core**
In `core/chanora_core/src/lib.rs`, remove these private items:
```rust
const RESOLUTION_PREFETCH_TTL: Duration = Duration::from_secs(120);
#[derive(Debug, Clone)]
struct ResolutionPrefetchEntry { ... }
#[derive(Debug, Default)]
struct ResolutionPrefetchCache { ... }
impl ResolutionPrefetchCache { ... }
fn normalize_prefetch_host(host: &str) -> String { ... }
async fn resolve_prefetch_socket(host: &str) -> Result<std::net::SocketAddr, CoreError> { ... }
```
Add this import near the other crate imports:
```rust
use chanora_server_prefetch::ServerPrefetcher;
```
- [ ] **Step 3: Replace the session field and constructor initialization**
Change the `ChanoraSession` field from:
```rust
resolution_prefetch: Arc<Mutex<ResolutionPrefetchCache>>,
```
to:
```rust
server_prefetch: ServerPrefetcher,
```
Change the constructor initialization from:
```rust
resolution_prefetch: Arc::new(Mutex::new(ResolutionPrefetchCache::default())),
```
to:
```rust
server_prefetch: ServerPrefetcher::new(),
```
- [ ] **Step 4: Delegate prefetch and fresh-match lookup**
Replace `prefetch_server_resolution` with:
```rust
pub async fn prefetch_server_resolution(&self, host: String) -> Result<(), CoreError> {
self.server_prefetch
.prefetch(host)
.await
.map_err(|err| CoreError::Protocol(chanora_protocol::ProtocolError::DnsFailed {
host: "prefetch".to_string(),
reason: err.to_string(),
}))
}
```
Replace `apply_prefetched_resolution` with:
```rust
async fn apply_prefetched_resolution(&self, cfg: &mut ConnectConfig) {
cfg.resolved_address = None;
let host = cfg.address.trim();
if host.is_empty() {
return;
}
if let Some(addr) = self.server_prefetch.fresh_match(host).await {
cfg.resolved_address = Some(addr);
}
}
```
Keep `prepare_connect_configs` unchanged except that it calls the updated `apply_prefetched_resolution`.
- [ ] **Step 5: Update core tests to use the prefetcher seam**
Keep these existing core tests:
```rust
session_clears_untrusted_prefetched_resolution_on_cache_miss
connect_config_without_prefetched_resolution_clears_only_resolved_address
connect_config_preparation_keeps_prefetched_address_out_of_stored_config
```
Replace any direct access to `session.resolution_prefetch` with calls to public test-support methods on `ServerPrefetcher`. In `crates/chanora_server_prefetch/src/lib.rs`, keep these methods gated with `#[cfg(any(test, feature = "test-support"))]`:
```rust
#[cfg(any(test, feature = "test-support"))]
pub async fn store_success_for_test(
&self,
generation: u64,
host: &str,
resolved_address: SocketAddr,
completed_at: Instant,
)
```
and:
```rust
#[cfg(any(test, feature = "test-support"))]
pub async fn begin_for_test(&self, host: &str) -> u64
```
Enable the feature for `chanora_core` tests by adding this dev-dependency in `core/chanora_core/Cargo.toml`:
```toml
[dev-dependencies]
chanora_server_prefetch = { path = "../../crates/chanora_server_prefetch", features = ["test-support"] }
```
Then core test setup should look like:
```rust
let generation = session.server_prefetch.begin_for_test("example.com").await;
session
.server_prefetch
.store_success_for_test(generation, "example.com", cached, std::time::Instant::now())
.await;
```
- [ ] **Step 6: Run core tests and fix compile errors**
Run: `cargo test -p chanora_core --lib`
Expected: all core lib tests pass.
- [ ] **Step 7: Commit Task 2**
```bash
git add Cargo.toml Cargo.lock core/chanora_core/Cargo.toml core/chanora_core/src/lib.rs crates/chanora_server_prefetch
git commit -m "refactor(core): use server prefetch crate"
```
---
### Task 3: Remove Duplicated Core Cache Tests and Verify Protocol Is Unchanged
**Files:**
- Modify: `core/chanora_core/src/lib.rs`
- Inspect only: `crates/chanora_protocol/src/adapter.rs`
- [ ] **Step 1: Remove core tests moved to the prefetch crate**
Delete these tests from `core/chanora_core/src/lib.rs` because they now belong in `chanora_server_prefetch`:
```rust
resolution_prefetch_cache_returns_fresh_exact_match
resolution_prefetch_cache_ignores_stale_entries
resolution_prefetch_cache_ignores_different_hosts
resolution_prefetch_cache_ignores_stale_generation_completion
```
Do not delete core trust-boundary tests:
```rust
session_uses_fresh_prefetched_resolution_for_connect_config
session_clears_untrusted_prefetched_resolution_on_cache_miss
connect_config_without_prefetched_resolution_clears_only_resolved_address
connect_config_preparation_keeps_prefetched_address_out_of_stored_config
```
- [ ] **Step 2: Confirm protocol source is untouched**
Run: `git diff -- crates/chanora_protocol/src/adapter.rs`
Expected: no diff. If there is a diff, revert only accidental protocol edits by manually restoring the changed lines from `HEAD`; do not use destructive git checkout/reset.
- [ ] **Step 3: Run combined Rust tests**
Run: `cargo test -p chanora_server_prefetch -p chanora_core -p chanora_protocol --lib`
Expected: all tests pass.
- [ ] **Step 4: Commit Task 3**
```bash
git add core/chanora_core/src/lib.rs Cargo.lock
git commit -m "test(core): keep prefetch tests at crate boundary"
```
---
### Task 4: Final Verification and Android Smoke
**Files:**
- No source edits expected.
- [ ] **Step 1: Run full focused Rust verification**
Run: `cargo test -p chanora_resolver -p chanora_server_prefetch -p chanora_protocol -p chanora_core --lib`
Expected: all tests pass.
- [ ] **Step 2: Run focused Flutter tests**
Run from `apps/chanora_flutter`:
```bash
flutter test test/services/server_resolution_prefetch_scheduler_test.dart test/services/connection_phase_state_test.dart test/services/ts3_server_link_test.dart
```
Expected: all tests pass.
- [ ] **Step 3: Build Android APK**
Run from `apps/chanora_flutter`:
```bash
flutter build apk --debug
```
Expected: build succeeds and produces `build/app/outputs/flutter-apk/app-debug.apk`.
- [ ] **Step 4: Run fresh-launch Android prefetch/connect smoke**
Run from `apps/chanora_flutter` with an emulator/device attached:
```bash
adb install -r "build/app/outputs/flutter-apk/app-debug.apk"
adb shell am force-stop app.chanora.chanora_flutter
adb logcat -c
adb shell monkey -p app.chanora.chanora_flutter -c android.intent.category.LAUNCHER 1
sleep 5
adb shell input tap 354 1363
sleep 8
adb logcat -d -v time | rg "resolution prefetch|prefetched|client request resolution|server address resolved|FATAL|AndroidRuntime|ANR"
adb shell uiautomator dump /sdcard/window.xml
adb exec-out cat /sdcard/window.xml
```
Expected logs include:
```text
resolution prefetch started
resolution prefetch result
connect using prefetched resolution
using prefetched server address
```
Expected UI hierarchy includes:
```text
Vigorous Pro
Leave server
Default Channel
ChanoraBeta
```
No app `FATAL`, app `AndroidRuntime` crash, or `ANR` lines should appear. `AndroidRuntime` lines from `monkey` or `uiautomator` are not app crashes.
- [ ] **Step 5: Run one channel switch smoke**
With the app still connected, run:
```bash
adb logcat -c
adb shell input tap 300 1705
sleep 2
adb shell input tap 300 1465
sleep 2
adb logcat -d -v time | rg "voice_join|client_move|FATAL|AndroidRuntime|ANR"
adb shell uiautomator dump /sdcard/window.xml
adb exec-out cat /sdcard/window.xml
```
Expected logs include successful moves to channel IDs similar to:
```text
voice_join accepted by server
client_move resolved by authoritative self channel change
```
Expected final UI has `ChanoraBeta` under `Default Channel`.
- [ ] **Step 6: Check final git state**
Run:
```bash
git status --short
git log --oneline -5
```
Expected: only unrelated untracked `config.json` remains, unless the user has added other unrelated work. The latest commits should be the prefetch crate split commits.
---
## Self-Review
- Spec coverage: the plan adds `chanora_server_prefetch`, moves cache policy and resolver-backed warming there, keeps core as trust boundary, preserves Flutter/protocol behavior, and includes Android connect smoke verification.
- Placeholder scan: no `TBD`, `TODO`, or unspecified edge handling remains.
- Type consistency: the plan uses `ServerPrefetcher`, `ServerPrefetchError`, `prefetch`, and `fresh_match` consistently across crate and core tasks.
@@ -0,0 +1,899 @@
# Server Resolution Prefetch Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add invisible server-address resolution prefetch so the active host field can warm Rust resolver state before Connect without changing connection semantics.
**Architecture:** Flutter schedules debounced prefetch calls for the active host field only. Rust owns resolution prefetch state, TTL, exact-match validation, and connect-time reuse. The protocol layer accepts an optional already-resolved socket address so Connect can skip resolver work only when the core cache says it is safe.
**Tech Stack:** Flutter/Dart, flutter_rust_bridge generated bindings, Rust async Tokio, `chanora_core`, `chanora_protocol`, `chanora_resolver`, Flutter widget/service tests, Rust unit tests.
---
## File Structure
- Modify `core/chanora_core/src/lib.rs`: add a session-owned prefetch cache, prefetch API, connect-time cache lookup, and unit tests for TTL/exact-match/generation behavior.
- Modify `crates/chanora_protocol/src/adapter.rs`: add `resolved_address: Option<SocketAddr>` to `ConnectConfig` and skip `resolve_server_socket()` when present.
- Modify `crates/chanora_bridge/src/api.rs`: add `prefetch_server_resolution(host: String)` bridge function and pass cache-aware connects through `ChanoraSession`.
- Regenerate `crates/chanora_bridge/src/frb_generated.rs`: generated Rust bridge bindings.
- Regenerate `apps/chanora_flutter/lib/src/rust/api.dart`, `api.freezed.dart`, and `frb_generated.dart`: generated Dart bridge bindings.
- Create `apps/chanora_flutter/lib/services/server_resolution_prefetch_scheduler.dart`: small testable debounce helper for host-field prefetch scheduling.
- Modify `apps/chanora_flutter/lib/main.dart`: wire `_hostCtl` listener, settings-loaded prefetch, and disposal.
- Create `apps/chanora_flutter/test/services/server_resolution_prefetch_scheduler_test.dart`: Flutter/Dart tests for debounce and empty-host behavior.
Keep all prefetch UI invisible. Do not prefetch bookmarks in bulk.
---
### Task 1: Protocol Config Accepts Prefetched Socket
**Files:**
- Modify: `crates/chanora_protocol/src/adapter.rs`
- [ ] **Step 1: Add a failing protocol config test**
Add this test inside the existing `#[cfg(test)] mod tests` in `crates/chanora_protocol/src/adapter.rs`:
```rust
#[test]
fn connect_config_can_carry_prefetched_socket_address() {
let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap();
let cfg = ConnectConfig {
address: "example.com".to_string(),
nickname: "Tester".to_string(),
password: None,
identity: None,
ready_timeout: Duration::from_secs(1),
resolved_address: Some(addr),
};
assert_eq!(cfg.resolved_address, Some(addr));
}
```
- [ ] **Step 2: Run the test to verify it fails**
Run: `cargo test -p chanora_protocol connect_config_can_carry_prefetched_socket_address`
Expected: FAIL to compile with a message like `struct ConnectConfig has no field named resolved_address`.
- [ ] **Step 3: Add the minimal config field**
Update `ConnectConfig` in `crates/chanora_protocol/src/adapter.rs`:
```rust
#[derive(Debug, Clone)]
pub struct ConnectConfig {
/// Server address: `hostname[:port]` or TSDNS name.
pub address: String,
/// Optional already-resolved socket address from core's invisible
/// prefetch cache. When present, the protocol layer skips address
/// resolution but still opens a normal TS3 connection only after
/// the user requested Connect.
pub resolved_address: Option<std::net::SocketAddr>,
/// Nickname to use on the server.
pub nickname: String,
/// Optional server password.
pub password: Option<String>,
/// Optional pre-existing identity (base64 string accepted by
/// `tsclientlib::Identity::new_from_str`). If `None`, a fresh
/// identity is generated and **not persisted** — production callers
/// should provide one from secure identity storage.
pub identity: Option<String>,
/// How long to wait for the initial state snapshot before
/// returning `ProtocolError::Timeout`.
pub ready_timeout: Duration,
}
impl Default for ConnectConfig {
fn default() -> Self {
Self {
address: String::new(),
resolved_address: None,
nickname: "Chanora".to_string(),
password: None,
identity: None,
ready_timeout: Duration::from_secs(10),
}
}
}
```
- [ ] **Step 4: Run the focused test**
Run: `cargo test -p chanora_protocol connect_config_can_carry_prefetched_socket_address`
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add crates/chanora_protocol/src/adapter.rs
git commit -m "feat(protocol): accept prefetched server address"
```
---
### Task 2: Protocol Connect Skips Resolver On Prefetch Hit
**Files:**
- Modify: `crates/chanora_protocol/src/adapter.rs`
- [ ] **Step 1: Add a failing helper test**
Add a private helper next to `resolve_server_socket()` only after this failing test is added. First add this test in `#[cfg(test)] mod tests`:
```rust
#[test]
fn server_socket_from_config_prefers_prefetched_address() {
let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap();
let cfg = ConnectConfig {
address: "example.com".to_string(),
resolved_address: Some(addr),
nickname: "Tester".to_string(),
password: None,
identity: None,
ready_timeout: Duration::from_secs(1),
};
assert_eq!(server_socket_from_config(&cfg), Some(addr));
}
```
- [ ] **Step 2: Run the test to verify it fails**
Run: `cargo test -p chanora_protocol server_socket_from_config_prefers_prefetched_address`
Expected: FAIL to compile with `cannot find function server_socket_from_config`.
- [ ] **Step 3: Add the helper and wire connection task**
Add this helper near `resolve_server_socket()`:
```rust
fn server_socket_from_config(cfg: &ConnectConfig) -> Option<SocketAddr> {
cfg.resolved_address
}
```
Find the call in `connection_task` that currently resolves the address, shaped like:
```rust
let server = resolve_server_socket(&cfg.address).await?;
```
Replace it with:
```rust
let server = match server_socket_from_config(&cfg) {
Some(addr) => {
info!(
target: "chanora_protocol",
input = %cfg.address,
resolved = %addr,
"using prefetched server address"
);
addr
}
None => resolve_server_socket(&cfg.address).await?,
};
```
- [ ] **Step 4: Run protocol tests**
Run: `cargo test -p chanora_protocol server_socket_from_config_prefers_prefetched_address`
Expected: PASS.
- [ ] **Step 5: Run broader protocol tests**
Run: `cargo test -p chanora_protocol`
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add crates/chanora_protocol/src/adapter.rs
git commit -m "fix(protocol): reuse prefetched server address"
```
---
### Task 3: Core Prefetch Cache Data Model
**Files:**
- Modify: `core/chanora_core/src/lib.rs`
- [ ] **Step 1: Write failing cache tests**
Add these tests inside `#[cfg(test)] mod tests` in `core/chanora_core/src/lib.rs`:
```rust
#[test]
fn resolution_prefetch_cache_returns_fresh_exact_match() {
let mut cache = ResolutionPrefetchCache::default();
let now = std::time::Instant::now();
let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap();
let generation = cache.begin(" Example.COM ");
cache.store_success(generation, " Example.COM ", addr, now);
assert_eq!(cache.fresh_match("example.com", now), Some(addr));
}
#[test]
fn resolution_prefetch_cache_ignores_stale_entries() {
let mut cache = ResolutionPrefetchCache::default();
let now = std::time::Instant::now();
let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap();
let generation = cache.begin("example.com");
cache.store_success(generation, "example.com", addr, now - RESOLUTION_PREFETCH_TTL - std::time::Duration::from_secs(1));
assert_eq!(cache.fresh_match("example.com", now), None);
}
#[test]
fn resolution_prefetch_cache_ignores_different_hosts() {
let mut cache = ResolutionPrefetchCache::default();
let now = std::time::Instant::now();
let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap();
let generation = cache.begin("example.com");
cache.store_success(generation, "example.com", addr, now);
assert_eq!(cache.fresh_match("other.example.com", now), None);
}
#[test]
fn resolution_prefetch_cache_ignores_stale_generation_completion() {
let mut cache = ResolutionPrefetchCache::default();
let now = std::time::Instant::now();
let first: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap();
let second: std::net::SocketAddr = "127.0.0.2:9987".parse().unwrap();
let old_generation = cache.begin("example.com");
let new_generation = cache.begin("example.com");
cache.store_success(new_generation, "example.com", second, now);
cache.store_success(old_generation, "example.com", first, now);
assert_eq!(cache.fresh_match("example.com", now), Some(second));
}
```
- [ ] **Step 2: Run tests to verify they fail**
Run: `cargo test -p chanora_core resolution_prefetch_cache --lib`
Expected: FAIL to compile with missing `ResolutionPrefetchCache` and `RESOLUTION_PREFETCH_TTL`.
- [ ] **Step 3: Add the cache model**
Add near `NetworkDiagnostics` in `core/chanora_core/src/lib.rs`:
```rust
const RESOLUTION_PREFETCH_TTL: Duration = Duration::from_secs(120);
#[derive(Debug, Clone)]
struct ResolutionPrefetchEntry {
normalized_host: String,
resolved_address: std::net::SocketAddr,
completed_at: std::time::Instant,
generation: u64,
}
#[derive(Debug, Default)]
struct ResolutionPrefetchCache {
latest_generation: u64,
entry: Option<ResolutionPrefetchEntry>,
last_failure: Option<String>,
}
impl ResolutionPrefetchCache {
fn begin(&mut self, host: &str) -> u64 {
self.latest_generation = self.latest_generation.saturating_add(1);
self.last_failure = None;
let _ = normalize_prefetch_host(host);
self.latest_generation
}
fn store_success(
&mut self,
generation: u64,
host: &str,
resolved_address: std::net::SocketAddr,
completed_at: std::time::Instant,
) {
if generation != self.latest_generation {
return;
}
self.entry = Some(ResolutionPrefetchEntry {
normalized_host: normalize_prefetch_host(host),
resolved_address,
completed_at,
generation,
});
self.last_failure = None;
}
fn store_failure(&mut self, generation: u64, error: String) {
if generation != self.latest_generation {
return;
}
self.last_failure = Some(error);
}
fn fresh_match(&self, host: &str, now: std::time::Instant) -> Option<std::net::SocketAddr> {
let normalized = normalize_prefetch_host(host);
let entry = self.entry.as_ref()?;
if entry.normalized_host != normalized {
return None;
}
if now.duration_since(entry.completed_at) > RESOLUTION_PREFETCH_TTL {
return None;
}
Some(entry.resolved_address)
}
}
fn normalize_prefetch_host(host: &str) -> String {
host.trim().to_lowercase()
}
```
- [ ] **Step 4: Run focused core tests**
Run: `cargo test -p chanora_core resolution_prefetch_cache --lib`
Expected: all four tests PASS.
- [ ] **Step 5: Commit**
```bash
git add core/chanora_core/src/lib.rs
git commit -m "feat(core): add resolution prefetch cache"
```
---
### Task 4: Core Prefetch API And Connect Cache Lookup
**Files:**
- Modify: `core/chanora_core/src/lib.rs`
- [ ] **Step 1: Write failing session tests**
Add this test in `#[cfg(test)] mod tests` in `core/chanora_core/src/lib.rs`:
```rust
#[tokio::test]
async fn session_uses_fresh_prefetched_resolution_for_connect_config() {
let session = ChanoraSession::new();
let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap();
{
let mut cache = session.resolution_prefetch.lock().await;
let generation = cache.begin("example.com");
cache.store_success(generation, "example.com", addr, std::time::Instant::now());
}
let mut cfg = ConnectConfig::default();
cfg.address = " example.com ".to_string();
session.apply_prefetched_resolution(&mut cfg).await;
assert_eq!(cfg.resolved_address, Some(addr));
}
```
- [ ] **Step 2: Run the test to verify it fails**
Run: `cargo test -p chanora_core session_uses_fresh_prefetched_resolution_for_connect_config --lib`
Expected: FAIL to compile with missing `resolution_prefetch` and `apply_prefetched_resolution`.
- [ ] **Step 3: Add session field and constructor initialization**
Add to `ChanoraSession`:
```rust
/// Invisible server-address prefetch cache. Warmed by Flutter typing
/// but validated by Rust before Connect can reuse it.
resolution_prefetch: Arc<Mutex<ResolutionPrefetchCache>>,
```
Initialize in `ChanoraSession::new()`:
```rust
resolution_prefetch: Arc::new(Mutex::new(ResolutionPrefetchCache::default())),
```
- [ ] **Step 4: Add connect-time cache lookup helper**
Add methods inside `impl ChanoraSession` before `connect()`:
```rust
async fn apply_prefetched_resolution(&self, cfg: &mut ConnectConfig) {
let host = cfg.address.trim();
if host.is_empty() {
return;
}
let resolved = {
let cache = self.resolution_prefetch.lock().await;
cache.fresh_match(host, std::time::Instant::now())
};
match resolved {
Some(addr) => {
info!(
target: "chanora_core",
host = %host,
resolved = %addr,
"connect using prefetched resolution"
);
cfg.resolved_address = Some(addr);
}
None => {
info!(target: "chanora_core", host = %host, "connect prefetch miss or stale");
}
}
}
```
In `connect()`, after identity resolution and before `ProtocolClient::connect(cfg.clone())`, add:
```rust
self.apply_prefetched_resolution(&mut cfg).await;
```
- [ ] **Step 5: Add prefetch scheduling API in core**
Add this method in `impl ChanoraSession`:
```rust
pub async fn prefetch_server_resolution(&self, host: String) -> Result<(), CoreError> {
let normalized = normalize_prefetch_host(&host);
if normalized.is_empty() {
return Ok(());
}
let generation = {
let mut cache = self.resolution_prefetch.lock().await;
cache.begin(&normalized)
};
let cache = self.resolution_prefetch.clone();
tokio::spawn(async move {
info!(target: "chanora_core", host = %normalized, "resolution prefetch started");
let result = resolve_prefetch_socket(&normalized).await;
let mut guard = cache.lock().await;
match result {
Ok(addr) => {
info!(
target: "chanora_core",
host = %normalized,
resolved = %addr,
"resolution prefetch result"
);
guard.store_success(generation, &normalized, addr, std::time::Instant::now());
}
Err(err) => {
warn!(
target: "chanora_core",
host = %normalized,
error = %err,
"resolution prefetch failed"
);
guard.store_failure(generation, err.to_string());
}
}
});
Ok(())
}
```
Add this helper outside `impl ChanoraSession`:
```rust
async fn resolve_prefetch_socket(host: &str) -> Result<std::net::SocketAddr, CoreError> {
let resolver = chanora_resolver::ChanoraResolver::new()
.map_err(|err| CoreError::Protocol(chanora_protocol::ProtocolError::DnsFailed {
host: host.to_string(),
reason: format!("resolver initialization failed: {err}"),
}))?;
let resolved = resolver
.resolve_client_address(host)
.await
.map_err(|err| CoreError::Protocol(chanora_protocol::ProtocolError::DnsFailed {
host: host.to_string(),
reason: err.to_string(),
}))?;
resolved
.parse::<std::net::SocketAddr>()
.map_err(|err| CoreError::Protocol(chanora_protocol::ProtocolError::DnsFailed {
host: host.to_string(),
reason: format!("resolver returned invalid socket address '{resolved}': {err}"),
}))
}
```
- [ ] **Step 6: Run focused core tests**
Run: `cargo test -p chanora_core session_uses_fresh_prefetched_resolution_for_connect_config --lib`
Expected: PASS.
- [ ] **Step 7: Run core tests**
Run: `cargo test -p chanora_core --lib`
Expected: PASS.
- [ ] **Step 8: Commit**
```bash
git add core/chanora_core/src/lib.rs
git commit -m "feat(core): prefetch server resolution"
```
---
### Task 5: Bridge API Exposes Prefetch
**Files:**
- Modify: `crates/chanora_bridge/src/api.rs`
- Regenerate: `crates/chanora_bridge/src/frb_generated.rs`
- Regenerate: `apps/chanora_flutter/lib/src/rust/api.dart`
- Regenerate: `apps/chanora_flutter/lib/src/rust/api.freezed.dart`
- Regenerate: `apps/chanora_flutter/lib/src/rust/frb_generated.dart`
- [ ] **Step 1: Add bridge function**
Add after `connect()` in `crates/chanora_bridge/src/api.rs`:
```rust
/// Warm server address resolution for the active host field. This is
/// intentionally fire-and-forget from the UI perspective: it schedules
/// Rust-side prefetch work and never opens a TS3 session.
pub async fn prefetch_server_resolution(host: String) -> Result<(), BridgeError> {
runtime()
.spawn(async move { session().prefetch_server_resolution(host).await })
.await
.map_err(|e| task_join_error("prefetch_server_resolution", e))??;
Ok(())
}
```
- [ ] **Step 2: Regenerate flutter_rust_bridge bindings**
Run from repo root:
```bash
flutter_rust_bridge_codegen generate
```
Expected: generated files update and `apps/chanora_flutter/lib/src/rust/api.dart` contains:
```dart
Future<void> prefetchServerResolution({required String host}) => RustLib
.instance
.api
.crateApiPrefetchServerResolution(host: host);
```
- [ ] **Step 3: Run bridge/core compile check**
Run: `cargo test -p chanora_bridge --lib`
Expected: PASS.
- [ ] **Step 4: Run Flutter analyzer smoke check**
Run: `cd apps/chanora_flutter && flutter analyze`
Expected: no new errors related to generated bindings.
- [ ] **Step 5: Commit**
```bash
git add crates/chanora_bridge/src/api.rs crates/chanora_bridge/src/frb_generated.rs apps/chanora_flutter/lib/src/rust/api.dart apps/chanora_flutter/lib/src/rust/api.freezed.dart apps/chanora_flutter/lib/src/rust/frb_generated.dart
git commit -m "feat(bridge): expose resolution prefetch"
```
---
### Task 6: Flutter Prefetch Scheduler Helper
**Files:**
- Create: `apps/chanora_flutter/lib/services/server_resolution_prefetch_scheduler.dart`
- Create: `apps/chanora_flutter/test/services/server_resolution_prefetch_scheduler_test.dart`
- [ ] **Step 1: Write failing scheduler tests**
Create `apps/chanora_flutter/test/services/server_resolution_prefetch_scheduler_test.dart`:
```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:chanora_flutter/services/server_resolution_prefetch_scheduler.dart';
void main() {
test('debounces host edits and prefetches latest trimmed host', () async {
final calls = <String>[];
final scheduler = ServerResolutionPrefetchScheduler(
delay: const Duration(milliseconds: 20),
prefetch: (host) async => calls.add(host),
);
scheduler.schedule(' first.example.com ');
scheduler.schedule(' second.example.com ');
await Future<void>.delayed(const Duration(milliseconds: 35));
expect(calls, ['second.example.com']);
scheduler.dispose();
});
test('skips empty hosts', () async {
final calls = <String>[];
final scheduler = ServerResolutionPrefetchScheduler(
delay: const Duration(milliseconds: 10),
prefetch: (host) async => calls.add(host),
);
scheduler.schedule(' ');
await Future<void>.delayed(const Duration(milliseconds: 25));
expect(calls, isEmpty);
scheduler.dispose();
});
test('dispose cancels pending prefetch', () async {
final calls = <String>[];
final scheduler = ServerResolutionPrefetchScheduler(
delay: const Duration(milliseconds: 30),
prefetch: (host) async => calls.add(host),
);
scheduler.schedule('example.com');
scheduler.dispose();
await Future<void>.delayed(const Duration(milliseconds: 45));
expect(calls, isEmpty);
});
}
```
- [ ] **Step 2: Run tests to verify they fail**
Run: `cd apps/chanora_flutter && flutter test test/services/server_resolution_prefetch_scheduler_test.dart`
Expected: FAIL to compile with missing `server_resolution_prefetch_scheduler.dart`.
- [ ] **Step 3: Implement scheduler**
Create `apps/chanora_flutter/lib/services/server_resolution_prefetch_scheduler.dart`:
```dart
import 'dart:async';
typedef ServerResolutionPrefetch = Future<void> Function(String host);
class ServerResolutionPrefetchScheduler {
ServerResolutionPrefetchScheduler({
required this.prefetch,
this.delay = const Duration(milliseconds: 700),
});
final ServerResolutionPrefetch prefetch;
final Duration delay;
Timer? _timer;
bool _disposed = false;
void schedule(String rawHost) {
if (_disposed) return;
_timer?.cancel();
final host = rawHost.trim();
if (host.isEmpty) return;
_timer = Timer(delay, () {
if (_disposed) return;
unawaited(prefetch(host));
});
}
void dispose() {
_disposed = true;
_timer?.cancel();
_timer = null;
}
}
```
- [ ] **Step 4: Run scheduler tests**
Run: `cd apps/chanora_flutter && flutter test test/services/server_resolution_prefetch_scheduler_test.dart`
Expected: PASS.
- [ ] **Step 5: Format Dart files**
Run: `cd apps/chanora_flutter && dart format lib/services/server_resolution_prefetch_scheduler.dart test/services/server_resolution_prefetch_scheduler_test.dart`
Expected: files formatted, no errors.
- [ ] **Step 6: Commit**
```bash
git add apps/chanora_flutter/lib/services/server_resolution_prefetch_scheduler.dart apps/chanora_flutter/test/services/server_resolution_prefetch_scheduler_test.dart
git commit -m "feat(ui): add resolution prefetch scheduler"
```
---
### Task 7: Wire Prefetch Scheduler Into Home UI
**Files:**
- Modify: `apps/chanora_flutter/lib/main.dart`
- [ ] **Step 1: Add imports and state fields**
In `apps/chanora_flutter/lib/main.dart`, add:
```dart
import 'services/server_resolution_prefetch_scheduler.dart';
```
Inside `_BetaHomeState`, add:
```dart
late final ServerResolutionPrefetchScheduler _resolutionPrefetch;
```
- [ ] **Step 2: Initialize scheduler and listener**
In `initState()`, before `_eventsSub = rust.eventsStream().listen(_onEvent);`, add:
```dart
_resolutionPrefetch = ServerResolutionPrefetchScheduler(
prefetch: (host) => rust.prefetchServerResolution(host: host),
);
_hostCtl.addListener(_onHostEdited);
```
Add method in `_BetaHomeState`:
```dart
void _onHostEdited() {
_resolutionPrefetch.schedule(_hostCtl.text);
}
```
- [ ] **Step 3: Schedule prefetch after settings load**
In `_loadUiSettings()`, after setting host/nickname from settings and before the `catch`, add:
```dart
if (settings.host.isNotEmpty) {
_resolutionPrefetch.schedule(settings.host);
}
```
Ensure this is not duplicated if an existing `_hostCtl.text = settings.host;` listener already schedules it. If both paths fire, keep only the explicit `_resolutionPrefetch.schedule(settings.host);` and temporarily remove/re-add the listener around `_hostCtl.text = settings.host`, or accept the duplicate because scheduler debounce collapses it. Prefer accepting the duplicate for minimal change.
- [ ] **Step 4: Dispose scheduler and listener**
In `dispose()`, before `_hostCtl.dispose();`, add:
```dart
_hostCtl.removeListener(_onHostEdited);
_resolutionPrefetch.dispose();
```
- [ ] **Step 5: Run focused Flutter tests**
Run: `cd apps/chanora_flutter && flutter test test/services/server_resolution_prefetch_scheduler_test.dart test/services/connection_phase_state_test.dart`
Expected: PASS.
- [ ] **Step 6: Run analyzer**
Run: `cd apps/chanora_flutter && flutter analyze`
Expected: no new analyzer errors.
- [ ] **Step 7: Commit**
```bash
git add apps/chanora_flutter/lib/main.dart
git commit -m "feat(ui): prefetch active server address"
```
---
### Task 8: End-To-End Verification On Android
**Files:**
- No source changes expected unless verification finds a bug.
- [ ] **Step 1: Run focused Rust tests**
Run: `cargo test -p chanora_resolver -p chanora_protocol -p chanora_core --lib`
Expected: PASS.
- [ ] **Step 2: Run focused Flutter tests**
Run: `cd apps/chanora_flutter && flutter test test/services/server_resolution_prefetch_scheduler_test.dart test/services/connection_phase_state_test.dart test/services/ts3_server_link_test.dart`
Expected: PASS.
- [ ] **Step 3: Build Android debug APK**
Run: `cd apps/chanora_flutter && flutter build apk --debug`
Expected: `✓ Built build/app/outputs/flutter-apk/app-debug.apk`.
- [ ] **Step 4: Install and launch through ADB**
Run from `apps/chanora_flutter`:
```bash
adb install -r "build/app/outputs/flutter-apk/app-debug.apk"
adb shell am force-stop app.chanora.chanora_flutter
adb logcat -c
adb shell monkey -p app.chanora.chanora_flutter -c android.intent.category.LAUNCHER 1
```
Expected: install success and app launches.
- [ ] **Step 5: Allow prefetch, connect, and inspect logs**
Run from repo root:
```bash
sleep 2
adb shell input tap 354 1363
sleep 8
adb logcat -d -v time | rg "resolution prefetch|prefetched|client request resolution|server address resolved|FATAL|AndroidRuntime"
```
Expected:
- No app `FATAL` lines.
- Logs include `resolution prefetch started` and either `resolution prefetch result` or safe failure.
- If prefetch completed before tap, logs include `connect using prefetched resolution` and protocol logs include `using prefetched server address`.
- If prefetch did not complete before tap, connect still succeeds through normal resolver path.
- [ ] **Step 6: Inspect UI hierarchy**
Run:
```bash
adb shell uiautomator dump /sdcard/window.xml
adb exec-out cat /sdcard/window.xml
```
Expected: connected server view with channel count, not stuck on `Synchronizing...`.
- [ ] **Step 7: Final status check**
Run: `git status --short`
Expected: no unintended source changes. Unrelated existing `config.json` may remain untracked and must not be committed.
---
## Self-Review
Spec coverage:
- Active-field-only prefetch: Tasks 6 and 7.
- Rust-owned resolver cache and exact-match validation: Tasks 3 and 4.
- 2-minute TTL: Task 3.
- Invisible UI: Tasks 6 and 7 avoid any UI status changes.
- No bookmark fan-out: Task 7 wires only `_hostCtl` and settings-loaded host.
- No TS3 session before Connect: Task 4 only resolves address; Task 2 only uses resolved address during actual protocol connect.
- Diagnostics: Tasks 2 and 4 add logs for prefetch and cache hit/miss.
- Testing and Android smoke: Task 8.
Completeness scan: no incomplete markers are used. Each code-changing task includes concrete code snippets and commands.
Type consistency: `resolved_address` is added to protocol `ConnectConfig`, core uses `cfg.resolved_address`, bridge exposes `prefetch_server_resolution`, and Dart uses generated `prefetchServerResolution`.
@@ -0,0 +1,114 @@
# DV Evidence Pack Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Create a document set that lets a DV meeting review Chanora's current verification posture, traceability, release blockers, waivers, and evidence without implying incomplete work is complete.
**Architecture:** The pack is documentation-only. Verification plans live under `docs/verification/`; release decision evidence lives under `docs/release/`; cross-document traceability lives under `docs/governance/`; security, privacy, and legal gate summaries live in their existing README-advertised folders.
**Tech Stack:** Markdown, existing SysRS/SysDes/SRS baselines, implementation status report, CI workflow definitions.
---
### Task 1: Create Verification Plan Set
**Files:**
- Create: `docs/verification/verification-master-plan.md`
- Create: `docs/verification/swe4-unit-verification-plan.md`
- Create: `docs/verification/swe5-software-integration-verification-plan.md`
- Create: `docs/verification/swe6-software-verification-plan.md`
- Create: `docs/verification/sys4-system-integration-verification-plan.md`
- [x] **Step 1: Write master plan**
Create `docs/verification/verification-master-plan.md` with lifecycle scope, evidence rules, entry/exit criteria, current evidence sources, open gates, and reviewer decision framing.
- [x] **Step 2: Write SWE.4 plan**
Create `docs/verification/swe4-unit-verification-plan.md` with unit verification scope for Rust crates, Flutter services/widgets, diagnostics, storage, state reducers, audio DSP, and known unit-test gaps.
- [x] **Step 3: Write SWE.5 plan**
Create `docs/verification/swe5-software-integration-verification-plan.md` with cross-component integration scope for Flutter-bridge-core, protocol-state, audio-platform, secure storage, diagnostics export, resolver prefetch, and packaging hooks.
- [x] **Step 4: Write SWE.6 plan**
Create `docs/verification/swe6-software-verification-plan.md` with SRS-level acceptance scope and the SysRS-241 through SysRS-257 MVP acceptance matrix.
- [x] **Step 5: Write SYS.4 plan**
Create `docs/verification/sys4-system-integration-verification-plan.md` with system-level integration scope for external compatible servers, OS services, hardware, network, app stores, diagnostics, and release evidence.
### Task 2: Create Release and DV Decision Records
**Files:**
- Create: `docs/release/release-readiness-go-nogo-record.md`
- Create: `docs/release/dv-waiver-register.md`
- [x] **Step 1: Write release readiness record**
Create `docs/release/release-readiness-go-nogo-record.md` with current candidate metadata, decision state, platform readiness, verification status, legal/security/privacy gates, blockers, and meeting recommendation.
- [x] **Step 2: Write waiver register**
Create `docs/release/dv-waiver-register.md` with explicit waivers for DEC-012, Android Keystore DEK, iOS voice-processing mode, source-build-only desktop/iOS artifacts, state reducer coverage, and VAD deferral.
### Task 3: Create Traceability and Gate Summaries
**Files:**
- Create: `docs/governance/traceability-matrix.md`
- Create: `docs/security/security-privacy-legal-guideline.md`
- Create: `docs/security/dependency-and-supply-chain-report.md`
- Create: `docs/privacy/privacy-policy.md`
- Create: `docs/legal/trademark-and-attribution-review.md`
- [x] **Step 1: Write traceability matrix**
Create `docs/governance/traceability-matrix.md` summarizing SysRS to SysDes to SRS to verification coverage, including the MVP acceptance and verification handoff items.
- [x] **Step 2: Write security/privacy/legal guideline**
Create `docs/security/security-privacy-legal-guideline.md` summarizing release gates and evidence expectations for secure storage, diagnostics redaction, dependency review, privacy policy, and affiliation wording.
- [x] **Step 3: Write dependency report**
Create `docs/security/dependency-and-supply-chain-report.md` summarizing current CI checks and open evidence gaps without claiming DEC-012 completion.
- [x] **Step 4: Write privacy policy baseline**
Create `docs/privacy/privacy-policy.md` as an engineering release-candidate privacy baseline covering local storage, permissions, diagnostics, and the no automatic telemetry posture.
- [x] **Step 5: Write trademark review**
Create `docs/legal/trademark-and-attribution-review.md` with current non-affiliation wording requirement and open legal sign-off state.
### Task 4: Verify Documentation Pack
**Files:**
- Inspect all created files.
- [x] **Step 1: Search for forbidden sentinel text**
Run the sentinel-language scan over `docs/verification`, `docs/release`, `docs/governance`, `docs/security`, `docs/privacy`, and `docs/legal`.
Expected: no matches introduced by the DV evidence pack except intentional historical references in source documents outside these folders.
- [x] **Step 2: Confirm expected files exist**
Run: `ls docs/verification docs/release docs/governance docs/security docs/privacy docs/legal`
Expected: all DV evidence pack files are listed.
- [x] **Step 3: Inspect git status and diff**
Run: `git diff -- docs/verification docs/release docs/governance docs/security docs/privacy docs/legal docs/superpowers/plans/2026-05-29-dv-evidence-pack.md`
Expected: only intended Markdown additions are present.
---
## Self-Review
- Spec coverage: covers verification plan set, release decision evidence, waiver register, traceability matrix, and minimum security/privacy/legal gate summaries.
- Placeholder scan: plan contains no incomplete instructions.
- Type consistency: document paths match the README-advertised folders and task file list.
@@ -0,0 +1,79 @@
# Finish DV Document Tree Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Fill the README-advertised document tree with baseline candidate documents so DV reviewers can navigate all required gates.
**Architecture:** Keep canonical large baselines at current root paths and add README-path stubs or summaries where needed. Governance, release, security, privacy, legal, UI/UX, i18n, and references each get explicit baseline documents that state current evidence and open gates honestly.
**Tech Stack:** Markdown documentation aligned to SysRS, SysDes, SRS, SAD, SDD, verification, release, and implementation status.
---
### Task 1: Add Requirements Path Wrappers
**Files:**
- Create: `docs/requirements/sysrs.md`
- Create: `docs/requirements/srs.md`
- [x] **Step 1: Create README entry records that point to canonical root documents and summarize DV review anchors**
These wrappers preserve README paths without duplicating the canonical baselines.
### Task 2: Add Governance Baselines
**Files:**
- Create: `docs/governance/document-index.md`
- Create: `docs/governance/document-naming-convention.md`
- Create: `docs/governance/baseline-approval-record.md`
- Create: `docs/governance/baseline-candidate-validation-report.md`
- Create: `docs/governance/document-review-report.md`
- Create: `docs/governance/product-decision-register.md`
- Create: `docs/governance/decision-impact-assessment.md`
- Create: `docs/governance/git-commit-message-convention.md`
- Create: `docs/governance/repo-format-validation-report.md`
- Create: `docs/governance/path-migration-map.md`
- [x] **Step 1: Write governance records**
Each record summarizes status for DV, identifies owner expectations, and avoids claiming final release approval.
### Task 3: Add Remaining Release, Security, UI/UX, I18n, Reference Docs
**Files:**
- Create: `docs/release/platform-release-policy.md`
- Create: `docs/security/threat-model.md`
- Create: `docs/security/secure-storage-audit-report.md`
- Create: `docs/security/diagnostic-redaction-audit-report.md`
- Create: `docs/ui-ux/material3-guideline.md`
- Create: `docs/ui-ux/material3-design-tokens.md`
- Create: `docs/ui-ux/material3-component-catalog.md`
- Create: `docs/ui-ux/adaptive-layout-platform-guide.md`
- Create: `docs/i18n/localization-architecture.md`
- Create: `docs/references/external-references.md`
- Create: `docs/references/aspice-swe2-swe3-integration-note.md`
- [x] **Step 1: Write remaining baseline docs**
Use concise DV-ready records that reference current implementation and open gaps.
### Task 4: Verify Complete Tree
**Files:**
- Inspect all created documents.
- [x] **Step 1: Check README paths exist**
Run a shell `test -f` command over every README-advertised path.
- [x] **Step 2: Sentinel-language scan**
Run the sentinel-language scan over `docs/requirements`, `docs/architecture`, `docs/verification`, `docs/release`, `docs/security`, `docs/privacy`, `docs/legal`, `docs/ui-ux`, `docs/i18n`, `docs/governance`, and `docs/references`; expect no matches.
---
## Self-Review
- Spec coverage: fills all README-advertised document paths except already existing files.
- Placeholder scan: plan contains no incomplete document instructions.
- Type consistency: file paths match README tree.
@@ -0,0 +1,108 @@
# State Sync and UI Settings Validation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Complete validation-backed state-sync evidence and UI settings persistence while updating DV documents.
**Architecture:** State reducer work stays in `crates/chanora_state/src/lib.rs` with Rust unit tests. UI settings persistence stays in `apps/chanora_flutter/lib/services/ui_preferences_service.dart` with Flutter service tests; app-level theme application is wired in `apps/chanora_flutter/lib/main.dart` only if needed by the persisted setting.
**Tech Stack:** Rust/cargo tests, Flutter/Dart, shared_preferences, Markdown documentation.
---
### Task 1: State Reducer Validation
**Files:**
- Modify: `crates/chanora_state/src/lib.rs`
- [x] **Step 1: Write failing regression test**
Add `channel_delete_removes_clients_in_deleted_channel` proving channel deletion removes clients assigned to that channel and emits client-removal deltas before the channel-removal delta.
- [x] **Step 2: Verify RED**
Run: `cargo test -p chanora_state channel_delete_removes_clients_in_deleted_channel --locked`
Expected: FAIL because deleted-channel clients remain in state.
- [x] **Step 3: Implement minimal reducer fix**
In `StateEvent::ChannelDeleted`, collect clients whose `client.channel == id`, remove them from `clients` and `client_order`, then emit deterministic `ClientRemoved` deltas before `ChannelRemoved`.
- [x] **Step 4: Verify GREEN**
Run: `cargo test -p chanora_state channel_delete_removes_clients_in_deleted_channel --locked`
Expected: PASS.
- [x] **Step 5: Run full state crate tests**
Run: `cargo test -p chanora_state --locked`
Expected: all state crate tests pass.
### Task 2: UI Settings Persistence
**Files:**
- Modify: `apps/chanora_flutter/lib/services/ui_preferences_service.dart`
- Modify: `apps/chanora_flutter/test/services/ui_preferences_service_test.dart`
- Modify: `apps/chanora_flutter/lib/main.dart`
- [x] **Step 1: Add failing tests for theme persistence**
Add tests for default `system` theme mode, saving `dark`, saving `light`, and invalid stored value fallback to `system`.
- [x] **Step 2: Verify RED**
Run: `flutter test test/services/ui_preferences_service_test.dart`
Expected: FAIL because `UiThemeMode`, `themeMode`, and `saveThemeMode` do not exist.
- [x] **Step 3: Implement minimal service changes**
Add `UiThemeMode`, `UiSettings.themeMode`, persisted key `ui.theme_mode`, and `saveThemeMode`.
- [x] **Step 4: Verify GREEN**
Run: `flutter test test/services/ui_preferences_service_test.dart`
Expected: PASS.
- [x] **Step 5: Wire app theme mode**
Make `ChanoraApp` load persisted theme mode and pass `themeMode` into `MaterialApp`.
- [x] **Step 6: Run focused Flutter tests**
Run: `flutter test test/services/ui_preferences_service_test.dart`
Expected: PASS.
### Task 3: Documentation Updates
**Files:**
- Modify: `docs/implementation-status-2026-05-28.md`
- Modify: `docs/release/dv-waiver-register.md`
- Modify: `docs/verification/swe4-unit-verification-plan.md`
- Modify: `docs/verification/swe6-software-verification-plan.md`
- Modify: `docs/architecture/sdd.md`
- [x] **Step 1: Update implementation status**
Mark `chanora_state` scaffold statement as superseded by reducer implementation/tests and mark UI settings persistence implemented for SharedPreferences scope.
- [x] **Step 2: Update waiver and verification docs**
Record reducer evidence and leave event replay as the remaining P1 state-sync gap.
- [x] **Step 3: Verify docs**
Run sentinel-language scan over edited docs.
---
## Self-Review
- Spec coverage: covers reducer evidence, UI settings persistence, and document updates.
- Placeholder scan: plan contains no incomplete implementation instructions.
- Type consistency: `UiThemeMode`, `themeMode`, and `saveThemeMode` names are used consistently.
@@ -0,0 +1,68 @@
# SWE.2/SWE.3 Baselines Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add reviewable SWE.2/SAD and SWE.3/SDD baselines so the DV document chain is no longer missing the architecture and detailed design layers.
**Architecture:** Keep SWE.2 in `docs/architecture/sad.md` and SWE.3 in `docs/architecture/sdd.md`, matching the README document tree. Update DV traceability and verification-plan wording to consume these baselines while preserving honest limitations for areas that still need deeper detail.
**Tech Stack:** Markdown, existing SysDes/SRS baselines, current Flutter/Rust workspace structure.
---
### Task 1: Write SWE.2 SAD Baseline
**Files:**
- Create: `docs/architecture/sad.md`
- [x] **Step 1: Create SAD with architecture views**
Write sections for purpose, upstream sources, components, static view, runtime flows, interface catalogue, dependency rules, non-functional allocation, architectural decisions, verification handoff, traceability, and open architecture risks.
### Task 2: Write SWE.3 SDD Baseline
**Files:**
- Create: `docs/architecture/sdd.md`
- [x] **Step 1: Create SDD with module designs**
Write sections for purpose, upstream sources, module catalogue, detailed API/data/state design, persistence, diagnostics, platform adapters, build/release design, verification hooks, traceability, and open detailed-design risks.
### Task 3: Update DV Traceability
**Files:**
- Modify: `docs/governance/traceability-matrix.md`
- Modify: `docs/verification/verification-master-plan.md`
- [x] **Step 1: Remove SAD/SDD missing limitation**
Update traceability text so it says SAD and SDD baselines exist, with known depth limitations instead of missing-document limitations.
- [x] **Step 2: Update verification plan inputs**
Update verification master plan to reference SAD and SDD as current inputs for SWE.4/SWE.5.
### Task 4: Verify SWE.2/SWE.3 Pack
**Files:**
- Inspect created and updated docs.
- [x] **Step 1: Sentinel-language scan**
Run the sentinel-language scan over `docs/architecture`, `docs/governance`, and `docs/verification`.
Expected: no matches introduced by this baseline pack.
- [x] **Step 2: File presence check**
Run: `ls docs/architecture`
Expected: `sad.md` and `sdd.md` are listed.
---
## Self-Review
- Spec coverage: creates SAD and SDD baselines and updates DV traceability consumers.
- Placeholder scan: no incomplete instructions are present.
- Type consistency: document names match README paths.