Skip to content

Commit a7cd84a

Browse files
bomly-guyclaude
andauthored
build: require Go 1.27 (#412)
* docs: plan the SDK maturity program (ADR-0036 through ADR-0039) Records the four architecture decisions behind the cross-repo SDK maturity effort — content-addressable dependency identity, SBOM assertions as typed model fields, a single SDK home for PURL and SPDX behavior, and the Go 1.27 move with strict untrusted-document parsing — and adds dev-docs/SDK_MATURITY_PLAN.md, the phased execution plan that sequences them across bomly-sdk, the plugin repos, and this CLI. The plan folds in issues #410 and #396 and five previously unreported fidelity defects found while surveying the current state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: record the SDK-first placement rule as ADR-0040 The principle behind the maturity program becomes a standing rule: behavior about shared domain objects lands in bomly-sdk first, and the CLI and plugins keep only what is theirs by nature. Recorded as ADR-0040 and wired into the fix-at-the-right-depth convention in CLAUDE.md and AGENTS.md so future fixes and features default to the source of truth instead of restarting the drift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — identity facet safety, PURL suffix delimiter, precision fixes ADR-0036: the occurrence suffix delimiter moves off '#', which PURL syntax uses for subpaths, and raw ResolvedURL is excluded from the persistent content-address facets (machine-specific, credential-bearing, and hashing does not protect low-entropy secrets); nodes distinguishable only by raw evidence keep distinct run-local IDs but share the stable facet address. ADR-0039: duplicate-key semantics described accurately (input order, replace-or-merge by target type) and the toolchain auto-download claim qualified to Go 1.21+ with GOTOOLCHAIN=auto. Plan: PURL qualifier loss limited to the BuildPackageURL fallback; strict ingest wording covers invalid UTF-8 alongside duplicate names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — published-ID safety, injective encoding, per-entry assertions ADR-0036: readable IDs never encode raw evidence even hashed (raw-only discriminators become run-local ordinals); PURL qualifiers enter identity only through a purlkit allowlist with URL gates; the content address encoding becomes length-prefixed so untrusted delimiter bytes cannot collide facets. ADR-0037: document assertions attach to GraphEntry (GraphContainer is multi-entry), and the scope mapping is defined for the set with a scalar projection rule plus a lossless bomly:scopes side channel. ADR-0038: the deprecated-SPDX-ID replacement map relocates into spdxkit as an audited authority rather than being deleted — the license list marks deprecation but does not encode replacements. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — facet timing, ADR-0040 loan, plan/ADR consistency ADR-0036: occurrence facets are assigned at consolidation, after contradiction is established — never at node creation — so ADR-0033's gap-filling keeps folding origin-less witnesses instead of duplicating nodes. Plan: the phase-0 CycloneDX scope fix is recorded as an explicit ADR-0040 loan (SDK issue filed and linked, replaced in 2.6); phases 1.2 and 1.4 now match the amended ADRs (replacement map relocates; document assertions attach per GraphEntry). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — identity URL strictness, export entry path, address is not a node key ADR-0036: the identity facet strips query and fragment from artifact URLs too — stricter than ADR-0033's publication rule, which keeps a benign artifact query — so signed/tokenized URLs cannot enter the address or the published suffix; and the content address is documented as a stable-facet class identifier, never a per-node primary key (address-keyed stores pair it with a local occurrence discriminator). ADR-0037/plan 2.5: the export surface takes the prepared entries rather than the bare merged graph, so per-entry document assertions actually reach the codec instead of being discarded at consolidation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — fallback ID escaping, merged-export projection, plugin toolchain phase ADR-0036: the occurrence delimiter is reserved in both ID families — the fallback coordinate form percent-encodes whitespace and the delimiter per field, so an unescaped base like 'a@b 1' can no longer collide with 'a@b' plus suffix '1'. ADR-0037: the merged-export projection is defined — one document links each source's identity (SPDX externalDocumentRefs, CycloneDX bom-type external reference) rather than re-asserting it, and the fixed-point promise is scoped to single-source flows. ADR-0039/plan 0.2: the nine bomly-plugin-* repos join the toolchain phase, each bumping before adopting the first 1.27 SDK tag, since a dependency's go directive sets their minimum. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — query-distinguished occurrences, typed reference locators ADR-0036: contradiction detection keeps using the ADR-0033-normalized origin (query intact); when identity normalization makes established contradictions coincide — two artifacts distinguished only by query — they are handled like raw-evidence-only occurrences with distinct run-local ordinals and a shared stable-facet address, so safety normalization narrows what persists but never folds occurrences ADR-0033 keeps distinct. ADR-0037: ExternalReference carries a typed locator validated per reference category — URL categories pass the ADR-0033 gates, identifier categories (purl, cpe, advisory IDs) validate against their own grammars — since Bomly itself emits non-URL SPDX locators today. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: usage facts carry their attribution (reachable ∧ runtime ∧ direct) Scope, relationship, and reachability are usage facts, and conjunctive filters must hold of one usage — today Scopes is a node-level union with no site attribution, Relationship a merged scalar, and Reachability vulnerability-level with no module link, so a workspace using a package directly-in-dev and transitively-at-runtime satisfies 'reachable, runtime, direct' node-wise while no single usage does. ADR-0037 and phase 1.4 now record scope/relationship per PackageLocation (the union becomes derived) and the establishing module root on Reachability, so filters select usages and displays show the path whose attribution satisfies every conjunct. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — repeatable reachability evidence, producer migrations ADR-0037: reachability becomes a repeatable evidence list keyed by module root, with the vulnerability annotation as the derived summary — a singular annotation with one root field could keep at most one analysis after vulnerability consolidation — and the conjunctive join is evidence-to-location within the same root. Plan: new phase 2.8 migrates the producers and consumers (native detectors write per-site scopes/relationship, the four analyzer repos emit per-module evidence as behavioral bumps, CLI/MCP filtering joins through attribution, with a regression test for the dev-direct vs runtime-transitive workspace); the risk table no longer calls analyzer bumps mechanical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — ephemeral insertion discriminator, kind-preserving copies, SPDX link data ADR-0036: the ID-keyed graph means same-detector contradicting records must coexist before consolidation classifies them, so the SDK insertion entry point assigns an ephemeral non-durable discriminator that consolidation finalizes (fold as gap, or replace with the durable facet) — the ephemeral form never reaches output or persistence. ADR-0037: the SDK ships a kind-preserving edge-copy/rename primitive so consolidation's edge rebuilds cannot flatten ingested relationship kinds to depends-on, with a guard test; and the per-entry carrier retains what source links require, captured at ingest — SPDX namespace plus document checksum, CycloneDX serial plus version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — normative parameters for identity, scopes, references, projections ADR-0036: fallback tuples enter identity only post-normalization with idempotence as the guarded invariant; ordinal ordering is stated (lexicographic by contradiction key, ties by manifest path then location); readable-ID parameters fixed (space delimiter, 6-byte sha256 hex suffix, o-decimal ordinals, percent-escaping, last-unescaped-space decode); the address encoding is byte-fixed (UTF-8, 4-byte big-endian lengths, tag/identity/occurrence order, zero-length absents, 16-byte truncation) with golden vectors as a 1.3 deliverable; addresses are post-consolidation only; silent shortening is prohibited. ADR-0037: ExternalReference gains Category to round-trip the SPDX triple, hashes ride the bomly: comment channel in SPDX; complete scope-carrier rules (sorted deduped list, carrier-beats-scalar, unknown-drop-with-warning, scalar ingest mapping); the usage join key is (module root, declaration site) with no cross-site mixing; the merged-document projection is defined per format (SPDX DESCRIBES-per-root + externalDocumentRef with checksum, CycloneDX BOM-Link urn:cdx:serial/version) with checksums captured for every ingested document. ADR-0039: the strict-ingest guarantee is narrowed to exactly duplicate names and invalid UTF-8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: address review — location module root, optional-scope preservation, locator-kind dispatch ADR-0037: the location record carries its owning module root as a field (two modules can share one lockfile path, so the site alone cannot join evidence to its usage); the ingested CycloneDX scope scalar is preserved as a source assertion and re-emitted verbatim on single-source export so optional and excluded never collapse; and external-reference validation dispatches on an explicit locator kind derived from the normalized format/category/type tuple, never on the SPDX-only Category axis. Plan 2.8 has detectors populate the module root on locations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: reachability evidence references exact occurrence nodes; reverse index is derived Folds the agreed occurrence-attribution model into ADR-0037 and the plan: reachability evidence may carry optional DependencyRefs to the exact graph nodes involved (final by analysis time — analyzers run post-consolidation), with the module root as the mandatory attribution floor and node refs the precision ceiling for analyzers that can attribute (govulncheck build module versions, jsreach nested node_modules copies). Display follows the evidence honestly: exact occurrence with refs, 'reachable via this module' / 'not established here' without — never 'unreachable'. The package→nodes direction stays derived via an SDK reverse-index helper; a stored back-reference would break the registry's position-free design and go stale on renames. Plan 1.4/2.7/2.8 updated accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * build: require Go 1.27 Bumps the go directive to 1.27.0 (plan step 0.2, CLI slice; ADR-0039) and absorbs the one-time go mod tidy require-block consolidation. Every workflow reads the toolchain via go-version-file, so no CI edits. CONTRIBUTING gains the Go prerequisite (GOTOOLCHAIN=auto downloads it automatically); .gitignore gains go.work/go.work.sum so the local cross-repo workspace never lands in a commit — main must stay replace-free for remote go install. The bump exposed one latent test defect: Go 1.27 test binaries carry dependency build info, so TestRootVersion_IncludesTrackedDependencyVersions ran its assertion loop for the first time in years and failed — the rendered format gained the module path long ago while the test still expected 'Syft:'. The test now asserts the exact rendered line and fails loudly when dependency resolution comes back empty, so it can never pass vacuously again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * build: bump golangci-lint to v2.13.2 for Go 1.27 golangci-lint v2.12.0 is built with go1.26 and refuses a module whose go directive targets 1.27 ('the Go language version used to build golangci-lint is lower than the targeted Go version'). v2.13.2 is the current release built on Go 1.27; make lint passes with 0 issues. Bumped in both pin sites — the Makefile and the lint workflow step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: pin the v1 wire's lenient decode with fixtures when strictness lands Review nitpick on the stacked toolchain PR, folded into ADR-0039 and plan 2.6: the plugin wire's compatibility contract is frozen fixtures, and the leniency itself — duplicate names accepted, invalid UTF-8 replaced — gets pinned by SDK wire fixtures when json/v2 strict ingest lands, so the migration cannot tighten the trusted plugin wire by accident. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent cff609e commit a7cd84a

8 files changed

Lines changed: 41 additions & 22 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- name: Run golangci-lint
3030
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
3131
with:
32-
version: v2.12.0
32+
version: v2.13.2
3333

3434
test:
3535
name: Test

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@ qodana.yaml
4040
# accidental root-level build outputs
4141
/bomly
4242
/bomly-lite
43+
44+
# local cross-repo workspace (go work init . ../bomly-sdk); never committed —
45+
# main must stay replace-free so remote go install keeps working
46+
go.work
47+
go.work.sum

CONTRIBUTING.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public issue tracker.
3232

3333
## Development
3434

35+
Building requires Go 1.27 or newer. On Go 1.21+ with the default
36+
`GOTOOLCHAIN=auto`, the right toolchain downloads automatically from the
37+
`go.mod` directive; with `GOTOOLCHAIN=local` or an older Go, install Go 1.27
38+
first.
39+
3540
```sh
3641
make build # bin/bomly and bin/bomly-lite
3742
make test # unit tests (includes the plugin fixture compile check)

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
BINARY_NAME=bomly
22
LITE_BUILD_TAGS=bomly_external_syft,bomly_external_grype
3-
GOLANGCI_LINT_VERSION=v2.12.0
3+
GOLANGCI_LINT_VERSION=v2.13.2
44
GO_LICENSES_VERSION=v1.6.0
55
GOPATH_BIN=$(shell go env GOPATH)/bin
66
EXE_SUFFIX=$(if $(filter Windows_NT,$(OS)),.exe,)

dev-docs/SDK_MATURITY_PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ the golden refresh happens **once**:
160160
| 2.3 | Adopt identity: node IDs SDK-derived end to end; regenerate schemas, goldens, smoke; release-notes callout for the one-time ID change | ADR-0036 |
161161
| 2.4 | **Close #410**: `LicenseRef-*` + `hasExtractedLicensingInfos` emission, mixed-validity composition, canonical ingest coordinates via `SplitEcosystemName`; round-trip asserts `Org`+`Name`+`EcosystemName()` together | Also removes ADR-0035's recorded limitation |
162162
| 2.5 | **Close #396** on the typed model: ingest populates typed fields through their gates; the export surface takes the prepared entries rather than the bare merged graph, so per-entry document assertions reach the codec; export projects them; merge follows the declared classes; fixed-point test (single-source export → ingest → export byte-stable for preserved fields; a merged export links source identities per ADR-0037, with merged fixtures for both formats validated through the codecs and the official format validators); hostile-document fuzz coverage | Deferred #391 items stay deferred per ADR-0037 |
163-
| 2.6 | Export full scope sets (fixes survey defect 2) and adopt json/v2 strict ingest with documented rejection behavior | ADR-0039 |
163+
| 2.6 | Export full scope sets (fixes survey defect 2) and adopt json/v2 strict ingest with documented rejection behavior, and pin the v1 plugin wire's lenient decode with SDK wire fixtures so the migration cannot tighten it by accident | ADR-0039 |
164164
| 2.7 | Registry-lookup and PURL-fallback helper consolidation across output/render/tui/mcp presentation layers, built on the SDK's derived reverse-index/lookup helper | Survey §3 items 5–6 |
165165
| 2.8 | Usage-attribution adoption: native detectors record per-site scopes, relationship, and the owning module root on locations; the four analyzer repos emit per-module-root reachability evidence, with optional `DependencyRefs` to exact occurrence nodes where attributable — govulncheck via build module versions, jsreach via nested `node_modules` locations (behavioral bumps, not mechanical); CLI filtering, rendering, and MCP join conjunctive filters through attribution; regression test for the workspace case where a package is direct-in-dev in one module and transitive-at-runtime in another | Without producer and consumer migration the 1.4 fields stay empty and the conjunction cannot work |
166166

dev-docs/adr/0039-both-modules-build-on-go-1-27.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ error. Other reader-divergence classes (case-variant field matching, for
4343
one) are not covered by json/v2's defaults and are not claimed here; closing
4444
any of them would be its own validation with its own decision. The change is
4545
documented as deliberate behavior in `docs/SBOM.md`. The plugin wire (`bomly.plugin.v1`) keeps its current
46-
decoding semantics: its compatibility contract is frozen fixtures, not
47-
strictness, and tightening it is a protocol decision that would need its own
48-
ADR.
46+
decoding semantics: enabled plugins are trusted native processes, so its
47+
compatibility contract is frozen fixtures, not strictness, and tightening it
48+
is a protocol decision that would need its own ADR. When strict parsing
49+
lands, the wire's lenient decode is itself pinned by fixtures in the SDK's
50+
wire-compatibility suite (a payload with a duplicate name and one with
51+
replaced invalid UTF-8 must keep decoding under v1), so the json/v2
52+
migration cannot tighten the plugin wire by accident.
4953

5054
**Standard library over dependencies where it now suffices.** The stdlib
5155
`uuid` package takes over document serial-number generation. New language

go.mod

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/bomly-dev/bomly-cli
22

3-
go 1.26.3
3+
go 1.27.0
44

55
require (
66
github.com/BurntSushi/toml v1.6.0
@@ -16,6 +16,7 @@ require (
1616
github.com/bomly-dev/bomly-plugin-pyreach-analyzer v0.1.0
1717
github.com/bomly-dev/bomly-plugin-scorecard-matcher v0.1.0
1818
github.com/bomly-dev/bomly-plugin-syft-detector v0.1.0
19+
github.com/bomly-dev/bomly-sdk v0.4.2
1920
github.com/charmbracelet/bubbles v1.0.0
2021
github.com/charmbracelet/bubbletea v1.3.10
2122
github.com/charmbracelet/lipgloss v1.1.0
@@ -32,18 +33,6 @@ require (
3233
gopkg.in/yaml.v3 v3.0.1
3334
)
3435

35-
require (
36-
github.com/anchore/clio v0.1.1 // indirect
37-
github.com/anchore/grype v0.117.0 // indirect
38-
github.com/anchore/syft v1.51.0 // indirect
39-
github.com/evanw/esbuild v0.28.1 // indirect
40-
github.com/glebarez/sqlite v1.11.0 // indirect
41-
github.com/pandatix/go-cvss v0.6.2 // indirect
42-
golang.org/x/net v0.58.0 // indirect
43-
golang.org/x/vuln v1.6.0 // indirect
44-
google.golang.org/protobuf v1.36.12 // indirect
45-
)
46-
4736
require (
4837
cel.dev/expr v0.25.2 // indirect
4938
cloud.google.com/go v0.123.0 // indirect
@@ -70,6 +59,7 @@ require (
7059
github.com/acobaugh/osrelease v0.1.0 // indirect
7160
github.com/adrg/xdg v0.5.3 // indirect
7261
github.com/agext/levenshtein v1.2.3 // indirect
62+
github.com/anchore/clio v0.1.1 // indirect
7363
github.com/anchore/fangs v0.1.1 // indirect
7464
github.com/anchore/go-collections v0.1.1 // indirect
7565
github.com/anchore/go-homedir v0.1.1 // indirect
@@ -80,7 +70,9 @@ require (
8070
github.com/anchore/go-struct-converter v0.2.0-rc2 // indirect
8171
github.com/anchore/go-sync v0.1.1 // indirect
8272
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 // indirect
73+
github.com/anchore/grype v0.117.0 // indirect
8374
github.com/anchore/stereoscope v0.3.0 // indirect
75+
github.com/anchore/syft v1.51.0 // indirect
8476
github.com/andybalholm/brotli v1.2.0 // indirect
8577
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
8678
github.com/aquasecurity/go-pep440-version v0.0.1 // indirect
@@ -114,7 +106,6 @@ require (
114106
github.com/bodgit/plumbing v1.3.0 // indirect
115107
github.com/bodgit/sevenzip v1.6.1 // indirect
116108
github.com/bodgit/windows v1.0.1 // indirect
117-
github.com/bomly-dev/bomly-sdk v0.4.2
118109
github.com/cespare/xxhash/v2 v2.3.0 // indirect
119110
github.com/charmbracelet/colorprofile v0.4.3 // indirect
120111
github.com/charmbracelet/harmonica v0.2.0 // indirect
@@ -152,13 +143,15 @@ require (
152143
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
153144
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
154145
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
146+
github.com/evanw/esbuild v0.28.1 // indirect
155147
github.com/facebookincubator/nvdtools v0.1.5 // indirect
156148
github.com/fatih/color v1.18.0 // indirect
157149
github.com/felixge/fgprof v0.9.5 // indirect
158150
github.com/felixge/httpsnoop v1.0.4 // indirect
159151
github.com/fsnotify/fsnotify v1.9.0 // indirect
160152
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
161153
github.com/glebarez/go-sqlite v1.22.0 // indirect
154+
github.com/glebarez/sqlite v1.11.0 // indirect
162155
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
163156
github.com/go-git/go-billy/v5 v5.9.1 // indirect
164157
github.com/go-git/go-git/v5 v5.19.2 // indirect
@@ -252,6 +245,7 @@ require (
252245
github.com/opencontainers/runtime-spec v1.3.0 // indirect
253246
github.com/openvex/go-vex v0.2.8 // indirect
254247
github.com/package-url/packageurl-go v0.1.5 // indirect
248+
github.com/pandatix/go-cvss v0.6.2 // indirect
255249
github.com/pborman/indent v1.2.1 // indirect
256250
github.com/pelletier/go-toml v1.9.5 // indirect
257251
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
@@ -315,19 +309,22 @@ require (
315309
golang.org/x/crypto v0.55.0 // indirect
316310
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
317311
golang.org/x/mod v0.40.0 // indirect
312+
golang.org/x/net v0.58.0 // indirect
318313
golang.org/x/oauth2 v0.36.0 // indirect
319314
golang.org/x/sync v0.22.0 // indirect
320315
golang.org/x/sys v0.47.0 // indirect
321316
golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 // indirect
322317
golang.org/x/text v0.41.0 // indirect
323318
golang.org/x/time v0.15.0 // indirect
324319
golang.org/x/tools v0.49.0 // indirect
320+
golang.org/x/vuln v1.6.0 // indirect
325321
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
326322
gonum.org/v1/gonum v0.17.0 // indirect
327323
google.golang.org/api v0.271.0 // indirect
328324
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
329325
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
330326
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
327+
google.golang.org/protobuf v1.36.12 // indirect
331328
gopkg.in/warnings.v0 v0.1.2 // indirect
332329
gorm.io/gorm v1.31.2 // indirect
333330
howett.net/plist v1.0.1 // indirect

internal/cli/version_cmd_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,16 @@ func TestRootVersion_IncludesTrackedDependencyVersions(t *testing.T) {
136136
if !strings.Contains(versionText, "bomly 0.9.0-test") {
137137
t.Fatalf("expected version output to contain core version, got:\n%s", versionText)
138138
}
139-
for _, item := range selectedDependencyVersions() {
140-
want := item.Label + ":"
139+
// Test binaries carry dependency build info on the required toolchain,
140+
// so an empty resolution means the assertion loop below would be
141+
// vacuous — which is exactly how the old "Label:" expectation survived
142+
// years after the rendered format gained the module path.
143+
resolved := selectedDependencyVersions()
144+
if len(resolved) == 0 {
145+
t.Fatal("selectedDependencyVersions() resolved nothing; the assertions below would be vacuous")
146+
}
147+
for _, item := range resolved {
148+
want := item.Label + " (" + item.Module + "): " + item.Version
141149
if !strings.Contains(versionText, want) {
142150
t.Fatalf("expected version output to contain %q, got:\n%s", want, versionText)
143151
}

0 commit comments

Comments
 (0)