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.
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.
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.
protocol_versionis a full semver string, also exposed asPROTOCOL_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
Unknownsentinel (via#[serde(other)]). - Every new field on an existing struct requires
#[serde(default)](ordefault = "...") so old encoders stay compatible. - Every public enum that can grow variants must carry an
Unknownvariant with#[serde(other)]. Adding a new variant is a minor bump only if theUnknownsentinel 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.
#![forbid(unsafe_code)]at the top oflib.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 (seeCoverageSourcefor kebab,Verdict/Confidencefor snake). - Enum
Unknownsentinels via#[serde(other)](seeReportVerdict,Verdict,Confidence,Feature,Watermark). - Optional fields use
#[serde(default)];Option<T>fields skip-serialize withskip_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,nurseryandcargoatwarn(priority -1), with a short documented allow-list inCargo.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 todenybefore the 1.0 cut); new public items should still carry rustdoc.
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.
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 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.
- Conventional commits:
feat:,fix:,chore:,refactor:,test:,docs: - Signed commits (
git commit -S) - No AI attribution in commits
- One file for the whole contract: keeps grep-ability and review surface small. Do not split until it's genuinely unreadable.
- Plain
Stringpaths, notPathBuf: the wire is JSON; paths are already stringly typed on both sides andPathBufserde is lossy on Windows. - SHA-256-truncated IDs, two recipes: per-surface finding IDs (
fallow:prod|hot|blast|importance:<8 hex>) hash unseparatedfile+function+line+kindand truncate to 8 hex, short enough for CLI output and CI annotations.FunctionIdentity::stable_id(fallow:fn:<16 hex>) hashes NUL-delimitedfile \0 name \0 start_lineand 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/anyhowdependency: 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.