# AGENTS.md — `crates/vnidrop` (Rust core) Nearest guide when editing under `crates/vnidrop/`. Root [`AGENTS.md`](../../AGENTS.md) still applies; this file wins for Rust-specific commands and conventions. --- ## Purpose This crate is the transfer backend exposed to Kotlin via UniFFI (`VnidropCore`). It owns Iroh, blobs, SQLite history, tickets, approval handshake, and file streaming. Read [`CORE_FLOW.md`](CORE_FLOW.md) before changing send/receive/export/cancel. --- ## Commands (run from repo root) Always prefer workspace commands so lockfile/fmt stay consistent: ```bash make format make test-rust make check-rust ``` Focused integration suites: ```bash make test-rust-transfer make test-rust-approval make test-rust-lifecycle make test-rust-output-sink ``` Docs (CI uses `-D warnings`): ```bash RUSTDOCFLAGS='-D warnings' cargo doc -p vnidrop --no-deps ``` Run `make format` after finishing Rust edits without asking. --- ## Module layout ``` src/ runtime/ mod.rs # CoreInner, startup recovery, emit helpers facade.rs # UniFFI surface, block_on, cancel entry share.rs # share / import receive.rs # receive, download, export, OutputSinkFile lifecycle.rs # cancel share, delete, status, access mode, shutdown provider.rs # provider messages, per-peer transfer progress filesystem.rs # collect sources, atomic publish, path rules repository.rs # SQLite approval.rs / handshake.rs / ticket.rs / access_policy.rs / event_hub.rs api.rs # UniFFI records/enums tests/ # crate-private unit tests tests/ # public-API integration tests + support/ ``` **Do not** reassemble a single huge `runtime.rs`. Prefer new focused modules if a file approaches ~800 LoC of non-test code. --- ## Hard constraints 1. **Public API stability:** UniFFI surface changes break Kotlin. Prefer additive changes; update shared Kotlin call sites in the same change when required. 2. **Streaming stays in Rust.** Platform passes paths or FDs; core does not pull whole files into Kotlin. 3. **Android FDs are files only.** `SourceKind::FileDescriptor` with `is_directory=true` must fail; directories are expanded on the platform side. 4. **Cancel:** signal active-transfer oneshot **synchronously** before async DB work. Use existing `take_active_transfer` / facade cancel path. Do not reintroduce nested exclusive `Runtime::block_on` deadlocks. 5. **No lock across await:** Clippy `await_holding_lock` fails CI. 6. **ReceiveOutputSink:** after successful `start_file`, exactly one of `finish_file` or `abort_file` (see `OutputSinkFile` Drop). 7. **No-overwrite publish** for path receives (temp + hard link / exclusive rename). 8. Integration tests must use the **public** API + `tests/support/` only. --- ## Code style (Rust) - Clippy clean with `-D warnings`. - Prefer exhaustive `match`; avoid catch-all arms that hide new enum variants. - Prefer comparing whole objects in tests when practical. - Comment only non-obvious why (concurrency, durability, platform FS quirks). - Do not add one-off private helpers used once if inline is clearer. - Prefer private modules; export deliberately via `lib.rs` / UniFFI. --- ## Testing - Unit / private: `src/tests/` (see crate `tests.rs` paths). - Integration: `tests/*.rs` + `tests/support/mod.rs` (`TestNode`, `MemoryOutputSink`, `CoreGuard`, etc.). - Bug fixes need a regression test. - Prefer gates/latches over multi-second sleeps (see output_sink cancel test). - Failure tests: durable status and/or events when applicable. - Recovery tests: shut down core, reopen same data dir. Details: [`tests/README.md`](tests/README.md). --- ## PR / verify checklist for this crate ```bash make check-rust ``` If you touched cancel, export, or sinks, also: ```bash make test-rust-output-sink ```