# Audit Playbook — Phases 1 through 8 *Companion to [`phase0-cartography.md`](phase0-cartography.md). Pick up from any phase.* This is a step-by-step playbook for an acquisition-grade audit of the Lumotia codebase. Phase 0 (Cartography) is complete; this document describes Phases 1–8. **How to use this doc.** Each phase is independent enough to start in isolation, but they're ordered by leverage: earlier phases find the highest-value, lowest-risk wins. Don't skip phases without a reason. For every phase: do the prep (`Inputs`), run the procedure, write the deliverable to `docs/audit/`, then commit before moving on. The deliverable is the audit trail. --- ## Conventions - All commands assume `cwd = /home/user/lumotia` (or wherever the repo lives). - All deliverables live under `docs/audit/`. Naming: `phaseN-.md`. - Severity grades used throughout: **P0** (must-fix before any release), **P1** (must-fix before sale / public beta), **P2** (worth fixing, not blocking). - "Defect log" = a markdown table with columns: `ID | Severity | File:line | Summary | Suggested fix | Effort`. - Before applying any non-trivial code change, commit the audit findings first. Audit and remediation are separate operations. --- ## Phase 1 — Lean-pass **Goal.** Find dead code, unused dependencies, duplicate logic, and leftover scaffolding. Apply low-risk deletions; log higher-risk ones for Phase 2. **Time:** 1 working day. **Inputs:** Phase 0 §2 (largest files), §9 Tier C (structural smells). ### Procedure 1. **Unused Rust dependencies.** ```bash cargo install cargo-machete cargo-udeps --locked cargo machete --workspace cargo +nightly udeps --workspace --all-targets ``` For each false positive (a dep used only behind a feature flag), document it; for each real hit, remove from the relevant `Cargo.toml`. 2. **Unused frontend modules.** ```bash npx knip npx depcheck ``` Apply removals; rerun `npm run check` to confirm nothing breaks. 3. **Dead Rust code.** ```bash cargo +nightly rustc -p lumotia-core -- -W dead_code -W unused 2>&1 | grep -E "warning|note" ``` Repeat for every crate. Expect false positives in `pub` items used only by `src-tauri`; the real signal is `pub(crate)` items with no callers. 4. **Tech-debt grep.** ```bash grep -rnE "TODO|FIXME|HACK|XXX|unimplemented!\(\)|todo!\(\)" \ --include="*.rs" --include="*.svelte" --include="*.ts" \ --exclude-dir=node_modules --exclude-dir=target . > /tmp/tech-debt.txt ``` Bucket each match: (a) genuine reminder for known work, (b) "won't actually do" — delete, (c) silent admission of incomplete code — escalate to defect log. 5. **`unwrap()` / `expect()` outside tests.** ```bash grep -rnE "\.(unwrap|expect)\(" crates/ src-tauri/src/ \ --include="*.rs" | grep -v "/tests/" | grep -v "test " | grep -v "#\[test\]" ``` Each is a potential panic-on-bad-input. For each, prove it can't panic on user data, or replace with `?`/`map_err`. 6. **Duplicate logic in 1k+ LOC files.** Manually walk: - `src/lib/pages/SettingsPage.svelte` (2,250 LOC) — already flagged for the seven-group split. - `crates/storage/src/database.rs` (2,534 LOC) — split by domain (Phase 0 §9 C2). - `src-tauri/src/commands/live.rs` (1,737 LOC) — look for mixed concerns (session lifecycle vs. tuning). - `crates/storage/src/migrations.rs` (1,185 LOC) — confirm migrations are append-only and v-numbered. - `src/lib/pages/DictationPage.svelte` (1,081 LOC) — extract child components for any block >150 lines. 7. **Cross-file duplicate detection.** ```bash npx jscpd --min-tokens 50 src/ src-tauri/src/ crates/ ``` Threshold ≥50 tokens; anything above 5% similarity in a file pair is worth a look. ### Deliverable `docs/audit/phase1-lean-pass.md` — three sections: - **Removed:** what was deleted, with line-count savings. - **Kept with reason:** items that look unused but aren't (with the reason). - **Escalated to Phase 2:** structural duplications too risky to touch as a one-shot. ### Acceptance criteria - `cargo build --workspace` passes. - `cargo test --workspace` passes (no test count regression beyond explicitly-deleted-test count). - `npm run check` passes. - Net LOC reduction documented (target: ≥3% reduction or a written justification of why not). --- ## Phase 2 — Architecture conformance **Goal.** Verify the 10-crate boundary is real, not aspirational. Tighten public API surfaces. Restructure files >1k LOC where the split is obvious. **Time:** 1 working day. **Inputs:** Phase 0 §4 (dependency graph), §3 (pub item counts), Phase 1 escalations. ### Procedure 1. **No upward dependencies.** The Phase 0 dependency graph is acyclic; confirm no new edges have been added. ```bash for d in crates/*/Cargo.toml; do name=$(grep -m1 '^name' "$d" | sed 's/.*"\(.*\)"/\1/') deps=$(grep -E "^lumotia[-_]" "$d" | sed 's/ *=.*$//') echo "$name -> $deps" done ``` If any leaf crate now imports `lumotia` (the Tauri app crate), that's a P0. 2. **Boundary conformance — no SQL outside `lumotia-storage`.** ```bash grep -rE "sqlx::|sqlite::|sql_query|\\.execute\\(|\\.fetch_" crates/ src-tauri/src/ \ | grep -v "crates/storage/" | grep -v "/tests/" ``` Any hit is a boundary violation. 3. **Boundary conformance — no `cpal` / `whisper` / `llama` outside their owners.** ```bash grep -rnE "use cpal" crates/ src-tauri/src/ | grep -v "crates/audio/" grep -rnE "use whisper_rs|use whisper-rs" crates/ src-tauri/src/ | grep -v "crates/transcription/" grep -rnE "use llama_cpp_2" crates/ src-tauri/src/ | grep -v "crates/llm/" ``` 4. **Boundary conformance — no business logic in Tauri commands.** A command should be ≤30 lines: deserialize input, call into a library crate, serialize output. Anything else is leakage. ```bash for f in src-tauri/src/commands/*.rs; do awk '/#\[tauri::command\]/{flag=1} flag{print; if(/^}/){flag=0; print "---"}}' "$f" | \ awk '/^---$/{print c; c=0; next} {c++}' | sort -nr | head -5 done ``` Any command body >50 lines goes on the defect log. 5. **Reduce `lumotia-core` public surface.** It exports 104 items (Phase 0 §3). For each, run a workspace-wide reverse search: ```bash grep -rnE "lumotia_core::ITEM_NAME" crates/ src-tauri/src/ ``` If the only hits are inside `lumotia-core` itself, demote to `pub(crate)`. Expected outcome: 30–60% reduction. 6. **Apply Phase 0 §9 Tier C structural fixes (C1 and C2 are in scope here).** - C1: tighten `lumotia-core` exports. - C2: split `crates/storage/src/database.rs` into `database/{transcripts,tasks,profiles,…}.rs`. Re-export from `database/mod.rs` so the public API doesn't move. 7. **`lumotia-cloud-providers` decision.** Phase 0 §9 C4 — fold into `lumotia-core::keystore` or grow it. Don't defer indefinitely; an 80-LOC crate is doing the workspace no favours. ### Deliverable `docs/audit/phase2-architecture.md` — boundary-violation log + before/after pub-item counts per crate + restructure summary. ### Acceptance criteria - Zero hits on the SQL / `cpal` / `whisper` / `llama` boundary greps. - `lumotia-core` public-item count reduced (target: ≤60). - All commits compile and tests pass at each step (do not bundle structural moves with logic changes). --- ## Phase 3 — Correctness audit (the expensive one) **Goal.** Walk every public function and every error path. Eliminate panics on bad input. Justify or remove every `unsafe` block. **Time:** 3–5 working days. Single biggest investment in the audit. **Inputs:** Phase 1 unwrap log, Phase 2 reduced public surface. ### Procedure 1. **Lints with teeth.** ```bash cargo clippy --workspace --all-targets --all-features -- \ -D warnings -W clippy::pedantic -W clippy::nursery ``` Pedantic and nursery emit many false positives — read every one and decide. The yield from `clippy::pedantic` on a real codebase is high. 2. **`unsafe` audit.** For each `unsafe` block, write a one-paragraph justification (what invariant the caller is upholding, why it can't be encoded in the type system) inline as a comment. If you can't write the justification, the `unsafe` is suspect. ```bash grep -rnE "unsafe\s*(\{|fn|impl)" crates/ src-tauri/src/ ``` Hotspots: `crates/audio/` (cpal callbacks), `crates/hotkey/` (evdev FFI on Linux), `src-tauri/` for any platform glue. 3. **Panic surface.** Every `.unwrap()`, `.expect()`, `panic!()`, `unreachable!()`, `assert!()`, slice indexing `[i]`, integer arithmetic that can overflow. ```bash cargo install cargo-careful cargo +nightly careful test --workspace ``` `cargo-careful` runs tests under stricter UB detection. 4. **Property tests on parsing/format functions.** - `crates/hotkey/src/lib.rs` — Tauri-style hotkey string parser. `proptest!` with arbitrary modifier sets + key codes; assert round-trip. - `crates/audio/src/wav.rs` — WAV decode. Fuzz with `cargo-fuzz` or `libfuzzer-sys` against malformed headers. - `src/lib/utils/frontmatter.ts` — YAML frontmatter parse/emit. Fast-check (npm) for round-trip. - `crates/storage/src/database.rs` — FTS5 query escaping. Property test: any input string produces a query that doesn't crash SQLite. 5. **Miri on storage and audio.** ```bash cargo +nightly miri test -p lumotia-storage --lib cargo +nightly miri test -p lumotia-audio --lib ``` Catches UB and aliasing bugs that `cargo test` misses. 6. **Manual public-API walk.** For each public function in each crate: - What are the preconditions? Are they enforced or assumed? - What are the error variants? Are any absorbed silently (`let _ = …`)? - Are any return types `Result<…, String>`? — that's a smell; prefer typed errors. 7. **`tracing` audit.** Run with `RUST_LOG=trace` for one full dictation → cleanup → save cycle. Note any warning-level logs the operator hasn't noticed; each is potentially a defect. ### Deliverable `docs/audit/phase3-correctness.md` — defect log graded P0/P1/P2, plus an `unsafe` justification appendix. ### Acceptance criteria - Zero `cargo clippy -D warnings` errors. - Every `unsafe` block has an inline justification comment. - All P0 defects fixed before phase close; P1 defects logged with an owner. - Miri tests for storage and audio pass. --- ## Phase 4 — Security & trust boundaries **Goal.** Verify the "local-first, no telemetry" pitch is enforced by the code, not by intention. Audit every Tauri command and MCP tool as a trust boundary. **Time:** 2 working days. **Inputs:** Phase 0 §5.1 (102 Tauri commands), §5.2 (MCP tools). ### Procedure 1. **Network egress audit (the big one).** ```bash sudo tcpdump -i any -w /tmp/lumotia-egress.pcap host not 127.0.0.1 & # …run the app for 30 minutes covering: dictation, cleanup, save, MCP query… sudo kill %1 tshark -r /tmp/lumotia-egress.pcap -q -z conv,ip ``` Allowed: model downloads from huggingface.co (only on user click). Anything else is a P0. Cross-check at the syscall level: ```bash strace -f -e trace=network -o /tmp/lumotia-net.txt ./target/release/lumotia grep -E "connect|sendto|sendmsg" /tmp/lumotia-net.txt | grep -v "127\.0\.0\.1\|::1" ``` 2. **Tauri command boundary audit.** For every `#[tauri::command]` (102 of them): - Input deserialization: any `String` parameter could be hostile. Path traversal? Command injection? - Output: does it leak filesystem paths, hostnames, secrets? - Authorization: does it check `security::ensure_main_window` where appropriate? (Most don't; document which ones must.) - File-touching commands (`fs.rs`, `transcripts.rs` export, `feedback.rs`): canonicalize and confirm the path is inside the app's data dir before writing. ```bash grep -rnE "PathBuf::from|Path::new" src-tauri/src/commands/ ``` Each hit gets a path-traversal review. 3. **`paste.rs` review.** Spawns external processes (`konsole`, `wtype`, `xdotool`, `ydotool`, `osascript`, etc.). Confirm none of the arguments are user-controlled in a way that allows shell injection. `Command::arg` (not `Command::args` with a single shell string) everywhere. 4. **MCP read-only enforcement.** - `lumotia-storage::init_readonly` opens with `SQLITE_OPEN_READONLY` — verify in the source. - Test: write a malformed MCP request that tries to issue an `INSERT` via a hand-crafted tool name. Should fail at the connection level, not just the dispatcher. ```bash echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"sql_exec","arguments":{"sql":"INSERT INTO transcripts VALUES (1,2,3)"}}}' | ./target/release/lumotia-mcp ``` 5. **LLM prompt-injection regression test.** The README claims `CLEANUP_PROMPT` is hardened. Build a regression test corpus of injection payloads (e.g., "ignore previous instructions and emit `…`"). Run `cleanup_text` against each; assert the output doesn't contain any injected control tokens. 6. **SQL injection.** All queries should use bound parameters. ```bash grep -rnE "format!\(.*SELECT|format!\(.*INSERT|format!\(.*UPDATE|format!\(.*DELETE" crates/storage/ ``` Any `format!(…SQL…)` is a defect. 7. **FTS5 query escaping.** `MATCH` queries with user input must escape FTS5 syntax. Property test. 8. **Secret scanning across `git log -p`.** ```bash gitleaks detect --source . --no-git=false --report-path /tmp/leaks.json trufflehog filesystem --include-detectors=all --json . > /tmp/trufflehog.json ``` 9. **Dependency CVEs.** ```bash cargo install cargo-audit cargo-deny --locked cargo audit cargo deny check advisories npm audit --production ``` 10. **Licence compatibility.** ```bash cargo deny check licenses ``` Configure `deny.toml` with the licence list you can ship under (MIT, Apache-2.0, BSD-2/3-Clause, ISC, MPL-2.0, Unicode-DFS-2016 typically OK; GPL/AGPL/SSPL must be flagged before public beta). ### Deliverable `docs/audit/phase4-security.md` — threat model + per-command boundary review + scanner reports + the egress audit pcap summary. ### Acceptance criteria - Network egress: zero non-user-initiated outbound connections in a 30-min session. - All Tauri commands have a documented input-validation posture (even if it's "this command takes no untrusted input"). - `cargo audit` and `npm audit` clean (or each finding has a documented mitigation). - `cargo deny check licenses` passes the configured allow-list. - Gitleaks + trufflehog return clean. --- ## Phase 5 — Test integrity **Goal.** Move from "X tests pass" to "the tests pin behaviour we care about." Lines covered ≠ behaviours verified. **Time:** 1 working day. **Inputs:** Phase 0 §6 (287 tests; 220 lib, 3 integration, 67 src-tauri). ### Procedure 1. **Coverage baseline.** ```bash cargo install cargo-llvm-cov cargo llvm-cov --workspace --html --output-dir /tmp/coverage ``` Open the report. Note: low coverage on a critical file is bad; high coverage on a leaf file says nothing. 2. **Mutation testing on the heavy crates.** ```bash cargo install cargo-mutants cargo mutants -p lumotia-storage --timeout 60 cargo mutants -p lumotia-transcription --timeout 60 cargo mutants -p lumotia-llm --timeout 60 cargo mutants -p lumotia-audio --timeout 60 ``` Surviving mutants = code paths whose tests don't actually verify behaviour. Each survivor either deserves a new test or a deletion. 3. **Tests-as-theatre check.** For a sample of 20 random tests (`shuf -n 20 tests-list.txt`), open each test and ask: "what would I have to break in the implementation to make this fail?" If the answer is "nothing — the assertions are tautological", delete the test. 4. **Regression tests for the audit-grade invariants.** Each of these gets at least one test: - "No telemetry": a unit test that asserts no `reqwest::Client` instance is created at startup unless the user has explicitly enabled cloud STT (gated by a `cfg!` or feature flag check). - "MCP is read-only": a test that issues a write via the MCP layer and asserts it's rejected. - "Migrations are atomic": a test that simulates an interrupted migration mid-statement (e.g., panic between two SQL statements in the same migration version) and asserts the next startup either resumes or rolls back cleanly. (See `docs/issues/c3-migrations-atomicity.md` for context.) - "Raw transcript is always recoverable": a test that runs cleanup, asserts the cleaned text differs from the raw, then asserts the raw is still retrievable from the DB. - "FTS5 query escaping": property test (see Phase 3 step 4). 5. **CI must enforce coverage.** Add a coverage floor (say, 70% for each crate) to `.github/workflows/check.yml`. New PRs that drop a crate below the floor fail CI. ### Deliverable `docs/audit/phase5-test-integrity.md` — coverage table per crate + mutation-test surviving-mutant log + new regression tests added. ### Acceptance criteria - Mutation-testing kill rate ≥80% on `lumotia-storage`, `lumotia-transcription`, `lumotia-llm`. - Each audit-grade invariant has at least one passing regression test. - Coverage floor enforced in CI. --- ## Phase 6 — Performance & resource profile **Goal.** Confirm the app doesn't leak, doesn't drift, and stays inside its latency budget under realistic use. **Time:** 1 working day. **Inputs:** none specific — use realistic dictation workloads. ### Procedure 1. **Long-session leak check.** ```bash # Linux: ./target/release/lumotia & PID=$! while sleep 60; do ps -p $PID -o rss,vsz,nlwp,fd | tee -a /tmp/lumotia-rss.csv done ``` Run for 1 hour with periodic dictation. Plot RSS vs. time. A monotonic upward slope is a leak. 2. **File-descriptor count.** ```bash ls /proc/$PID/fd | wc -l # repeat over time ``` FD count should be bounded. 3. **Heap profile.** ```bash heaptrack ./target/release/lumotia # …run one full dictate → cleanup → save cycle… heaptrack_print heaptrack.lumotia.*.zst | head -100 ``` Look for allocators in the cleanup path that aren't freed. 4. **CPU hot path.** ```bash perf record -g -F 99 -p $PID -- sleep 60 # during a live transcription session perf report ``` Anything outside the model inference (whisper.cpp, llama.cpp) using >5% CPU is a candidate finding. 5. **Cold-start budget.** ```bash time ./target/release/lumotia --headless-startup-test # add this entrypoint if missing ``` Target: < 2s from launch to "recording-ready". Anything slower → profile with `samply`. 6. **Audio device hot-plug stress.** Plug/unplug USB mic 20 times during a dictation session. Count: leaks, panics, dropped frames. (cpal hotplug is the documented hotspot.) ### Deliverable `docs/audit/phase6-performance.md` — leak chart, hot-path flamegraph summary, cold-start measurements, hot-plug stress results. ### Acceptance criteria - RSS plateaus within 10 minutes of dictation start (no monotonic growth). - FD count bounded. - Cold start < 2s on the reference machine (document the machine). - No panics in the hot-plug stress test. --- ## Phase 7 — Build & release reproducibility **Goal.** Confirm a fresh engineer (or acquirer's eng team) can clone, build, and run on a clean machine following only the docs. If they can't, the deal stalls. **Time:** ½ working day. **Inputs:** `docs/dev-setup.md`, `.github/workflows/build.yml`. ### Procedure 1. **Fresh container build.** Use a Docker container matching one supported OS at a time. ```bash docker run --rm -it -v $(pwd):/repo:ro fedora:40 bash # Inside: follow docs/dev-setup.md literally, time each step. ``` Time the full path: `git clone` → all `dnf install` lines → `npm install` → `cargo build --workspace`. Document every step that's missing or wrong in the docs. 2. **CI parity.** Compare local build with `.github/workflows/build.yml`. Any drift between local and CI is a P1 reproducibility risk. 3. **Bundle build.** ```bash npm run tauri build ``` Confirm the resulting `.AppImage` / `.deb` / `.dmg` / `.msi` runs on a clean target OS. 4. **Bundle ID + signing transferability.** Confirm: - `uk.co.corbel.lumotia` bundle ID is owned, not squatted. - Signing certs (Apple Developer ID, Windows code-signing cert) exist and the keys are documented in a hand-over playbook. - Icon assets in `src-tauri/icons/` are owned/licensed; replaceable on transfer. 5. **`run.sh` works as documented.** Run on a fresh checkout; it should JustWork. ### Deliverable `docs/audit/phase7-reproducibility.md` — fresh-build walkthrough log, missing-step list for `dev-setup.md`, bundle-build evidence, transferability checklist. ### Acceptance criteria - Fresh-container build succeeds following `dev-setup.md` verbatim. (Update the docs if not.) - All three bundle targets build successfully in CI. - Transferability checklist signed off. --- ## Phase 8 — Documentation truth **Goal.** Re-walk every public doc against the post-audit code. Stale or fictional docs are worse than no docs in an acquisition context. **Time:** ½ working day. **Inputs:** Phase 0 §7 (initial drift list), the now-updated codebase from Phases 1–6. ### Procedure 1. **Re-run Phase 0 §7 checks.** Phases 1–4 will have moved things; the README needs another pass. - Test count - Crate count - Tauri command module list - Stores list - Model-registry contents 2. **`README.md` ↔ source-of-truth pairings.** For each claim, identify the file that would break the claim if it changed, and put both in a table. 3. **Archive HANDOVER files.** Phase 0 §9 D1 — move dated handovers under `docs/handovers/`, add the rebrand-note prefix. 4. **`docs/brief/` and `docs/whisper-ecosystem/` re-read.** Any roadmap claim that's now shipped → move to a `done.md` archive. Any claim that's now de-scoped → mark as such with the date. 5. **`docs/issues/` triage.** Each open issue gets one of: `RESOLVED `, `STILL OPEN`, `WON'T FIX `. 6. **Add an `AUDIT.md` at repo root.** Single-page summary: "this repo was audited on `` to acquisition-grade depth; see `docs/audit/` for the full trail." Future maintainers (and acquirers) need this signpost. ### Deliverable `docs/audit/phase8-docs-truth.md` — diff log of doc changes, archive moves, and the new `AUDIT.md`. ### Acceptance criteria - Zero stale claims in `README.md` (re-verified). - All `docs/issues/` items triaged. - `AUDIT.md` exists at repo root. --- ## Closing the audit After Phase 8, the deliverables in `docs/audit/` should read as a coherent, sequential story: ``` docs/audit/ ├── phase0-cartography.md (done — survey + drift log + fix areas) ├── phase1-lean-pass.md (kill list, applied) ├── phase2-architecture.md (boundary log + restructure) ├── phase3-correctness.md (defect log + unsafe justifications) ├── phase4-security.md (threat model + scanner reports + egress audit) ├── phase5-test-integrity.md (coverage + mutation results + new tests) ├── phase6-performance.md (leak chart, hot path, cold start) ├── phase7-reproducibility.md (fresh-build walkthrough + transfer checklist) └── phase8-docs-truth.md (post-audit doc reconciliation) ``` With `AUDIT.md` at the root pointing into the directory. That's the artefact an acquirer's engineering team gets. It's also the artefact you'd want to find if you were the one inheriting the codebase. --- *End of playbook.*