Argos is a desktop forensics tool that recovers permanently-deleted image files from storage devices. This document is the entry point for any AI agent working on this codebase. Read it in full before touching code.
Computer science and software engineering must always be respected. Bad practices — especially in low-level code — are abolished without exception.
If you cannot uphold this law for a given change, stop and ask. Do not approximate.
- Backend: Rust, single crate with modules. MSRV pinned in
rust-toolchain.toml. - Frontend: Tauri 2.x with TypeScript (strict mode).
- No workspace. Modules under
src/express the architecture; do not split into sub-crates without an ADR.
src/
├── main.rs — binary entry, wires bridge to runtime
├── lib.rs — re-exports the public surface for tests/bench
├── io/ — raw device access, read-only, sector-aligned
├── carve/ — carving engines (hdd/, ssd/), dispatch by device class
├── validate/ — Huffman validation, CRC32, format-specific checks
├── reassemble/ — PUP + SHT for HDD; trivial linear for SSD
├── custody/ — hashing, audit log, chain-of-custody artifacts
├── bridge/ — Tauri command handlers; sole FFI/IPC boundary
└── error.rs — crate-wide error enum (thiserror)
No comments. Anywhere. Ever. No //, no /* */, no /// doc-comments. The code is the documentation.
- If a block needs explanation, extract a named function or introduce a newtype.
- Architectural rationale belongs in
docs/, not in source. - This rule applies to Rust, TypeScript, and shell scripts. Configuration files (TOML, JSON) are exempt because comments there are inert.
Every line of code in Argos must be load-bearing and integrated. Two principles:
- No duplication. If the same logic appears in two places, it is a bug. Refactor on the second occurrence, not the third. Search before writing: a similar function may already exist. "Almost the same" with a different parameter is still duplication — generalize.
- No dead code. If a symbol is not reached from
main, the bridge, or a test, it does not belong in the codebase. Delete it.#[allow(dead_code)]is forbidden in non-test code.dead_code,unused_imports,unused_variables,unreachable_codeare denied at the crate level.cargo-machete(unused dependencies) andcargo-udepsare CI gates.- "We'll need this later" hooks are dead code by another name. Add them when you need them.
- Public API in
lib.rsre-exports only what tests, benchmarks, or the bridge actually consume. - TypeScript:
noUnusedLocals,noUnusedParameters,noFallthroughCasesInSwitchare strict; ESLintno-unused-varsis denied.
Duplicate detection is run in CI (e.g., tokei plus a similarity check) and any flagged hotspot blocks merge until refactored or justified.
- Source devices are opened read-only with direct I/O (
O_RDONLY | O_DIRECTon Linux,FILE_FLAG_NO_BUFFERINGon Windows). Output goes to a separate filesystem. - Every dump and every recovered artifact is hashed (SHA-256). Hashes and operations are logged to an append-only audit log.
- Bad sectors (
EIO) are recorded and skipped; never abort a recovery on them. - Operate at block level. Do not trust filesystem metadata for recovery decisions.
Forbidden in hot paths (carving, validation, matching, reassembly):
Vecreallocation in loops withoutwith_capacityor buffer reuse.clone()where&orCow<'_, T>would suffice.String/PathBufwhere&str/&Path/&[u8]work.Box<dyn Trait>in tight loops where generics monomorphize.- Unbuffered or unaligned I/O against raw devices.
asyncfor CPU-bound work (carving runs onrayon, nottokio).MutexwhereRwLock,parking_lot, atomics, or sharding fit better.- Per-fragment heap allocation (use
bytes::BytesMut, arena, or pooled buffers).
unsafeis allowed only when no safe alternative exists at the same performance level. Wrap eachunsafeblock in a function whose name and type signature encode the invariant (e.g.,read_into_aligned<const N: usize>(buf: &mut AlignedBuf<N>)). No// SAFETY:comments — refactor until the invariant is structural.- No
unwrap(),expect(), orpanic!outside tests and proven-by-construction invariants. - Errors via
thiserror(ArgosError). The binary may useanyhowonly at the top level. - Inputs from the bridge are validated against a typed scope; raw
Stringpaths from the frontend are rejected.
- IPC payloads >1 MiB go through
tauri::ipc::Responsewith raw bytes or stream events; never JSON-serialize large buffers. - Progress is pushed via events. The frontend never polls the backend.
- Capabilities are explicit allow-lists. No
fs:default. CSP is strict; nodangerousDisableAssetCspModification. - Lists of fragments/results are virtualized in the UI.
- Recovered images are served via
convertFileSrc/asset://, not loaded into JS memory. - EXIF and metadata are sanitized before render. No
dangerouslySetInnerHTML.
tracingwith structured fields. Never log: recovered content, user-supplied absolute paths, content hashes of recovered artifacts, or anything derivable from user data.- Logs are local, rotated, and redacted. No telemetry.
- Public functions return
Result<T, ArgosError>. panic!is reserved for invariants that, if violated, mean memory corruption or programmer error — never for user-input failures.
cargo clippy --all-targets -- -D warningsmust pass.cargo testandcargo test --releasefor hot-path code.cargo benchfor any change touchingcarve/,reassemble/, orvalidate/.cargo fmt --check,cargo deny check,cargo audit,cargo machete,cargo udepsare CI gates.cargo tauri devstarts the full application (backend + frontend).npm run buildandnpm run preview(infrontend/) are gates for frontend changes.
For domain-specific rules, agents read these (loaded via opencode.json):
.opencode/instructions/rust-style.md— allocation, unsafe, parallelism, testing, dead-code policy..opencode/instructions/tauri-patterns.md— IPC, capabilities, frontend..opencode/instructions/forensics-rules.md— read-only access, custody, bad sectors..opencode/instructions/algorithms-spec.md— PUP, SHT, Huffman validation, Aho–Corasick contracts..opencode/instructions/security-baseline.md— dependencies, threat-model anchors.
Architectural decisions are recorded as ADRs under docs/decisions/. If a change contradicts an ADR, write a new ADR superseding it — do not silently diverge.