release: v0.9.5 — polish + multi-SCM comment-suppress parity#22
Merged
release: v0.9.5 — polish + multi-SCM comment-suppress parity#22
Conversation
Replace dtolnay/rust-toolchain@stable with @1.88 in ci.yml, release.yml, and docs.yml so CI tracks the documented MSRV instead of whatever 'stable' resolves to on the runner. Newer clippy lints (e.g. cloned_ref_to_slice_refs, useless_vec, is_multiple_of) shipped in 1.94 and previously broke the build until source was adapted; pinning here removes that surprise. Bump deliberately when Cargo.toml rust-version is bumped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a 'How notes are upserted' subsection to docs/src/gitlab-ci.md that pins down the previously-unverified v0.7 question of whether the GitLab Notes API PUT preserves threading. Documented behaviour: - POST/PUT against /merge_requests/:iid/notes is a true upsert: the note ID is stable, so permalinks survive and the comment doesn't move in the MR timeline. - PUT does not refire Note Hook webhooks (so a comment-bridge wired to Note Hook does not loop on bomdrift's own edits). - Threaded replies live under the parent discussion, not the note, so reviewer replies stay attached across upserts -- matching the GitHub upsert shape. - Author/signing caveat: edits surface under the project access token's bot identity, not the MR author. Also explains why the diff path uses the Notes API rather than the Discussions API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… + CI sync guard
Three places previously held copies of the /bomdrift suppress
grammar:
- comment-suppress/entrypoint.sh (GitHub Action shell)
- examples/gitlab-ci/comment-bridge/worker.js (Cloudflare Worker JS)
- src/baseline.rs::parse_comment_directive (Rust, already documented
as the canonical-via-doc-comment in v0.7)
Promote the shell side to a sourced library in
scripts/parse-suppress-comment.sh, exposing parse_bomdrift_suppress()
with documented return codes. comment-suppress/entrypoint.sh now
sources it instead of inlining the regex + ID validator.
The Cloudflare Worker bridges can't source bash (different runtime),
so worker.js declares the regex with a comment pointing at the
canonical bash file. scripts/check-suppress-regex-sync.sh extracts
both regexes, normalizes [[:space:]]<->\\s and the JS / escapes,
and fails if they disagree. The new shell-bridges CI job runs:
- comment-suppress/test.sh (8 unit tests on the bash parser)
- check-suppress-regex-sync.sh
- bash -n on all shell scripts
- node --check on every bridge worker.js
The Rust regex stays as-is — Rust regex syntax differs slightly from
POSIX/JS so the parsers can't literally share bytes. The existing
doc comment on baseline::parse_comment_directive already references
the shell counterpart; the new CI guard keeps the two flavours
that CAN share grammar (shell + JS) in lockstep.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pre-v0.9.5, ExpiredEntry duplicated four fields of BaselineEntry. They now share one struct; expired_entries is Vec<BaselineEntry> with the invariant that expires.is_some() and the date is strictly before today at load time. ExpiredEntry remains a #[deprecated] type alias for back-compat with external consumers. The stderr warning text emitted by lib.rs is unchanged byte-for-byte; a new regression test (expired_entry_warning_text_is_stable) pins that format string against future drift. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ft finding ids Adds bomdrift::parse_synthetic_id() and SyntheticFindingKind, re-exported from the crate root so external VEX tooling can decode the synthetic finding ids bomdrift emits (e.g. as VEX statement vulnerability names) without re-implementing string-splitting against an undocumented format. Format remains 'bomdrift.<kind>:<purl>[:<extra>]'. The parser handles purls (one ':' from the 'pkg:' scheme) and the bare-component-name fallback the emitters use when component.purl is None. Synthetic-id emitters added for parity with the SARIF rule taxonomy: license-change, recently-published, deprecated, maintainer-set-changed. These are pure helpers — vex::apply / vex::emit are not yet wired to the new finding kinds (a v1.0+ scope item). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…orker bridges
Mirror the v0.9 GitLab Cloudflare Worker bridge for Bitbucket Cloud
and Azure DevOps so /bomdrift suppress <ID> comments now work as a
zero-click suppression UX on all four major SCMs.
Each bridge ships:
- worker.js (Cloudflare Worker, ~150 lines, plain JS, no deps)
- README.md (architecture diagram, threat model, deploy guide,
platform-specific gotchas, troubleshooting table)
- vercel-equivalent.md (port notes for Vercel / Netlify / Lambda)
The same five-guard security model as the GitLab bridge, adapted to
each platform's webhook + identity model:
Bitbucket Cloud (examples/bitbucket-pipelines/comment-bridge/):
1. HMAC-SHA256 X-Hub-Signature against byte-exact body
2. Event-type filter: pullrequest:comment_created only
3. Repo-full-name allowlist (org/repo)
4. Commenter permission: write|admin|owner via /workspaces
/<ws>/permissions
5. PR-context: state=OPEN AND source.full_name===destination.full_name
(rejects fork-PR comment-suppress)
→ triggers a custom 'bomdrift-comment-suppress' pipeline.
Azure DevOps (examples/azure-devops/comment-bridge/):
1. X-Bomdrift-Bridge-Secret custom header (constant-time compare)
2. Event-type: ms.vss-code.git-pullrequest-comment-event
3. Project-UUID allowlist
4. Commenter is a member of the project's Contributors team
5. PR-context: status=active AND targetRefName===MAIN_BRANCH
→ triggers POST /_apis/pipelines/<id>/runs with BOMDRIFT_NOTE_BODY
as a templateParameter.
Both pipeline templates updated:
- bitbucket-pipelines.yml gains a 'custom: bomdrift-comment-suppress'
step (only fires when the bridge triggers it).
- azure-pipelines.yml restructured into stages with a new
bomdrift_suppress stage gated on the BOMDRIFT_NOTE_BODY parameter
(normal PR builds leave it empty so only the diff stage runs).
Both new bridge worker.js files declare the canonical
BOMDRIFT_SUPPRESS_REGEX (with comment pointing at
scripts/parse-suppress-comment.sh) and are now picked up by
scripts/check-suppress-regex-sync.sh.
Docs:
- docs/src/bitbucket.md: 'Comment-driven suppression (advanced)'
section mirroring the GitLab equivalent.
- docs/src/azure-devops.md: same.
- STATUS.md: Bitbucket + Azure DevOps rows note v0.9.5 bridge support.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The v0.9 SPDX evaluator treated the right-hand side of a 'WITH' clause
as informational only — Apache-2.0 WITH LLVM-exception was permitted by
allow=[Apache-2.0] regardless of which exception applied. v0.9.5 adds
per-exception allow/deny:
[license]
allow_exceptions = ["LLVM-exception", "Classpath-exception-2.0"]
deny_exceptions = ["GCC-exception-3.1"]
--allow-exception ID,ID (repeatable + comma-split)
--deny-exception ID,ID
Semantics:
* Base-license deny check stays conservative (any required atomic in
the deny list → violation).
* Base allow + exception checks share Expression::evaluate, so OR
branches resolve correctly: (Apache-2.0 WITH LLVM-exception) OR
BSD-3-Clause with deny_exceptions=[LLVM-exception] permits via the
BSD-3-Clause path.
* Both exception lists empty → exceptions are permitted (preserves
v0.9 behavior; back-compat).
LicenseViolation::matched_rule cites the precise exception identifier
('exception:LLVM-exception denied' or 'exception:LLVM-exception not in
allow list'). The SARIF synthetic id encodes the full license string
including the WITH suffix, so partialFingerprints differ between
exception-driven and base-license violations on the same component
(asserted in a new SARIF test).
The --debug-calibration license row now surfaces matched_rule directly
(instead of the bare kind tag) so operators tuning policy see the why,
not just deny/not-allowed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lper, SPDX exceptions)
… regex, multi-SCM bridges)
- Bump Cargo.toml + Cargo.lock 0.9.0 → 0.9.5. - CHANGELOG.md: v0.9.5 entry covering per-exception SPDX allow/deny, Bitbucket + Azure DevOps comment-suppress bridges, public parse_synthetic_id helper, spdx crate exact-pin, BaselineEntry unification, CI Rust pin, suppress-regex single source of truth, GitLab threading docs. - docs/src/roadmap.md: add "Shipped (v0.9.5 — polish + multi-SCM parity)" section; remove per-exception SPDX from future candidates; add reachability cross-reference to non-goals. - Bump example v0.9.0 pins → v0.9.5 in README, quickstart, action-broke issue template. 389 tests pass, clippy + fmt clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SBOM diff
Added (1)Show details
Removed (1)Show details
Version changed (1)Show details
False positive? Report it · Suppress a finding? Comment |
Pinning CI Rust to 1.88 (this release) surfaces the clippy::uninlined_format_args lint that newer toolchains had already accepted. Inline the four offending sites: - src/clock.rs::is_expired_iso8601 (anyhow! macro) - src/render/markdown.rs::section_open (writeln + write) - src/render/sarif.rs test (assert! macro) Verified locally with rustup 1.88 toolchain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Promote clock::tests::env_lock to clock::test_env_lock (pub(crate), #[cfg(test)]). Have baseline::tests::lock_today, vex::tests::pin_clock, and enrich::registry::tests::days_since_zero_for_now all acquire the same crate-wide mutex. Before this fix, each module had a local mutex (or no mutex at all), so parallel `cargo test` threads in different modules could race on SOURCE_DATE_EPOCH — manifesting as an intermittent ubuntu-latest CI failure on baseline::tests::expired_object_entry_warns_and_does_not_ suppress (PR #22 first run). macOS and Windows happened to schedule the relevant tests in non-conflicting order. Verified: 3 consecutive `cargo test --release --all-features` runs green with default parallelism (was previously failing 1 in N). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v0.9.5 — polish + multi-SCM parity
The "v0.9 follow-up backlog" milestone. v0.9.5 ships eight items that
were deferred to v1.0 in the v0.9 changelog, with one headline feature
that's actually a behavior change rather than polish: comment-driven
suppression bridges for Bitbucket Cloud and Azure DevOps, giving
bomdrift parity across all four major SCMs.
Highlights
[license] allow_exceptions/deny_exceptionsarrays +--allow-exception/--deny-exceptionCLI flags. License expressions like
Apache-2.0 WITH LLVM-exceptionnow evaluated at the exception level too, not just the base.
Worker references with the same five guards as the v0.9 GitLab
bridge.
examples/bitbucket-pipelines/comment-bridge/andexamples/azure-devops/comment-bridge/. bomdrift now hascomment-driven suppression on GitHub (action), GitLab, Bitbucket,
and Azure DevOps.
bomdrift::vex::parse_synthetic_idpublic helper — round-tripsbomdrift's synthetic finding IDs back to a structured kind. Lets
external VEX tooling identify which finding a statement targets.
spdxcrate exact-pinned to=0.10.9. SPDX list updates canshift
LicenseId.is_gnu()membership and silently change policysemantics; pin makes bumps deliberate.
BaselineEntry/ExpiredEntryunified internally, publicalias preserved. No behavior change.
newer clippy lints (1.94 added several that broke v0.8 until
handled).
scripts/parse-suppress-comment.shplus a CI sync guard(
scripts/check-suppress-regex-sync.sh) that fails the build ifthe shell + JS copies drift.
docs/src/gitlab-ci.md. Closes the open question from v0.7.Process
Two parallel background sub-agents ran on isolated git worktrees
with disjoint file ownership (Rust core in one, platform/docs/CI/
bridges in the other), avoiding the cli.rs/lib.rs/markdown.rs
conflict pain that v0.8 and v0.9 had to serialize around. Both
branches merged cleanly into
release/v0.9.5with zero conflicts.Test status
cargo test --releaseubuntu /macos / windows.
cargo clippy --all-targets --all-features --release -- -D warningsclean.cargo fmt --all -- --checkclean.scripts/check-suppress-regex-sync.shfails the buildif the shell + JS comment-parser copies drift.
Scope notes
Deferred (still v1.0 candidates):
Post-merge checklist (maintainer)
v0.9.5on the merge commit.v1tag tov0.9.5.release/v0.9.5,feat/v0.9.5-rust, andfeat/v0.9.5-platformbranches.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com