For agents, on top of README.md, which they read first.

Notes for agents

  • The wire is a contract with the app and the tablet. Do not change a path, a JSON field, a status or a status text while moving logic. crates/whiskersd/tests and tools/preflight.sh run it end to end; the tests here pin it byte for byte (tests/service.rs). Key order matters: serde_json has preserve_order on purpose.
  • Policy lives here; state and calls live in adapters. If you are about to write a rule (a cap, an order of checks, what a refusal says) in whiskersd, it belongs here. If you are about to name a host, a file or a socket in this crate, it belongs in an adapter.
  • Everything must build for wasm: no std::fs, std::net, std::thread, Instant, SystemTime. Time comes from HasClock::clock(). Check with cargo check --target wasm32-unknown-unknown -p whiskers-service.
  • The order of checks in /speak is part of the wire (bad body, empty, not configured, too long, the day's cap), for both speak routes. The comment there says why; keep it.
  • Never log content: no message text, facts, summaries, keys or picture bytes. Lengths, counts, ids of pictures, statuses and durations (computed from the clock port) are fine. See crates/CLAUDE.md.
  • A new route gets a test in tests/service.rs with the exact bytes it replies, and its path goes in that file's EVERY_ROUTE list (a test fails if Service::complete forgets a route).
  • A route's capabilities are its Route<C> impl's bounds, and nothing else. impl<C: HasJudge + HasClock> Route<C> for Check is the entire statement of what /check needs. Never add a runtime "not configured" branch for a capability the adapter might not have, and never write a stand-in port to satisfy a bound: the route is simply not given to that adapter, and the compiler refuses to give it. A route's handler asks the adapter for exactly the capabilities in its bounds and no more (the compiler enforces that too).
  • Adding a route: its capability trait (if new) is in whiskers-ports, its laws in whiskers-conformance; here you add the route type (a unit struct, sealed), its Route impl, a line in Service::complete and in the Complete alias.