Skip to content

Commit 19010cb

Browse files
feat: protocol 0.7.0 - source_hash format pin, JSON-shape anchors, RiskBand + CoverageSource sentinels (#4)
* feat: protocol 0.7.0 - pin source_hash format, add JSON-shape anchor fixtures Closes #3. - Pin FunctionIdentity::source_hash to a cross-producer-comparable format: first 8 bytes of SHA-256(canonical body bytes) rendered as 16 lowercase hex chars. Canonicalization rule: signature line, body, closing brace, no whitespace normalization. Producers that cannot canonicalize the same way MUST omit the field rather than emit a divergent format. Closes the cross-producer non-comparability gap that the 0.6.0 "producer-defined, opaque string" wording allowed. - New pub fn source_hash_for(body: &[u8]) -> String helper. Reuses the existing sha2 dependency, no new transitive deps. Anchor fixture pins source_hash_for(b"function foo() { return 1; }") to "74846e29a52fe863" so producers can self-test against the conformance value in their CIs. - Tighten FunctionIdentity::stable_id_computed rustdoc with the literal phrase "NOT a validation gate"; consumers MUST NOT reject payloads whose stable_id differs from the computed value. - Byte-level JSON-shape anchor fixtures for FunctionIdentity (full + minimal) plus anchor fixtures for blast_radius_id and importance_id parallel to the existing function_identity_id fixture. - Per-field identity-stability assertions: function_identity_id is asserted invariant under independent mutation of start_column, end_line, end_column, source_hash. Refines the prior bundled function_identity_id_unchanged_by_columns into the per-field cases the rustdoc claims. - IdentityResolution::Unresolved shape fixture documenting the wire bytes an MCP agent or cloud aggregator sees for a failed-join entry. - Internal hex_prefix refactored to take a bytes count so the 4-byte truncation used by finding_id / hot_path_id / blast_radius_id / importance_id / function_identity_id and the 8-byte truncation used by source_hash_for share one auditable implementation. PROTOCOL_VERSION and Cargo.toml [package].version both bumped 0.6.0 -> 0.7.0 in lockstep. 13 new tests, all green; clippy clean; doc-warning clean; cargo publish --dry-run succeeds. * feat: add Unknown sentinels to RiskBand and CoverageSource Closes #5. Adds `#[serde(other)] Unknown` to the two remaining public enums that lacked the forward-compat sentinel. Surfaced by a /sweep audit of every public enum in the crate after the 0.7.0 source_hash format pin landed: - `RiskBand` (Response-side, `BlastRadiusEntry.risk_band`): future bands (`Critical`, `Negligible`) MAY now be added by producers as a minor bump; older consumers map unseen variants to Unknown rather than failing deserialization. - `CoverageSource` (Request-side, `Request.coverage.sources`): future source kinds (`IstanbulDir`, `TraceEvent`, `RuntimeBeacon`) MAY be added by the CLI as a minor bump; sidecars that have not seen the new kind map it to Unknown and skip the entry. Two new round-trip tests (`unknown_risk_band_round_trips`, `unknown_coverage_source_round_trips`) lock the forward-compat behaviour. Test count is now 61. Clippy / fmt / doc / publish all green. CHANGELOG `[0.7.0]` Added section gains a bullet. Module-level 0.7 overview block mentions the addition. No wire shape change for existing payloads. * ci: clear CI red on PR #4 (typos false positive + release.yml zizmor findings) Fixes three failing CI checks on PR #4: 1. **Typos** (new failure caused by this PR): the byte-equal JSON anchor literal added in the 0.7.0 source_hash format pin contains the hex string `e25ba02c5e53651f`, which typos tokenizes on digit boundaries and flags `ba` as a misspelling of `by` / `be`. Adds the specific hex value to `[default.extend-identifiers]` in `_typos.toml` with a comment explaining the scope (panel-verified anchor; changing the test inputs would invalidate the cross-producer conformance fixture). Narrowly scoped to one identifier; does not whitelist `ba` globally. 2. **Actions Security (zizmor) - superfluous-actions** on `.github/workflows/release.yml`: replaces `softprops/action-gh-release@v3` with a `gh release create` shell step. Equivalent behaviour using the gh CLI already preinstalled on ubuntu-latest runners. Adds a re-run-safe guard that skips creation when the release already exists for the tag (e.g. workflow_dispatch re-runs on an existing tag). All inputs routed through env: vars per the workflow's existing security-note convention. 3. **Actions Security (zizmor) - use-trusted-publishing** on `cargo publish`: suppressed via `# zizmor: ignore[use-trusted-publishing]` inline pragma with a TODO comment documenting the migration path. Real migration to crates.io OIDC trusted publishing requires registering the trusted publisher on the crates.io web UI plus adding `id-token: write` permission to the job; that infra change is out of scope for this PR and tracked by the inline TODO. 4. **CI** (meta-job): aggregates the above; turns green automatically once Typos and Actions Security pass. Local verification: cargo test (61 passed), clippy, fmt, doc, typos, zizmor (no findings; 12 ignored, 1 suppressed) all clean. Pre-existing context: main branch CI had been red for over a month due to these two zizmor findings on release.yml. The Typos failure is specific to this PR; the zizmor fixes incidentally unblock the release pipeline ahead of v0.7.0 tagging.
1 parent 95a73d7 commit 19010cb

5 files changed

Lines changed: 511 additions & 34 deletions

File tree

.github/workflows/release.yml

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ jobs:
134134
shell: bash
135135
env:
136136
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
137+
# zizmor: ignore[use-trusted-publishing]
138+
# TODO(trusted-publishing): migrate to crates.io OIDC trusted
139+
# publishing once the trusted publisher is registered for this
140+
# crate on https://crates.io. Requires configuring the trusted
141+
# publisher on the crates.io web UI, adding `id-token: write`
142+
# permission to this job, and switching to cargo's OIDC-based
143+
# publish flow. Token-based publish is the documented bootstrap
144+
# path until that infra exists.
137145
run: cargo publish
138146

139147
- name: Extract CHANGELOG slice for this version
@@ -166,12 +174,23 @@ jobs:
166174
} >> "$GITHUB_OUTPUT"
167175
168176
- name: Create GitHub Release
169-
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
170-
with:
171-
name: ${{ steps.changelog.outputs.title }}
172-
tag_name: ${{ steps.version.outputs.tag }}
173-
body: ${{ steps.changelog.outputs.body }}
174-
draft: false
175-
prerelease: false
177+
shell: bash
176178
env:
177-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
179+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
180+
TAG: ${{ steps.version.outputs.tag }}
181+
TITLE: ${{ steps.changelog.outputs.title }}
182+
BODY: ${{ steps.changelog.outputs.body }}
183+
run: |
184+
set -euo pipefail
185+
# All inputs are derived from this workflow's own steps (version
186+
# extraction + CHANGELOG slice), not from external event payload,
187+
# so direct env-var consumption by `gh release create` is safe.
188+
# If the release already exists for this tag (re-run path), skip
189+
# creation rather than failing the job.
190+
if gh release view "$TAG" >/dev/null 2>&1; then
191+
echo "::notice::release $TAG already exists; skipping gh release create"
192+
exit 0
193+
fi
194+
gh release create "$TAG" \
195+
--title "$TITLE" \
196+
--notes "$BODY"

CHANGELOG.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,90 @@ crate adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77
Pre-1.0 minor bumps may still contain breaking changes; see `CLAUDE.md` and
88
`.claude/rules/protocol-versioning.md` for the full policy.
99

10+
## [0.7.0] - 2026-05-21
11+
12+
### Changed (additive producer constraint)
13+
14+
- **`FunctionIdentity::source_hash` format is now pinned**, closing the
15+
cross-producer non-comparability gap that issue #3 surfaced. The field
16+
was previously documented as "opaque, producer-defined", which let each
17+
producer pipeline (`fallow-v8-coverage`, `oxc_coverage_v8`, the Istanbul
18+
ingester, browser / node beacons) emit incompatible hash formats
19+
(`sha256-hex`, `xxh3-base64`, `blake3-truncated`) and rendered the
20+
tiebreaker useless across pipelines.
21+
22+
Pinned format (added in protocol 0.7.0, MUST hold across producers):
23+
the first 8 bytes of `SHA-256(<canonical body bytes>)` rendered as 16
24+
lowercase hex characters. Canonical body bytes are the bytes the
25+
producing compiler or parser sees for the function, including the
26+
signature line and the closing brace, with NO whitespace
27+
normalization. Producers that cannot canonicalize the bytes the same
28+
way as their siblings MUST omit the field rather than emit a
29+
divergent format.
30+
31+
Migration for producers shipping a non-conforming `source_hash` today:
32+
switch the producer to the new `source_hash_for` helper in this crate,
33+
or omit the field. No wire shape change for consumers; the field stays
34+
`Option<String>`.
35+
36+
### Added
37+
38+
- **`source_hash_for(body: &[u8]) -> String`** helper computing the
39+
canonical `FunctionIdentity::source_hash` value. Reuses the existing
40+
`sha2` dependency. No new transitive deps. Producers MUST route every
41+
`source_hash` value through this helper so cross-producer agreement
42+
holds by construction.
43+
- **Anchor fixture** `source_hash_for_anchor_fixture` pinning
44+
`source_hash_for(b"function foo() { return 1; }")` to the literal
45+
string `"74846e29a52fe863"`. Producers self-test against this fixture
46+
in their own CIs to detect divergence at the source rather than at
47+
cross-surface join time.
48+
- **`function_identity_full_json_shape_anchor_fixture`** and
49+
**`function_identity_minimal_json_shape_anchor_fixture`** locking the
50+
byte-level JSON output of `FunctionIdentity` for the every-field-set
51+
and the minimum-required (all `Option`s `None`) shapes. Catches silent
52+
field-reorder regressions and `skip_serializing_if` drift.
53+
- **`identity_resolution_unresolved_shape_fixture`** documenting the
54+
on-wire shape an MCP agent or cloud aggregator sees for a failed-join
55+
entry where `resolution = "unresolved"` and columns / `source_hash`
56+
are absent.
57+
- **`blast_radius_id_anchor_fixture`** and
58+
**`importance_id_anchor_fixture`** parallel to the existing
59+
`function_identity_id_anchor_fixture`. Locks the canonical hash
60+
inputs and truncation for the remaining stable-ID helpers so every
61+
producer can self-test agreement.
62+
- **Per-field stability assertions** for `function_identity_id`:
63+
separate tests pin invariance under independent mutation of
64+
`start_column`, `end_line`, `end_column`, and `source_hash`. The
65+
prior `function_identity_id_unchanged_by_columns` test bundled all
66+
four metadata fields; the per-field cases catch a future regression
67+
where the helper accidentally starts hashing one specific metadata
68+
field but not the others.
69+
- **Tightened rustdoc** on `FunctionIdentity::stable_id_computed`. The
70+
method is now explicitly documented as a diagnostic helper, NOT a
71+
validation gate. Consumers MUST NOT reject payloads whose `stable_id`
72+
differs from the computed value; doing so would turn every such
73+
consumer into a hard-fail on the next protocol major that evolves the
74+
hash inputs.
75+
- **`Unknown` sentinel variants** on `RiskBand` and `CoverageSource`,
76+
closing the last two forward-compat gaps surfaced by a /sweep audit
77+
of every public enum in the crate. Adds `#[serde(other)] Unknown` to
78+
both. Future producers MAY add new variants (`Critical` / `Negligible`
79+
for `RiskBand`; `IstanbulDir` / `TraceEvent` / `RuntimeBeacon` for
80+
`CoverageSource`) as additive minor bumps; consumers that have not
81+
seen the new variant yet map it to `Unknown` rather than failing
82+
deserialization. Closes the latent gap that future variant additions
83+
on either enum would have required a major bump. Closes #5.
84+
85+
### Other
86+
87+
- `PROTOCOL_VERSION` bumped to `"0.7.0"`. `Cargo.toml [package].version`
88+
matches per `.claude/rules/protocol-versioning.md`.
89+
- Internal `hex_prefix` refactored to take a `bytes` count so the 4-byte
90+
truncation used by `finding_id` / `hot_path_id` / `blast_radius_id` /
91+
`importance_id` / `function_identity_id` and the 8-byte truncation
92+
used by `source_hash_for` share one auditable implementation.
93+
1094
## [0.6.0] - 2026-05-20
1195

1296
### Added

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fallow-cov-protocol"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
edition = "2021"
55
rust-version = "1.75"
66
license = "MIT OR Apache-2.0"

_typos.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,12 @@ extend-exclude = [
33
"Cargo.lock",
44
"target/",
55
]
6+
7+
# SHA-256 prefix anchor values used by FunctionIdentity / source_hash_for
8+
# tests inside src/lib.rs. typos tokenizes hex strings on digit boundaries
9+
# and would otherwise flag substrings like `ba` (inside e25ba02c5e53651f)
10+
# as misspellings of `by` / `be`. Each entry is the exact 16-hex output of
11+
# `source_hash_for` or `function_identity_id` pinned in the test source;
12+
# changing the test inputs would invalidate the panel-verified anchors.
13+
[default.extend-identifiers]
14+
e25ba02c5e53651f = "e25ba02c5e53651f"

0 commit comments

Comments
 (0)