All notable changes to fallow-cov-protocol are documented here. The format
follows Keep a Changelog, and this
crate adheres to Semantic Versioning.
Pre-1.0 minor bumps may still contain breaking changes; see CLAUDE.md and
.claude/rules/protocol-versioning.md for the full policy.
- License narrowed to MIT, matching the
fallowCLI repository. Previous releases up to and including 0.8.0 remain dual-licensedMIT OR Apache-2.0on crates.io; the narrowing applies to releases published after this entry. TheLICENSEfile is now present in the repository. - MSRV bumped from
1.85to1.96, the same as thefallowCLI. Thesha2requirement is relaxed from=0.11.0to0.11, because the exact pin only kept Rust 1.85 support.rust-toolchain.tomlnow pins Rust 1.99.0 for lint and format, separate from the MSRV.
- Runtime signal fields from the Fallow Cloud runtime-context v1 shape.
All fields are optional and additive, so older readers and older
producers keep working. A reader gets
Nonefor an absent field and fornull. Every signal is context only: it never changes a verdict, a confidence or a risk band.Findinggetsrepo_path,covered_by_test,period_tracking_state,errors,performance(withcpuandbrowser) andbranches.BlastRadiusEntrygetsproduction_callers.- New types:
TrackingState,SignalState,ErrorState,BreakdownState,BranchUsageState,BranchRangeKind,FunctionErrors,ErrorTop,ErrorBreakdown,RouteEvents,HandledEvents,Performance,CpuUsage,BrowserPerformance,BranchUsage,NeverRunRangeandProductionCaller. Each new enum has anUnknownsentinel. Each new struct is#[non_exhaustive]. - Inside a signal object, a count that was not measured serializes as
null, never as zero, the same as on the cloud wire.
SECURITY.md(private vulnerability reporting, deserialization threat model, dependency trust boundary) andCODE_OF_CONDUCT.md, mirroringfallow.
-
function_identity_idrecipe reconciled with the cloud store. Thestable_idpreimage is now NUL-delimited and truncated to 16 hex characters:stable_id = "fallow:fn:" + SHA-256(file \0 name \0 start_line).hex[:16]Previously (0.7.x) it was
SHA-256(file + name + start_line + "function"), plain unseparated UTF-8, truncated to 8 hex. The change adopts the defensive choices the cloud aggregation store (computeStableId) was authored with first: NUL delimiters (so("ab","c")cannot collide with("a","bc"); producers MUST reject NUL bytes infile/name) and 64-bit truncation (birthday-collision-safe for large monorepos, where 32-bit becomes risky past ~65k functions per partition). Columns stay excluded, preserving cross-producer agreement (V8 / Istanbul / oxc). -
Every
fallow:fn:value changes. Persisted ids reset: CI dedup, suppression files, agent cross-references, and cloudfunction_hitsrows must be regenerated. Consumers that persist ids MUST run their re-baseline / backfill path on upgrade.
function_identity_id_v1retains the exact 0.7.x recipe for the upgrade grace window. Consumers holding pre-0.8.0 baselines recompute BOTHfunction_identity_idandfunction_identity_id_v1fromfile+name+start_lineand suppress on either match, so findings stay suppressed until the next re-baseline. Marked#[deprecated]; producers MUST NOT emit its value on the wire.
- Producers (
fallowCLI,fallow-covsidecar): no code change beyond bumping thefallow-cov-protocoldependency to0.8; the new recipe is picked up by recompiling against the updated helper. - Cloud (
computeStableId): dropcolumnNumberfrom the preimage, add thefallow:fn:prefix (the NUL delimiter and 16-hex width already match), and backfillfunction_hits.stable_id. - Baseline consumers: extend the suppression match with
function_identity_id_v1for the grace window.
- Corrected the false "stable across line moves" claim on
FunctionIdentity::stable_id(issue #17). The rustdoc onFinding::idand the 0.6.0 CHANGELOG entry both describedstable_idas line-move-immune and claimed "suppressions survive line shifts". This was never true:function_identity_idhashesfile + name + start_line + "function", so moving a function to a new line mints a newstable_id, exactly as it mints a newFinding::id. The hash inputs, wire shape, andPROTOCOL_VERSIONsemantics are unchanged; only the prose is corrected. - Documented what
stable_idactually provides: it is the cross-surface (one value per function across findings / hot-paths / blast-radius / importance) and cross-producer (V8 / Istanbul / oxc agree because columns are excluded from the hash) join key. It is NOT a line-move-immune key. - Added a "which key for which job" explainer to the
Finding::idrustdoc covering all three identifiers:Finding::id(per-finding suppression),stable_id(cross-surface + cross-producer join), andsource_hash(content tiebreaker, the only one that survives a line move with an unchanged body). Consumers that want line-move-tolerant matching layersource_hashon top ofstable_idwhen present.
- MSRV bumped from
1.75to1.85. Required to consumesha2 = "0.11", whose transitive deps (hybrid-array,cpufeatures,block-buffer,crypto-common,digest) all declareedition = "2024". Both real consumers of this crate (thefallowCLI and thefallow-covsidecar) are already on Rust 1.92, so the bump has no practical downstream impact. UpdatedCargo.toml,rust-toolchain.toml, themsrvCI job, and the in-repo docs in lockstep. - Bumped
sha2from0.10to0.11. No public API change; the hash inputs andsource_hash_foroutput format are unchanged.
-
FunctionIdentity::source_hashformat is now pinned, closing the cross-producer non-comparability gap that issue #3 surfaced. The field was previously documented as "opaque, producer-defined", which let each producer pipeline (fallow-v8-coverage,oxc_coverage_v8, the Istanbul ingester, browser / node beacons) emit incompatible hash formats (sha256-hex,xxh3-base64,blake3-truncated) and rendered the tiebreaker useless across pipelines.Pinned format (added in protocol 0.7.0, MUST hold across producers): the first 8 bytes of
SHA-256(<canonical body bytes>)rendered as 16 lowercase hex characters. Canonical body bytes are the bytes the producing compiler or parser sees for the function, including the signature line and the closing brace, with NO whitespace normalization. Producers that cannot canonicalize the bytes the same way as their siblings MUST omit the field rather than emit a divergent format.Migration for producers shipping a non-conforming
source_hashtoday: switch the producer to the newsource_hash_forhelper in this crate, or omit the field. No wire shape change for consumers; the field staysOption<String>.
source_hash_for(body: &[u8]) -> Stringhelper computing the canonicalFunctionIdentity::source_hashvalue. Reuses the existingsha2dependency. No new transitive deps. Producers MUST route everysource_hashvalue through this helper so cross-producer agreement holds by construction.- Anchor fixture
source_hash_for_anchor_fixturepinningsource_hash_for(b"function foo() { return 1; }")to the literal string"74846e29a52fe863". Producers self-test against this fixture in their own CIs to detect divergence at the source rather than at cross-surface join time. function_identity_full_json_shape_anchor_fixtureandfunction_identity_minimal_json_shape_anchor_fixturelocking the byte-level JSON output ofFunctionIdentityfor the every-field-set and the minimum-required (allOptionsNone) shapes. Catches silent field-reorder regressions andskip_serializing_ifdrift.identity_resolution_unresolved_shape_fixturedocumenting the on-wire shape an MCP agent or cloud aggregator sees for a failed-join entry whereresolution = "unresolved"and columns /source_hashare absent.blast_radius_id_anchor_fixtureandimportance_id_anchor_fixtureparallel to the existingfunction_identity_id_anchor_fixture. Locks the canonical hash inputs and truncation for the remaining stable-ID helpers so every producer can self-test agreement.- Per-field stability assertions for
function_identity_id: separate tests pin invariance under independent mutation ofstart_column,end_line,end_column, andsource_hash. The priorfunction_identity_id_unchanged_by_columnstest bundled all four metadata fields; the per-field cases catch a future regression where the helper accidentally starts hashing one specific metadata field but not the others. - Tightened rustdoc on
FunctionIdentity::stable_id_computed. The method is now explicitly documented as a diagnostic helper, NOT a validation gate. Consumers MUST NOT reject payloads whosestable_iddiffers from the computed value; doing so would turn every such consumer into a hard-fail on the next protocol major that evolves the hash inputs. Unknownsentinel variants onRiskBandandCoverageSource, closing the last two forward-compat gaps from a pre-release audit of every public enum in the crate. Adds#[serde(other)] Unknownto both. Future producers MAY add new variants (Critical/NegligibleforRiskBand;IstanbulDir/TraceEvent/RuntimeBeaconforCoverageSource) as additive minor bumps; consumers that have not seen the new variant yet map it toUnknownrather than failing deserialization. Closes the latent gap that future variant additions on either enum would have required a major bump. Closes #5.
PROTOCOL_VERSIONbumped to"0.7.0".Cargo.toml [package].versionmatches per.claude/rules/protocol-versioning.md.- Internal
hex_prefixrefactored to take abytescount so the 4-byte truncation used byfinding_id/hot_path_id/blast_radius_id/importance_id/function_identity_idand the 8-byte truncation used bysource_hash_forshare one auditable implementation.
FunctionIdentitytype and optionalidentityfield onStaticFunction,Finding,HotPath,BlastRadiusEntry, andImportanceEntry. Becomes the canonical cross-surface join key between the OSS CLI's static function inventory, V8 / Istanbul runtime coverage, test coverage fromoxc-coverage-instrument, source-map remapped findings, andfallow-cloudaggregation. The legacyfile/function/linetriple is preserved verbatim for display and 0.5-era consumers.function_identity_id(file, name, start_line)helper emittingfallow:fn:<8 hex>. Hash inputs arefile + name + start_line + "function"; columns andsource_hashare intentionally NOT hashed so producers with different positional fidelity (V8 byte offsets vs Istanbul UTF-16 columns vs oxc spans) agree on the join key.IdentityResolutionenum withResolved/Fallback/Unresolved/Unknownvariants. Lets cloud aggregation record per-function whether the identity came from a source-map lookup, a best-effort fallback, or remains unresolved. Required field onFunctionIdentity(not#[serde(default)]): a missing field would silently default and hide the resolution-confidence signal.FunctionIdentity::stable_id_computedconvenience method for consumers that want to sanity-check a producer-suppliedstable_id.- Column-semantic lock in rustdoc on
FunctionIdentity::start_columnandend_column: 1-indexed UTF-16 column, anchored at the function-body start (Istanbulloc.start, V8 mapped from byte offset via script text, oxcSpan::startmapped to UTF-16). Producers MUST normalize to this anchor. - Conformance fixture test
function_identity_id_anchor_fixturewith hard-coded inputs ("src/render.tsx","render",42) and expected hash"fallow:fn:43629542". Producers run the same fixture in their CIs to catch divergence at the source.
StaticFunction,Finding,HotPath,BlastRadiusEntry, andImportanceEntryare now#[non_exhaustive]. This is a one-time source-level break for downstream Rust consumers that constructed these via struct literals; the wire shape is unchanged and forward-compatible. Future field additions become pure additive changes that no longer require a source break.PROTOCOL_VERSIONbumped to"0.6.0".
Cross-repo rollout order (load-bearing, do not invert):
- Publish
fallow-cov-protocol0.6.0 (this release). - Update the closed-source
fallow-covsidecar to depend on 0.6.0, start populatingidentityon every emittedFinding/HotPath/BlastRadiusEntry/ImportanceEntryviafunction_identity_id, and ship a sidecar release. The wire is purely additive, so the upgraded sidecar's 0.6 output remains a valid 0.5 envelope for any CLI consumer that has not yet upgraded (theidentityfield is#[serde(default)]andskip_serializing_ifon the consumer side); the sidecar therefore satisfies the grace window required by.claude/rules/protocol-versioning.mdwithout needing to emit two envelope shapes in parallel. Sidecar / CLI / cloud repos with auto-update Dependabot or similar bot config SHOULD pin or delay the 0.6.0 bump (and gate auto-merge) until the matching consumer PR has landed in lockstep; rolling the bumps independently does not break the wire but reverses the coordination order this rule file requires. - Update the OSS
fallowCLI to depend on 0.6.0. Continue reading the legacyfile/function/linefields for display. Switch the join key toidentity.stable_idwhen present (tracked infallow-rs/fallow#506). - Migrate
fallow-cloudaggregation to preferidentity.stable_idfor dedup / merge keys (tracked infallow-rs/fallow-cloud#63), and the browser / node beacon paths to emit column data (tracked infallow-rs/fallow-cloud#64).
For downstream Rust consumers of this crate:
- Code that constructs
StaticFunction,Finding,HotPath,BlastRadiusEntry, orImportanceEntryvia struct literals will fail to compile against 0.6.0. Replace struct literals with builder helpers in the producing crate, or accept the one-time source break. - Legacy
Finding.id/HotPath.id/BlastRadiusEntry.id/ImportanceEntry.idcontinue to ship through 0.6 alongsideidentity.stable_id. Existing suppression files keyed on the legacy IDs remain valid. - Suppression key vs join key. The two IDs serve different
axes:
Finding.idis the per-finding suppression key (hashes the currentline, so it changes when a function moves);identity.stable_idis the cross-surface + cross-producer join key (same function gets one value across findings, hot paths, blast-radius, and importance entries, and across V8 / Istanbul / oxc producers because columns are excluded from the hash). It also hashesstart_line, so it is NOT immune to line moves: a moved function gets a newstable_idjust as it gets a newFinding.id. (The 0.6.0 entry originally claimedstable_idwas "stable across line moves"; that was never true and is corrected here per #17.) Agent tooling writing NEW suppression / baseline entries SHOULD preferidentity.stable_idwhen present so one entry correlates the function across every surface. Readers MUST accept both forms during the grace window.
Summary.capture_quality: Option<CaptureQuality>(ADR 009 step 6b, deliverable 2 of 3). Surfaces{ window_seconds, instances_observed, lazy_parse_warning, untracked_ratio_percent }so the CLI can render a "short window" warning alongside low-confidence verdicts and quantify the delta continuous cloud monitoring would provide. Optional for forward-compatibility with 0.2.x sidecars; 0.3.x always sets it.CaptureQuality::LAZY_PARSE_THRESHOLD_PERCENT = 30.0. Untracked ratio above this threshold trips the lazy-parse warning.Options.window_seconds: Option<u64>andOptions.instances_observed: Option<u32>. Finer-grained inputs forCaptureQuality; both fall back to existingperiod_days/deployments_seenwhenNone.
PROTOCOL_VERSIONbumped to"0.3.0".
- Workspace-wide lint configuration bringing parity with the main
fallowcrate:clippy::{all, pedantic, nursery, cargo}atwarn, targeted restriction lints, expanded[lints.rust]block, andmissing_docs = "warn". .clippy.tomlwith thresholds for excessive nesting, unit size, cognitive complexity, and parameter count.rust-toolchain.tomlpinning the MSRV toolchain (1.75.0).deny.tomlwith permissive-only license allow-list and wildcard ban._typos.toml..github/workflows/ci.ymlcovering test / clippy / fmt / doc / typos / audit / deny / shear / zizmor / MSRV / publish dry-run on a three-OS matrix, with a deny-all permissions baseline and pinned action SHAs.release.tomllocking thecargo-releasemechanical knobs (signed commits + tags,PROTOCOL_VERSIONreplacement).- Rustdoc on every public item (fields, variants, helpers).
- Forward-compat round-trip test for
Watermark::Unknown. finding_id_is_lowercase_hex_asciicanonical-form test.
content_hashno longer useslet _ = write!(...)to build the hex prefix; replaced with a byte-level encoder (hex_prefix). The wire output is bit-identical — canonical input order, SHA-256, and 8-character truncation are unchanged.
- Stable
Finding::id/HotPath::idhashes viafinding_id/hot_path_idhelpers (fallow:prod:<hash>/fallow:hot:<hash>, SHA-256 truncated to 8 hex characters overfile + function + line + kind). - Per-finding
Verdictenum (safe_to_delete,review_required,coverage_unavailable,low_traffic,active) replacing 0.1'sCallState. - Full
Evidenceblock on each finding, mirroring the decision table inspec-production-coverage.md. Finding::invocationsasOption<u64>(nullable when V8 did not track the function).Confidence::VeryHighandConfidence::Nonevariants.#[serde(other)] Unknownsentinel on every enum that can grow variants (ReportVerdict,Verdict,Confidence,Feature,Watermark).
- Top-level report verdict renamed:
Verdict→ReportVerdict. Per-findingVerdictis the newVerdicttype. StaticFunction::static_usedandStaticFunction::test_coveredare now required; 0.1-shape requests that omit them fail deserialization by design.
- Initial
Request/Responseenvelope,CoverageSource,StaticFindings,Summary,Finding,HotPath,Feature, and supporting enums. PROTOCOL_VERSIONconstant.- Full test suite covering forward-compat, serde casing, and round-trip behaviour.