diff --git a/AGENTS.md b/AGENTS.md index ca6831b41..021ccd248 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,14 +93,16 @@ If you find an asset that breaks these rules, stop and remove it from the reposi ## 4. Never crash -PrintCraft opens files from strangers. A malformed PDF, a bad tool argument or a corrupt settings file must produce an error the user (or agent) can act on, never a crash. +PrintCraft opens files from strangers, and people trust it with their work. A malformed PDF, a bad tool argument, a corrupt settings file or a full disk must produce an error the user (or agent) can act on, never a crash and never lost work. **This outranks feature work:** don't ship a feature by adding a panic path, and fix a crash before building on top of it. The shared standard for every Crafting App is [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md). - **Fail with `Result`.** Non-test code returns errors through the crate's error type and `?`. Where readers are lenient by design, fall back gracefully and record the repair (see Fidelity in `CLAUDE.md`). - **No panicking shortcuts.** No `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!` or `unimplemented!` outside tests, unless the call is provably infallible (e.g. writing to a `Vec`), with a comment saying why. `assert!` only for internal invariants that input cannot reach. +- **No `unsafe`.** The workspace sets `unsafe_code = "forbid"`. If a platform API needs it, use a maintained safe wrapper crate; a real exception needs the owner's sign-off. - **Treat every input-derived number as hostile.** Use `get()` instead of `[i]` or `[a..b]`. Slice strings only at char boundaries. Use checked/saturating arithmetic for lengths, offsets and counts. Guard against division by zero and NaN/inf casts, and never cast a negative value to `usize`. Cap allocations sized by the file. - **Bound recursion.** Object graphs can be cyclic or deeply nested. Walk them with seen-sets or depth limits. -- **Don't cascade.** Handle lock poisoning, and keep a panic in a worker thread from taking the app down. Engine entry points that run untrusted-document code keep their last-resort `catch_unwind` guard. It is a safety net, not a substitute for the rules above. +- **Don't cascade.** Handle lock poisoning, and keep a panic in a worker thread from taking the app down. Engine entry points that run untrusted-document code keep their last-resort `catch_unwind` guard, and command execution and file import/export must sit behind one that turns an escaped panic into an error message and keeps the user's document. Keep `panic = "unwind"` (the default) so the guard works. It is a safety net, not a substitute for the rules above. - **Prove it.** Every crash fix comes with a small synthetic regression test that panicked before the fix. Turn fuzzer findings (`cargo xtask fuzz`) into such tests before fixing them. +- **Enforced by clippy.** Root `clippy.toml` lets tests use `unwrap`/`expect`/`panic!`. A crate that is clean carries `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]`; new crates start with it. ## 5. Everything else diff --git a/CLAUDE.md b/CLAUDE.md index 6e46da0d2..9e5e674f4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,13 +19,14 @@ PrintCraft is a clean-room, open-source, Rust-native PDF application targeting A - **Privacy.** When observing Acrobat, use synthetic fixtures only. Never capture the Home view, recent files or account info. Capture by window id (`plan/acrobat/tools/`). Never commit Acrobat outputs, corpus files or personal data. - **Layering.** Nothing below L7 depends on egui/winit/eframe/rfd (`plan/architecture.md` §3). `cos`/`filters`/`crypt`/`arlington` stay standalone. - **Fidelity.** The PDF object graph is the model. Preserve unknown data. Saves are incremental unless a full rewrite is required. Never silently drop or repair data without recording it. -- **Never panic.** Every PDF, script, MCP call, settings file and keystroke is untrusted input, and none of it may crash the app. Return `Result` (the crate's error enum) and propagate with `?`, or fall back leniently and record it. In non-test code: +- **Never panic.** Every PDF, script, MCP call, settings file and keystroke is untrusted input, and none of it may crash the app or lose the user's work. This outranks feature work. Return `Result` (the crate's error enum) and propagate with `?`, or fall back leniently and record it. In non-test code: - No `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!` unless it is provably infallible, with a comment saying why. - No indexing or slicing with input-derived positions (use `get`, and slice strings only at char boundaries). - Use checked or saturating arithmetic on input-derived numbers, and guard against division by zero and NaN/inf casts. - Cap allocations sized by input, and bound recursion with depth limits or seen-sets. - Handle lock poisoning. - - Every crash fix gets a synthetic regression test. See `AGENTS.md` §4. + - No `unsafe` (`unsafe_code = "forbid"`). + - Every crash fix gets a synthetic regression test. See `AGENTS.md` §4 and `craftrules/standards/never-crash.md`. - **Rust only** in the product and build (`xtask`). No handwritten JS/TS. - **Quality gates** before every commit: `cargo fmt --check`, `cargo clippy --workspace -- -D warnings`, `cargo test --workspace`, and the wasm check once `xtask ci` exists (M0). - **Commits:** one task id per commit (e.g. `M1.4: xref stream reader`). Commit only green states. End messages with the attribution line required by the environment. diff --git a/Cargo.toml b/Cargo.toml index b112b06d5..238095f3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ egui_kittest = "0.36" rfd = "0.17" [workspace.lints.rust] -unsafe_code = "deny" +unsafe_code = "forbid" [workspace.lints.clippy] dbg_macro = "warn" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 000000000..4b6c79f55 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,4 @@ +allow-unwrap-in-tests = true +allow-expect-in-tests = true +allow-panic-in-tests = true +allow-indexing-slicing-in-tests = true