- BTreeMap over HashMap when iteration order is observable
- tokio for all concurrency — no
thread::spawn
- No
rand crate — all randomness via CrawlSeed -> ChaCha8Rng
- No
Instant::now() in core logic — time from ExecutionEnvelope or caller
- Atomics for counters/metrics only, never for control flow
- All errors typed as
PalimpsestError variants — no anyhow or eyre in library crates
- No
.unwrap() or .expect() in library code — binary crates may use them in main() only
- Every
? propagation must preserve the error taxonomy
panic! is a bug report, not control flow
bytes::Bytes for buffers crossing async boundaries
- Zero-copy:
&[u8] > Vec<u8> > String
- No
Clone on large types — use Arc<T> for shared ownership
- Pre-allocate buffers in hot paths
serde derive on all types crossing crate boundaries
#[serde(rename_all = "snake_case")] on enum variants
- JSON for human-readable formats
- Never change serialized field names without a migration plan
- Newtypes for domain concepts:
ContentHash, CaptureInstant, CrawlSeed, CrawlContextId
- Parse, don’t validate: constructors enforce invariants
Copy for small values (hashes, timestamps, IDs)
#[non_exhaustive] on public enums that may grow
- Every public function has at least one test
- Property-based tests (
proptest) for data transformation functions
- Snapshot tests (
insta) for serialization formats
- Determinism tests: same seed = byte-identical output