From 9f18cb4566c22de94ad78d08b4c75656554d0089 Mon Sep 17 00:00:00 2001 From: Jeppe Fredsgaard Blaabjerg Date: Tue, 29 Sep 2026 10:13:14 +0200 Subject: [PATCH 1/3] feat(manifest): mark build modules as firstParty in facts components Components whose exact GAV is a module of the scanned build now carry `firstParty: true` in .socket.facts.json, for maven, gradle and sbt. This lets dependency resolution stop treating the build's own modules as published packages that block upgrades. The match reuses the same GAV lookup that attaches project sources/targets. Published releases of a sibling module stay unmarked. Co-Authored-By: Claude Opus 5.5 --- src/commands/manifest/scripts/assemble.mts | 22 +++++++++--------- .../manifest/scripts/assemble.test.mts | 23 +++++++++++++++++++ src/commands/manifest/scripts/facts.mts | 2 ++ 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/commands/manifest/scripts/assemble.mts b/src/commands/manifest/scripts/assemble.mts index f4501a310..5634146c1 100644 --- a/src/commands/manifest/scripts/assemble.mts +++ b/src/commands/manifest/scripts/assemble.mts @@ -54,7 +54,11 @@ export function assembleFacts( const { directByRoot, finalNodes } = mergeByCoordinate(perRoot) const tool = (parsed.tool || 'gradle') as SocketFactsSbomMetadata['tool'] - const components = buildComponents(finalNodes) + const projectsByGav = new Map() + for (const p of parsed.projects.values()) { + projectsByGav.set(gav(p.group, p.name, p.version), p) + } + const components = buildComponents(finalNodes, projectsByGav) const projects = opts.emitProjects === false ? [] @@ -77,6 +81,7 @@ export function assembleFacts( artifactPaths: buildArtifactPaths( finalNodes, [...parsed.projects.values()], + projectsByGav, perRoot, fileExists, ), @@ -173,6 +178,7 @@ function mergeByCoordinate(perRoot: Map): { function buildComponents( finalNodes: Map, + projectsByGav: Map, ): SocketFactsSbomComponent[] { return [...finalNodes.keys()].sort().map(id => { const fn = finalNodes.get(id)! @@ -200,6 +206,9 @@ function buildComponents( if (!fn.prod) { comp.dev = true } + if (projectsByGav.has(gav(c.group, c.name, c.version ?? ''))) { + comp.firstParty = true + } if (fn.children.size) { comp.dependencies = [...fn.children].sort() } @@ -303,19 +312,10 @@ function buildClasspathByProject( function buildArtifactPaths( finalNodes: Map, projects: RawProject[], + projectsByGav: Map, perRoot: Map, fileExists: (path: string) => boolean, ): ResolvedArtifactPaths { - const projectsByGav = new Map< - string, - { sources: string[]; targets: string[] } - >() - for (const p of projects) { - projectsByGav.set(gav(p.group, p.name, p.version), { - sources: p.sources, - targets: p.targets, - }) - } const targetsByCoord = new Map() const targetsByGav = new Map() const sourcesByCoord = new Map() diff --git a/src/commands/manifest/scripts/assemble.test.mts b/src/commands/manifest/scripts/assemble.test.mts index af78da6d9..0f955130e 100644 --- a/src/commands/manifest/scripts/assemble.test.mts +++ b/src/commands/manifest/scripts/assemble.test.mts @@ -116,4 +116,27 @@ describe('records → assemble → sidecar', () => { 'g:lib:jar:1', ]) }) + it('marks only components with the exact coordinate of a build module as firstParty', () => { + const records = [ + 'meta\tmaven\t3.9.6\t17', + 'project\t:a\tg\ta\t1.0-SNAPSHOT\ta', + 'project\t:b\tg\tb\t1.0-SNAPSHOT\tb', + 'root\tr1\t:a\truntimeClasspath\t1', + 'node\tr1\tg:ext:jar:2\tg\text\t2\tjar\t\t1', + 'root\tr2\t:b\truntimeClasspath\t1', + 'node\tr2\tg:a:jar:1.0-SNAPSHOT\tg\ta\t1.0-SNAPSHOT\tjar\t\t1', + 'node\tr2\tg:ext:jar:2\tg\text\t2\tjar\t\t0', + 'edge\tr2\tg:a:jar:1.0-SNAPSHOT\tg:ext:jar:2', + 'node\tr2\tg:b:jar:0.9\tg\tb\t0.9\tjar\t\t1', + ].join('\n') + const { facts } = assembleFacts(parseRecords(records)) + + expect(facts.components.map(c => [c.id, c.firstParty ?? 'absent'])).toEqual( + [ + ['g:a:jar:1.0-SNAPSHOT', true], + ['g:b:jar:0.9', 'absent'], + ['g:ext:jar:2', 'absent'], + ], + ) + }) }) diff --git a/src/commands/manifest/scripts/facts.mts b/src/commands/manifest/scripts/facts.mts index 1ff25965f..c4150e7c4 100644 --- a/src/commands/manifest/scripts/facts.mts +++ b/src/commands/manifest/scripts/facts.mts @@ -27,6 +27,8 @@ export type SocketFactsSbomComponent = AnyPURL & { id: string direct?: boolean | undefined dev?: boolean | undefined + // A module of the scanned build itself (same GAV as a projects[] entry). + firstParty?: boolean | undefined dependencies?: string[] | undefined } From df660ec41b8442ddde629635670d43c877021b60 Mon Sep 17 00:00:00 2001 From: Jeppe Fredsgaard Blaabjerg Date: Tue, 29 Sep 2026 10:21:19 +0200 Subject: [PATCH 2/3] test(manifest): pin firstParty to sidecar components, never projects Co-Authored-By: Claude Opus 5.5 --- src/commands/manifest/scripts/assemble.test.mts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/commands/manifest/scripts/assemble.test.mts b/src/commands/manifest/scripts/assemble.test.mts index 0f955130e..52a01a073 100644 --- a/src/commands/manifest/scripts/assemble.test.mts +++ b/src/commands/manifest/scripts/assemble.test.mts @@ -129,7 +129,7 @@ describe('records → assemble → sidecar', () => { 'edge\tr2\tg:a:jar:1.0-SNAPSHOT\tg:ext:jar:2', 'node\tr2\tg:b:jar:0.9\tg\tb\t0.9\tjar\t\t1', ].join('\n') - const { facts } = assembleFacts(parseRecords(records)) + const { artifactPaths, facts } = assembleFacts(parseRecords(records)) expect(facts.components.map(c => [c.id, c.firstParty ?? 'absent'])).toEqual( [ @@ -138,5 +138,15 @@ describe('records → assemble → sidecar', () => { ['g:ext:jar:2', 'absent'], ], ) + + const acc: SidecarAccumulator = new Map() + accumulateSidecar(acc, facts, artifactPaths, '/abs/.socket.facts.json') + const bucket = serializeSidecar(acc)['/abs/.socket.facts.json']! + expect( + bucket.components.find(c => c.id === 'g:a:jar:1.0-SNAPSHOT')?.firstParty, + ).toBe(true) + for (const project of bucket.projects) { + expect(project).not.toHaveProperty('firstParty') + } }) }) From d1ab31a30b31c66b5a5ab90e676ee5122a79edea Mon Sep 17 00:00:00 2001 From: Jeppe Fredsgaard Blaabjerg Date: Tue, 29 Sep 2026 12:58:28 +0200 Subject: [PATCH 3/3] refactor(manifest): type firstParty as true-only Co-Authored-By: Claude Opus 5.5 --- src/commands/manifest/scripts/facts.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/manifest/scripts/facts.mts b/src/commands/manifest/scripts/facts.mts index c4150e7c4..aab42c0d2 100644 --- a/src/commands/manifest/scripts/facts.mts +++ b/src/commands/manifest/scripts/facts.mts @@ -28,7 +28,7 @@ export type SocketFactsSbomComponent = AnyPURL & { direct?: boolean | undefined dev?: boolean | undefined // A module of the scanned build itself (same GAV as a projects[] entry). - firstParty?: boolean | undefined + firstParty?: true | undefined dependencies?: string[] | undefined }