Imported from gencau/test-practices-agent-configurations (
dataset/repos/datadog§lading/AGENTS.md). Install upstream withnpx skills add gencau/test-practices-agent-configurations --skill datadog§lading. Copyright stays with the author.
Agent Instructions
Guidelines for AI agents working on the lading codebase. For detailed rationale behind these rules, see the Architecture Decision Records.
Quick Reference
| 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 Routing
| 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 |
Architecture Overview
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.
Core Constraints
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
Rules by Topic
Performance
- 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.
Determinism
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.
Error Handling
- 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.
Code Style
- 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.
Testing
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.
Dependencies
Evaluate before adding: performance, determinism, control, criticality. When in doubt, implement rather than import.
See ADR-008.
Documentation
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> { ... }
Validation Workflow
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 |
Property Test Configuration
| Environment | PROPTEST_CASES | PROPTEST_MAX_SHRINK_ITERS |
|---|---|---|
Local (ci/test) |
64 | 2048 |
| CI | 512 | 10000 |
Override locally: PROPTEST_CASES=512 ci/test
Key Reminders
- 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