Skip to content

fix(golang): reduce required co-package updates to minimal necessary set#63

Merged
kbsteere merged 17 commits intochainguard-dev:mainfrom
kbsteere:reduce-required-co-package-updates
May 4, 2026
Merged

fix(golang): reduce required co-package updates to minimal necessary set#63
kbsteere merged 17 commits intochainguard-dev:mainfrom
kbsteere:reduce-required-co-package-updates

Conversation

@kbsteere
Copy link
Copy Markdown
Member

@kbsteere kbsteere commented May 1, 2026

The co-update detection was flagging test-only and indirect transitive deps (testify, go-cmp, sys) that Go's MVS handles automatically, while missing the packages that actually matter.

  • CheckTransitiveRequirements now only checks direct project deps, removing indirect noise
  • FindVersionGroupPackages groups packages by module family prefix so all go.opentelemetry.io/otel/* move together including drifted versions
  • findMinCompatibleVersion walks release history to give actionable version recommendations instead of no-op current-version alerts
  • CheckAPIBreakingChanges uses apidiff to confirm genuine signature changes before surfacing them

Two additional fixes: the main module is skipped when it appears in a bump list to prevent gobump errors, and unloadable new versions are treated as breaking changes covering removed packages and broken releases.

kbsteere added 12 commits April 30, 2026 09:34
… set

- Filter CheckTransitiveRequirements to only flag direct project deps;
  indirect deps are resolved automatically by Go's MVS and cannot cause
  API breakage in the project's own code
- Add FindVersionGroupPackages to detect tightly-coupled module ecosystems
  (e.g. all go.opentelemetry.io/otel/* packages) using module family prefix
  heuristics, covering both co-released and version-drifted siblings
- Add second API compat pass over discovered co-updates so community
  packages that import a co-updated dep (e.g. otelgrpc importing otel)
  are surfaced as API compat alerts
When CheckAPICompatibilityWithCache flags a package (e.g. go-ldap/ldap/v3)
that imports a dep being updated (e.g. go-ntlmssp), walk the package's
version history to find the lowest release whose go.mod requires the
updated dep at >= the target version.

This converts API compat alerts from no-op suggestions (pkg@current) into
actionable version recommendations (pkg@min-compatible), directly addressing
build failures where a dep's API signature changed between versions.
Add CheckAPIBreakingChanges which compares the exported API of a package
between two versions using golang.org/x/exp/apidiff. This provides a
precise signal for whether a dep version bump introduces incompatible
changes (e.g. ProcessChallenge gaining a fourth argument in go-ntlmssp),
as opposed to compatible additions (new functions, new fields).

The function type-checks both versions using golang.org/x/tools/go/packages
in isolated temp modules, then runs apidiff.Changes to classify each
exported symbol change as compatible or incompatible.

This can be used alongside findMinCompatibleVersion and
CheckAPICompatibilityWithCache to distinguish false-positive compat alerts
from confirmed breaking changes that require downstream packages to update.
- resolveAndFilterPackages now skips any package whose path matches the
  main module of the go.mod being analyzed, logging a warning instead of
  letting it reach gobump where it would fail with 'bumping the main
  module is not allowed'
- checkMissingTransitiveDeps applies the same guard as a second line of
  defence for co-updates discovered through transitive checks
- CheckAPIBreakingChanges now treats an unloadable new version (removed
  package or internally broken release) as a breaking change rather than
  returning an error, giving callers a clear signal not to bump to that
  version
- Add sentinel errors errPackageNotFound and errNoTypeInfo to replace
  dynamic fmt.Errorf calls in loadPackageTypes (err113)
- Wrap packages.Error with %w in loadPackageTypes (errorlint)
- Wrap defer os.RemoveAll in closure discarding return value (errcheck)
- Fix stdlib import grouping for go/types and errors (gci, gofumpt)
- Extract runCoUpdateAPICompatChecks helper from checkMissingTransitiveDeps
  to reduce cyclomatic complexity below threshold (gocyclo)
…oUpdates

Extract the co-update detection core from checkMissingTransitiveDeps into
a new detectCoUpdates function so both the update path and analyzer path
use identical logic.

Previously checkTransitiveRequirementsForStrategy in analyzer.go had its
own diverged implementation: it used the deprecated CheckAPICompatibility,
had no version-group ecosystem detection, no findMinCompatibleVersion, no
main module skip, and included indirect project deps in version checks.

Both paths now share: FindVersionGroupPackages for ecosystem grouping,
CheckAPICompatibilityWithCache with findMinCompatibleVersion for actionable
version recommendations, main module skip, direct-only dep filtering, and
the second API compat pass for co-update deps.
RecommendStrategy was adding the main module to strategy.DirectUpdates
without checking whether the package being updated is the go.mod's own
module. The update path had this guard in resolveAndFilterPackages; now
both paths produce consistent output.
When findMinCompatibleVersion identifies a concrete minimum version for
an API compat alert (e.g. otelgrpc@v0.68.0 when bumping otel to v1.43.0),
add it to strategy.DirectUpdates so it appears in JSON, YAML, and deps
file output — not just as a human-readable warning string.

Packages already being updated are skipped to avoid duplicates.
- Set initial capacity on allMissingDeps and apiCompatibilityAlerts in
  detectCoUpdates to avoid realloc when co-updates match update count
- Parse go.mod once in RecommendStrategy and pass it to
  checkTransitiveRequirementsForStrategy to avoid reading the same file
  twice; fall back to parsing internally when caller passes nil
@kbsteere kbsteere requested a review from a team May 1, 2026 21:11
tcnghia
tcnghia previously approved these changes May 1, 2026
…rack

Packages like go.opentelemetry.io/otel/exporters/* use a v0.x cadence
while core otel uses v1.x. FindVersionGroupPackages was including them
in the version group and recommending them at the wrong target version
(e.g. v1.43.0 instead of v0.19.0).

Skip version group candidates whose current major differs from the target
major. The second-pass API compat check handles these packages correctly
via findMinCompatibleVersion, which finds the right v0.x version.
Add TestDetectCoUpdates_CrossMajorVersionGroupSkipped to guard against
recommending the wrong version for otel exporter packages. The otel
exporter family (go.opentelemetry.io/otel/exporters/*) uses v0.x while
core otel uses v1.x — the fix must find v0.19.0 via the API compat chain
rather than v1.43.0 from the version group.

Add a FindVersionGroupPackages test case documenting that the function
itself returns cross-major family members; the filter lives in
detectCoUpdates.
@kbsteere kbsteere enabled auto-merge (squash) May 4, 2026 14:03
@kbsteere kbsteere requested review from a team and tcnghia May 4, 2026 14:06
@kbsteere kbsteere marked this pull request as draft May 4, 2026 20:18
auto-merge was automatically disabled May 4, 2026 20:18

Pull request was converted to draft

tcnghia
tcnghia previously approved these changes May 4, 2026
…ersionGroupPackages

Move the cross-major version guard into FindVersionGroupPackages itself so
that all callers are protected, not just detectCoUpdates. Previously,
otel/exporters/prometheus@v0.60.0 would be included in the version group
when bumping core otel to v1.43.0, and the function would return it with
a target of v1.43.0 — a version that does not exist, causing go mod tidy
to fail.

The fix compares each candidate's semver major against the source package's
current version major and excludes any mismatch. The second-pass API compat
chain (findMinCompatibleVersion) then finds the correct v0.x version instead.

Removes the now-redundant duplicate guard that was in detectCoUpdates.
…packages

When FindVersionGroupPackages returns a package on a different major
version track (e.g. otel/exporters/prometheus v0.60.0 when targeting
otel v1.43.0), detectCoUpdates now calls findMinCompatibleVersion to
find the correct v0.x version (e.g. v0.65.0) rather than either
applying the wrong v1.x target or relying on the second-pass API compat
chain to catch it.

FindVersionGroupPackages returns all family members regardless of major
version — the caller is responsible for resolving the right version.
@kbsteere kbsteere marked this pull request as ready for review May 4, 2026 21:09
…pdates

- Move familyRoot declaration inside the cross-major branch where it is used
- Remove duplicate familyRoot argument from log message
- Use distinct Reason string for cross-major packages so it doesn't
  incorrectly say 'both at X' when the versions differ
@kbsteere kbsteere enabled auto-merge (squash) May 4, 2026 21:15
@kbsteere kbsteere requested a review from tcnghia May 4, 2026 21:15
@kbsteere kbsteere requested a review from a team May 4, 2026 21:16
@kbsteere kbsteere merged commit 5392bee into chainguard-dev:main May 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants