SBOM dependency resolution¶
This page covers language detection, licence resolution (local manifests and the package registry), system dependencies, and test dependency classification. The root project’s own manifests, output formats, and usage are on The sbom subcommand.
Language detection¶
For every other component – vendored trees, vendor/, node_modules,
resources, and loose source drops – adacovex infers the language from the
file extensions actually present. The extension is the source of truth: a
.py file reports Python even when a Cargo.toml sits next to it, and the
manifest language only breaks ties.
Supported extensions:
Ada:
.ads,.adb,.ada,.gprJavaScript:
.js,.mjs,.cjsTypeScript:
.ts,.tsxCSS:
.cssHTML:
.html,.htmPython:
.pyGo:
.goRust:
.rsC:
.c,.hC++:
.cpp,.cc,.cxx,.hpp,.hh,.hxxC#:
.csJava:
.javaRuby:
.rbPHP:
.phpSwift:
.swiftKotlin:
.kt,.ktsScala:
.scalaOCaml:
.ml,.mliLua:
.luaPerl:
.plHaskell:
.hsElixir:
.ex,.exsErlang:
.erl,.hrlClojure:
.clj,.cljsDart:
.dartShell:
.sh,.bashPowerShell:
.ps1SQL:
.sqlFortran:
.f,.f90,.f95,.f03Assembly:
.s,.asmR:
.rJulia:
.jlZig:
.zigVHDL:
.vhd,.vhdlTcl:
.tcl
A directory that mixes languages reports its top 3 languages by file count
(for example "Ada; C; C++"), so a mixed-language vendored drop is summarised
by what it actually contains rather than by a single guess.
Licence resolution¶
Vendored manifest ecosystems report their licence from the local manifest:
package.json (license) for npm/pnpm, Cargo.toml for cargo, and
pyproject.toml / composer.json for pypi / composer. A licence file
beside the manifest is classified into an SPDX identifier when the manifest
carries none. When both are silent, adacovex resolves the version, website,
and licence from the package registry as an online fallback. The
resolver dispatches on the ecosystem (the PURL type) through one static
table, so adding a language is one row rather than a new code path:
npm –
npm view <pkg> version license homepage --json, parsed for the three fields from one JSON object.pnpm –
pnpm show <pkg> version license homepage --json, likewise.cargo (Rust) –
cargo search <pkg>, with the SPDX id read from the(license: ...)token in the output.go –
go list -m --json <module>@latest, with the version read from theVersionfield. See Go modules below: the Go toolchain reports no licence and no website, so both come from files instead.An ecosystem with no portable, reliable registry query keeps an empty licence, version, and website; nothing is ever guessed.
Set ADACOVEX_OFFLINE=1 (see Global configuration and state)
to skip every registry query. The resolver then returns only the
offline-known fields, and the native suite runs with it, so the
assessment gates never touch the network.
Go modules¶
A vendored Go component resolves its version and licence offline, from files, and only falls back to the registry for the version. Nothing in this path names a forge, so the same code serves a module on GitHub, GitLab, Gitea, Codeberg, or a private mirror.
Version, offline. A vendored module’s own
requireblock lists only its dependencies, so the module’s version cannot come from its owngo.mod. It comes from the vendor root’smodules.txt, the manifestgo mod vendorwrites for every vendored module, as a# <module> <version>line – the only offline source of it.Version, fallback. When
modules.txtis absent,go list -m --json <module>@latestsupplies the latest published version. The query goes through whatever module proxyGOPROXYnames, so the Go toolchain decides which source answers and adacovex never constructs a host name.Licence. The Go toolchain reports no licence in any form, so the licence is read from the licence file beside the manifest (
LICENSE,LICENCE, orCOPYING, each with an optional.mdor.txtsuffix) and classified into an SPDX identifier by matching marker phrases from the licence text: Apache-2.0, MIT, MPL-2.0, ISC, Unlicense, and the two- and three-clause BSD variants. A row matches only when every marker it requires is present and none it excludes is present, so a four-clause BSD or an ambiguous GPL text reports no licence. A licence is never guessed.Website. Always empty.
go list -mhas no website field, and itsOrigin.URLnames a forge-specific VCS endpoint, not a project home page, so adacovex does not surface it.
A vanity import path (one whose first element is not a forge host, such as
golang.org/x/text, gopkg.in/yaml.v3, or k8s.io/api) resolves the same
way as a forge-hosted path: the version and the licence come from files, and
no URL is ever constructed from the import path.
The PURL type stays pkg:golang and the PURL is
pkg:golang/<module path>@<version>. The registry ecosystem token is go,
not golang, so the vendored-manifest reader carries both: the PURL type
follows the package-url specification while the resolver dispatches on the
name of the CLI it spawns.
The npm and pnpm rows answer for all three fields with one --json call,
so each component boots node once instead of once per field. The fallback
runs only when the offline read finds nothing, so a vendored package that
ships a licence never touches the network – and the same holds for a Go
tree that carries a modules.txt and a licence file. The
resolved licence flows into every SBOM format (CycloneDX licenses, SPDX
licenseConcluded / licenseDeclared, Markdown License column) and the
dashboard detail panel; the resolved version and website appear in the
dashboard detail panel and the /api/deps JSON.
The resolver caches each answer in a per-project store under the project’s
result cache (the same --cache-dir the scan uses), keyed by the target
directory as well as the ecosystem and package name, with a 7-day TTL – the
same scheme as the system-tool version probes. The content-addressed result
cache does not cover these registry calls, so without this layer a warm run
still re-paid them; the meta cache serves every repeat from disk with no
subprocess spawn, and two projects that share a cache directory never serve
each other’s resolved licence or version.
Bundled dashboard assets (FlexSearch, nomnoml, graphre) resolve
their licence and website live from the package registry when a loose vendored
copy is scanned, preferring pnpm show <pkg> license and falling back to
npm, yarn, then bun – the same preference chain as every JavaScript
component (see Licence resolution). The SBOM and the
Credits tab therefore track the real upstream licence instead of a hard-coded
copy.
System dependencies¶
Discover_System_Dev_Deps scans the project’s build and dev files (Makefiles,
shell scripts, Python tools, CI workflows, GPR files, Ada sources) for a
curated set of known system binaries, then keeps only the tools that are
installed on PATH. Each becomes a system-scope component of the root with
a pkg:generic/<name> PURL, a resolved version, and no external link or
licence – by design adacovex provisions only the version for system tools
and never guesses a repository or licence for them.
The table is deny-by-default and grouped by category (build drivers, language
implementations and package managers, VCS, documentation tooling, CI and
container plumbing, performance engineering). A tool lands in the SBOM only
when (a) its exact lowercase name appears as a whole word in one of the
scanned build files, and (b) it is installed on PATH; whole-word matching
keeps “makefile” from registering “make”. The table stores only the name and
category: the version-probe flag is inferred at run time by trying
--version, then -v, then the version subcommand, and taking the first
flag that yields a version token – so a subcommand-only tool (go, fossil,
git-lfs) needs no special-cased column and a misconfigured entry cannot
exist.
adacovex and its covex alias take one exception to rule (b): the
running binary is the dependency, so a reference registers adacovex from
the reference alone, at the running binary’s version. Three rules order
it: the audited project being adacovex itself keeps it the root; a
manifest declaration keeps its own scope (base from alire.toml, dev
from alire-dev.toml); otherwise the installation method decides, as
adacovex status reports it – a source build is dev, a binary
installed outside a project is system.
Resolved versions are never hardcoded. Each probe result is cached per
machine (outside the result cache) together with the identity of the
binary it was probed from: the PATH-resolved executable path plus its size
and mtime. A cached version is served only after adacovex re-resolves the
tool on PATH and compares that identity, so an upgraded, replaced, or
PATH-shadowed binary re-probes on the next run while an unchanged toolchain
serves from cache with no subprocess spawn. Every version in the SBOM
describes the binary that is installed now.
system is a first-class dependency scope, distinct from base, dev,
transitive, vendored, and test; the dashboard gives it its own filter
checkbox, badge colour, and legend entry, and the SBOM lists it under
system scope.
The language shows up in the dashboard Dependency tab (per-dependency detail
popup) and in every SBOM renderer: CycloneDX components[].language,
SPDX/JSON adacovex:language property, and the Markdown table’s
Language column.
Tools that are really language packages are never registered as system
tools; they register as language components instead. The root project’s
own manifests (package.json, go.mod, requirements*.txt) register
the same way, and every read of them is structural – the rules are on
Root project declarations.
Test dependencies¶
A dependency used only by the project’s tests is classified test (the
adacovex:dep_scope property value "test"). adacovex recognises
test-only declarations in every supported ecosystem’s manifest, in addition
to the Alire [[test-depends-on]] sections and test project files:
Manifest file |
Test label |
|---|---|
|
a dependency section whose key contains |
|
the |
|
no native test-only section: the name heuristic is the signal (a module path whose last path segment starts or ends with |
|
the |
|
gems inside a |
|
|
|
optional-dependencies extras whose name contains |
|
dependencies declared inside a |
|
no native test section: the name heuristic is the signal |
A vendored component (for example a package under node_modules or
vendor/) is classified test when the project manifest that owns the
vendor directory declares it under one of these test labels, or when its
name carries the test label. The name heuristic checks the full name and
then the last segment after any / or :, so @playwright/test,
test-case, github.com/stretchr/testify, and org.testng:testng all
qualify, and it applies to lockfile-resolved names as well:
pnpm-lock.yaml / package-lock.json / yarn.lock entries next to an
owner package.json, Cargo.lock crate names, and alire.lock crates
that the manifest sets leave transitive. The e2e fixture’s
@playwright/test is the canonical example: it stays a devDependencies
entry of tests/e2e/package.json (and a pnpm-lock.yaml entry) and is
classified test by name.