Skip to content

Latest commit

 

History

History
92 lines (70 loc) · 6.41 KB

CLAUDE.md

File metadata and controls

92 lines (70 loc) · 6.41 KB

fallow-cov-protocol: Wire contract for Fallow Cloud

Versioned JSON envelope shared between the open-source fallow CLI and the closed-source fallow-cov production-coverage sidecar (the collection engine behind Fallow Cloud, the paid product that adds production data to fallow). This crate is the single source of truth for the request/response shape so the two repositories cannot drift.

Project structure

src/
  lib.rs            -- Entire public API: Request/Response envelopes, enums, ID helpers, tests
Cargo.toml          -- Crate metadata, pinned MSRV 1.96, pedantic clippy, unsafe_code = forbid
README.md           -- crates.io front page (what/why/versioning/status)
.claude/            -- Claude Code config: hooks, rules, reviewer agents

The crate is deliberately tiny: types + serde derives + a couple of ID helpers. Everything lives in one file so both sides can grep the contract in seconds.

Architecture (role of this crate)

  fallow CLI  ──spawn──▶  fallow-cov sidecar
   (OSS)          stdin       (closed-source)
     │        Request JSON          │
     │                              │
     │     Response JSON            │
     └──────────stdout──────────────┘
                 ▲
                 │
         fallow-cov-protocol
         (this crate, shared dep)

Both binaries depend on this crate. The CLI writes a Request to the sidecar's stdin; the sidecar writes a Response to stdout. The crate exposes zero I/O and zero business logic, only the shape of the bytes on the pipe.

Versioning policy (load-bearing)

  • protocol_version is a full semver string, also exposed as PROTOCOL_VERSION.
  • Major bumps are breaking. Consumers MUST reject mismatched majors.
  • Minor bumps add optional fields or enum variants. Consumers MUST forward-accept unknown fields (via serde defaults) and SHOULD map unknown enum variants to the crate's Unknown sentinel (via #[serde(other)]).
  • Every new field on an existing struct requires #[serde(default)] (or default = "...") so old encoders stay compatible.
  • Every public enum that can grow variants must carry an Unknown variant with #[serde(other)]. Adding a new variant is a minor bump only if the Unknown sentinel is present; otherwise it's a major.
  • The stable ID hashes (finding_id, hot_path_id) hash a canonical, unseparated UTF-8 order. That order is part of the public contract: changing it invalidates IDs persisted by CI dedup, suppression, and agent cross-references. Treat it as a major break even if the function signature is unchanged.
  • Until 1.0.0, minor + patch may contain breaking changes; always bump the relevant digit and announce in the changelog.

Code conventions

  • #![forbid(unsafe_code)] at the top of lib.rs, enforced via [lints.rust] in Cargo.toml.
  • Serde derives on every wire type. Enums use #[serde(rename_all = "snake_case")] or "kebab-case" explicitly (see CoverageSource for kebab, Verdict/Confidence for snake).
  • Enum Unknown sentinels via #[serde(other)] (see ReportVerdict, Verdict, Confidence, Feature, Watermark).
  • Optional fields use #[serde(default)]; Option<T> fields skip-serialize with skip_serializing_if = "Option::is_none" when absent is semantically different from default.
  • Non-trivial defaults use a named const fn default_<name>() -> T, not closures, which keeps the wire default auditable.
  • Clippy all, pedantic, nursery and cargo at warn (priority -1), with a short documented allow-list in Cargo.toml (tightly scoped crate, every public item is the contract).
  • MSRV pinned to 1.96 in Cargo.toml; do not rely on newer features without bumping it.
  • missing_docs = "warn" is a TODO until 1.0.0 (flip to deny before the 1.0 cut); new public items should still carry rustdoc.

Testing conventions

All tests live in the single tests mod in lib.rs. The required categories (forward-compat, unknown fields, casing, stable IDs, skip_serializing_if, defaulted fields) and their patterns are in .claude/rules/testing.md. When adding a new wire field or enum variant, add the matching round-trip + forward-compat test in the same PR. No exceptions.

Building & testing

cargo build                                       # Library build
cargo test                                        # All unit tests
cargo clippy --all-targets -- -D warnings         # Lints
cargo fmt --all -- --check                        # Formatting
cargo doc --no-deps --document-private-items      # Docs (catches broken intra-doc links)

Write for the reader

Write code comments, docs, commit messages, PR text, and changelog entries for a reader who did not see the session. Do not put the conversation, the prompt, discarded attempts, or the edit history into them. Describe the code as it is now. For a regression, state what breaks without the code, and link the issue. scripts/check-comment-quality.mjs blocks narration and history comments in CI, in the .githooks/pre-push hook, and in Claude edits.

Git conventions

  • Conventional commits: feat:, fix:, chore:, refactor:, test:, docs:
  • Signed commits (git commit -S)
  • No AI attribution in commits

Key design decisions

  • One file for the whole contract: keeps grep-ability and review surface small. Do not split until it's genuinely unreadable.
  • Plain String paths, not PathBuf: the wire is JSON; paths are already stringly typed on both sides and PathBuf serde is lossy on Windows.
  • SHA-256-truncated IDs, two recipes: per-surface finding IDs (fallow:prod|hot|blast|importance:<8 hex>) hash unseparated file+function+line+kind and truncate to 8 hex, short enough for CLI output and CI annotations. FunctionIdentity::stable_id (fallow:fn:<16 hex>) hashes NUL-delimited file \0 name \0 start_line and truncates to 16 hex (64-bit), reconciled with the cloud aggregation store in 0.8.0; the wider bound stays collision-safe for large monorepos aggregated over time.
  • No thiserror / anyhow dependency: this crate never produces errors; all fallibility lives on the two binaries that own I/O.
  • No chrono / time crate: periods are raw integers (observation_days, deployments_observed) to keep the dep tree minimal and the wire trivially parseable by non-Rust sidecars.

See .claude/rules/ for area-specific conventions and .claude/agents/ for reviewer personas.