Guidelines for AI agents working on the lading codebase. For detailed rationale behind these rules, see the Architecture Decision Records.
| Need | Answer |
|---|---|
| Validate changes | ci/validate (mandatory, never skip) |
| Run tests | ci/test |
| Run Kani proofs | ci/kani <crate> |
| Shape rule | 3+ repetitions = create abstraction |
| Dependencies | When in doubt, implement rather than import |
| HashMap allowed? | No - use IndexMap/IndexSet for determinism |
| Task | Key Rules | ADRs |
|---|---|---|
| Adding a generator | Pre-compute payloads, deterministic RNG, no panics, property tests for determinism | 001, 002, 003, 006 |
| Modifying payload generation | Property tests for invariants, deterministic output, no wall-clock time | 002, 003, 006 |
| Adding a blackhole | Must never backpressure target, property tests for throughput guarantees | 001, 005, 006 |
| Modifying throttle | Kani proofs required, formal invariants | 003, 005, 006 |
| Core algorithm changes | Proofs where feasible, property tests otherwise | 006 |
| Writing tests | Property tests default, document invariants | 006 |
| Adding a dependency | Evaluate: performance, determinism, control | 008 |
| Code style questions | Shape rule, no mod.rs, imports at top | 007 |
Generator --> Target --> Blackhole
- Generators: Create deterministic load, push to targets
- Targets: Programs being tested (subprocesses)
- Blackholes: Receive output from targets
Key principle: Pre-compute everything possible. See ADR-001, ADR-002.
Lading must be:
- Faster than targets - users must never wonder if lading is the bottleneck
- Deterministic - same seed = same load = reproducible results
- Extensible - support variety of protocols and use-cases
- Pre-allocate when size is known
- No allocations in hot paths
- Prefer worst-case over average-case algorithms
- No optimization without profiling data
See ADR-005.
When writing generators or payload code:
- Use explicit seed from configuration (never
thread_rng()) - Use
IndexMap/IndexSetoverHashMap/HashSet - No wall-clock time in payload generation
- Same seed must produce byte-identical output
See ADR-003.
- MUST NOT panic - use
Result<T, E>always - No
.unwrap()or.expect() - Propagate errors with context using
thiserror
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("failed to connect to {addr}: {source}")]
Connect { addr: SocketAddr, source: io::Error },
}See ADR-004.
- No
mod.rs- usefoo.rsnotfoo/mod.rs - Imports at top - never inside functions
- Named format args -
"{index}"not"{}" - Shape rule: 3+ repetitions = create abstraction
- No internal backward compatibility - only user configs need stability
See ADR-007.
Hierarchy: Proofs > property tests > unit tests
- Proofs are preferred for all code, but Kani proofs can run for unbounded time
- Trade-off: we require proofs for critical components where feasible
- The whole program must be correct, not just the throttle - generators, payload generation, blackholes, and protocol implementations all have correctness requirements
- Where proofs are impractical, use property tests via proptest
- Unit tests only for simple pure functions
Invariant documentation required - use formal mathematical language:
/// For all request > max_capacity: request(ticks, request) returns Err
///
/// Capacity requests exceeding maximum always fail.
#[kani::proof]
fn request_too_large_always_errors() { ... }See ADR-006 for patterns and examples.
Evaluate before adding: performance, determinism, control, criticality. When in doubt, implement rather than import.
See ADR-008.
Document why, not what. The code shows what; comments explain why.
//!for crate/module level///for types and functions- Functions: imperative verb,
# Errors,# Panicssections - US-ASCII only - no Unicode characters in code or documentation
/// Send payload bytes to the target.
///
/// # Errors
///
/// Returns error if connection is closed or write times out.
pub async fn send(&mut self, payload: &[u8]) -> Result<(), Error> { ... }MUST run ci/validate after any code changes.
| Task | Command |
|---|---|
| Full validation | ci/validate |
| Format | ci/fmt |
| Check | ci/check |
| Lint | ci/clippy |
| Test | ci/test |
| Kani proofs | ci/kani <crate> |
| Outdated deps | ci/outdated |
| Benchmarks | cargo criterion |
| Environment | PROPTEST_CASES | PROPTEST_MAX_SHRINK_ITERS |
|---|---|---|
Local (ci/test) |
64 | 2048 |
| CI | 512 | 10000 |
Override locally: PROPTEST_CASES=512 ci/test
- Run
ci/validateafter changes - never skip - Use ci/ scripts, not raw cargo commands
- No panics - Result types only
- The whole program must be correct - property tests for generators, payloads, blackholes; Kani proofs for throttle and core algorithms where feasible
- Generators must be deterministic (explicit seeding, IndexMap)
- Pre-compute in init, not hot paths
- No HashMap/HashSet where iteration order matters
- Document invariants with formal language in tests/proofs
- Consider measurement interference
- No mod.rs files, imports at file top only