Add socket manifest dynamic-sbom-inference - #1467
Open
Jeppe Fredsgaard Blaabjerg (jfblaa) wants to merge 22 commits into
Open
Add socket manifest dynamic-sbom-inference#1467Jeppe Fredsgaard Blaabjerg (jfblaa) wants to merge 22 commits into
Jeppe Fredsgaard Blaabjerg (jfblaa) wants to merge 22 commits into
Conversation
Recursively discovers independent gradle/sbt/maven build roots and generates a Socket facts SBOM for each, without re-invoking the manifest script on subproject/reactor-module directories a parent build root already covers. Maven reactors in particular have a pom.xml per module, so naive marker-file discovery would over-invoke; coverage is tracked per ecosystem using the facts SBOM's own projects[].subprojectDir, which every build-tool producer already reports. This is a standalone command rather than an --auto-manifest extension, so it can force stricter behavior later without touching auto's existing contract. Scoped narrowly for this first PR: discovery plus generation only, one global socket.json config applied to every discovered root (no per-build-root cascade), no new CLI flags beyond --exclude-paths/--verbose. Refs REA-685, REA-553
--exclude-paths on socket manifest auto/gradle/kotlin/maven/scala/ dynamic-sbom-inference reused the flag description from the scan/reach flag definitions verbatim, which talks about "the scan" and full application reachability analysis. None of these commands scan or run reachability analysis themselves, so the wording was confusing when viewed via --help on any of them standalone. Adds a manifest-scoped excludePathsFlag with wording specific to manifest/facts generation and switches all six commands to it.
…ation
Different projects in a repo may need different JDKs for their build
tool. Adds a javaHome field to defaults.manifest.{gradle,maven,sbt} in
socket.json, configurable via the socket manifest setup wizard, and
threads it through to the actual build-tool invocation (overrides
JAVA_HOME for that spawn only, everything else about the environment is
left untouched).
Applies to socket manifest gradle/kotlin/maven/scala, socket manifest
auto, and socket manifest dynamic-sbom-inference. Scoped to the Socket
facts generation path only, not the legacy --pom conversion path. No
new CLI flag - socket.json/the setup wizard is the only configuration
surface for now.
socket manifest dynamic-sbom-inference previously read socket.json once
at the overall recursion root and applied that single config to every
discovered build root. It now resolves each build root's own nearest
socket.json (walking up from that root, bounded at the recursion root,
nearest wins - no merging), so different projects in the same repo can
carry their own settings instead of being forced onto one shared config.
Also adds $VAR/${VAR} expansion for javaHome, resolved against the CLI
process's own environment. A hardcoded absolute JDK path only works on
whoever's machine wrote it; referencing an env var each developer sets
themselves (e.g. $JAVA11_HOME) makes a shared socket.json portable
across machines. Fails closed with a clear message if the referenced
variable isn't set, rather than silently passing a broken path to the
build tool.
readSocketJsonCascade (renamed from readOrDefaultSocketJsonUpTo) now merges defaults.manifest.<ecosystem> field-by-field across every ancestor between a build root and the recursion root, nearest winning per field, instead of one file replacing the root config wholesale. A subproject can now override just javaHome while still inheriting the root's excludeConfigs/bin/etc., rather than having to restate the whole config. Ecosystems the override doesn't mention are left untouched. Verified against the sandbox tree: a root socket.json setting excludeConfigs plus a nested one setting only javaHome both applied together for that build root.
dynamic-sbom-inference always generates Socket facts SBOMs, never pom.xml. If a build root's cascaded socket.json sets facts: false (the pom-mode opt-out other manifest commands honor), that's ignored here rather than skipping the project or silently doing nothing - but it's a real, deliberate setting the user made, so it's surfaced as a warning rather than silently overridden.
dynamic-sbom-inference previously forced Socket facts generation over an explicit defaults.manifest.<ecosystem>.facts: false (pom mode), just warning about it. Reverted: facts: false now skips the project again, same as before that change, since this command has no pom-mode equivalent to fall back to. Also adds a separate ignored: true field (gradle/maven/sbt) scoped specifically to dynamic-sbom-inference, for projects that should be skipped during recursive generation regardless of their facts/pom preference for other commands. Kept as its own boolean rather than folding into facts, since facts already means something specific to other manifest commands and overloading it would require touching every consumer for a value only this command understands. New skippedIgnored outcome status covers both reasons, distinguished via the warning message.
A cascaded (not just root-level) disabled: true produces identical per-root behavior to the ignored field just added, so keeping both was redundant. Removed ignored; disabled now does double duty: root-only ecosystem-wide gating for auto/gradle/etc. (unchanged), plus a cascaded per-build-root skip specifically for dynamic-sbom-inference. Renamed the skippedIgnored outcome status to skippedDisabled to match.
…ursive setup for dynamic-sbom-inference - socket.json manifest fields now accept `null` as an explicit "clear the inherited value" sentinel, distinct from leaving a field unset; the setup wizard writes it when a previously-set value is cleared instead of just deleting the key. - Add a lightweight per-ecosystem workspace enumeration path for gradle/sbt/maven (new standalone scripts, kept fully separate from the existing facts-generation scripts) that discovers a build's subprojects without running dependency resolution. - Add a hidden `socket manifest setup --dynamic-sbom-inference` mode: configures root-level defaults per ecosystem, then recursively marks `disabled: true` on build roots matching `--exclude-paths`, leaving everything else untouched. - Speed up `dynamic-sbom-inference`'s handling of a disabled build root with many nested candidates by reusing the nearest already-resolved disabled ancestor instead of re-walking the whole config cascade for each one, and only logging the root cause instead of once per nested candidate.
Previously each excluded build root got its own disabled:true write, relying on cascade to skip descendants already covered by an ancestor write. That only worked when the excluded ancestor happened to be a build root itself; a non-project directory containing multiple sibling projects would leave later siblings enabled. Instead, group excluded projects by the shallowest directory that actually matches --exclude-paths and write disabled:true there once, covering every ecosystem and sibling/nested project beneath it regardless of whether that directory is a build root of its own.
… is unknown A build root whose facts generation fails (a build-tool crash or a blocking resolution failure) never produces its projects[] list, so there's no way to tell whether a later candidate underneath it is already covered by that root or a genuinely independent project. Continuing to process further candidates in that state risked misclassifying subprojects and piling on doomed attempts against a build already known to be broken. Fail closed instead: abort the entire recursive walk as soon as one build root's workspace layout can't be determined, rather than continuing to sibling and nested candidates.
The recursive wizard (`socket manifest setup --dynamic-sbom-inference`) previously only ever disabled build roots matching --exclude-paths; every other discovered root was left completely untouched, with no way to set its bin/JDK/opts short of running the plain single-project wizard on it directly. Every non-excluded candidate now gets an interactive configure-or-inherit-defaults prompt (in discovery order, parent before child), seeded with its cascaded effective value so accepting every prompt unchanged preserves whatever it already inherits. Disabling a specific candidate is intentionally not offered here - that stays --exclude-paths' job, so a whole excluded subtree still collapses into a single write. Also fixes a real bug surfaced along the way: askForBin pre-filled the hardcoded tool fallback (mvn/./gradlew/sbt) as the prompt's shown value, so accepting the default was indistinguishable from explicitly typing it and got written to socket.json for no reason. The shown default is now only ever a prior explicit value; the fallback is mentioned as a hint instead. A matching guard drops any ecosystem section that ends up empty so it doesn't count as configured or trigger a write. Finally, the root step now detects which ecosystems are actually present at cwd (reusing the same check the plain wizard uses) and asks about detected ecosystems first, phrased accordingly, before offering to configure undetected ones "anyway" for subprojects that might need them.
Fixes two correctness bugs found in an audit pass: leaving a config prompt blank when the field was already explicitly cleared (null) deleted the key instead of preserving the clear, silently reverting it to inheriting an ancestor's value; and --exclude-paths never reached the wizard's workspace enumeration, so excluding a broken reactor member didn't stop the wizard from still trying to resolve it and aborting the whole walk. Also tightens several UX rough edges in the recursive wizard: drops a redundant write confirmation inconsistent with the rest of the flow, fixes a tally line that double-counted re-enabled candidates and never reported disabled ones, and softens wording that overclaimed knowledge the wizard doesn't actually have yet (a build root at cwd, a fixed --exclude-paths prompt count). Trims the surrounding comments down to non-obvious rationale only, per repo comment-style guidelines.
Cuts several multi-line comment blocks down to their non-obvious why, matching the repo's comment-style guidelines.
Jeppe Fredsgaard Blaabjerg (jfblaa)
requested a review
from Martin Torp (mtorp)
August 3, 2026 12:54
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 5 potential issues.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 36f354f. Configure here.
- A root-disabled ecosystem was dropped from the build-tool scan entirely, so a nested socket.json could never re-enable it. Moved the strip-before- scan helper (previously wizard-only) to discover-manifest-roots.mts and applied it in generateRecursiveManifests too, letting the existing per-directory cascade check be the sole arbiter of skip vs. include. - runManifestFacts's progress line used logger.log (stdout), polluting --json output with non-JSON lines ahead of the payload. Switched to logger.info (stderr), matching every other status line in that function. - `socket manifest setup --dynamic-sbom-inference` forwarded --exclude-paths without validation, unlike every sibling manifest command. Added the same assertValidExcludePaths call. - Recursive generation inferred a build-root failure from whether process.exitCode changed during a call, which misclassifies a real failure as empty if the exit code was already non-zero beforehand. Gave runManifestFacts an explicit null (failure) vs. undefined (empty) return so callers don't have to infer it from global state. - Verbose error logging in enumerate-workspaces/run-manifest-facts string-coerced the caught error directly; switched to the existing getErrorMessageOr helper.
…cket-manifest-dynamic-sbom-inference-for-recursive
The merge brought package.json's version from 1.1.150-prerelease to a clean release version, so these inline snapshots no longer matched.
1.1.152 never actually published (still 404s on the registry, staged dist-tag is still 1.1.151), so bump to the next-version-hint convention used elsewhere in this repo's release history and refresh the CLI banner snapshots to match.
Drop reactor-member "skippedCovered" lines from the per-line table - they're implied by their parent's line already showing up, and the aggregate count in the summary still reports them. Also drop the "across N build root(s)" total from the summary line: it counted every candidate directory visited, including reactor members that aren't independent build roots, which overstated how many actually exist.
Drop failed/skipped/empty from the tally: a failure aborts the whole walk immediately rather than accumulating (and is already reported via its own fail message), and the disabled/covered/empty buckets count candidate directories rather than independent build roots, so a total there is just as misleading as the "N build root(s)" figure already removed.
Root cause of the flaky snapshot mismatches on this PR: whether the CLI itself redacts its version banner (VITEST baked in at build time) or prints the real one and leaves redaction to this test helper depends on env propagation into the build step, not just the test run - so the same source can produce either "<redacted>" or the real "vX.Y.Z-prerelease" depending on how it was built. normalizeBanner's regex only stripped a bare "vX.Y.Z", leaving a trailing prerelease suffix dangling in one case but not the other. Broadened it to match a trailing prerelease/build suffix and to be idempotent on an already-redacted value, so both cases normalize identically. Refreshed the now-correct snapshots.
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.

Summary
socket manifest dynamic-sbom-inference: recursively discovers gradle/sbt/maven build roots and generates a facts SBOM per independent root, with reactor/multi-module coverage tracking so submodules aren't re-invoked.socket manifest setup --dynamic-sbom-inferencewizard to scaffold per-projectsocket.jsonconfig across a tree, with a cascadingdefaults.manifest.*inheritance model and an explicitnullsentinel for clearing an inherited value.Test plan
pnpm check:tsc/pnpm check:lintpnpm build:dist:srcNote
Medium Risk
Large new surface around spawning Gradle/Maven/sbt and monorepo classification; mis-skips or fail-closed aborts could block SBOM generation on complex trees, but changes are localized to manifest CLI and bundled build-tool scripts.
Overview
Adds
socket manifest dynamic-sbom-inference, which walks a tree under CWD, finds Gradle/Maven/sbt build roots, and writes.socket.facts.jsonper independent root. Reactor members already covered by a parent’s factssubprojectDirlist are skipped; a failed root aborts the walk so later roots aren’t misclassified.socket manifest setupgains hidden--dynamic-sbom-inference(and--exclude-paths) to scan the tree, bulk-disable excluded paths, and interactively configure per-rootsocket.jsonwith cascade inheritance and explicitnullto clear inherited values.Lightweight workspace enumeration (Gradle/sbt/Maven init scripts/plugins/extension) lists projects without resolving dependencies, used by the setup wizard’s coverage pruning. Dist build copies
socket-workspacesassets alongside facts scripts.Manifest commands now use a shared
exclude-pathsflag description,javaHomefromsocket.json(with$VAR/${VAR}expansion intoJAVA_HOME), andrunManifestFactsreturns generated projects for recursive coverage.Reviewed by Cursor Bugbot for commit 36f354f. Configure here.