Skip to content

Commit 6c64cc2

Browse files
jfblaaclaude
andauthored
feat(manifest): mark build modules as firstParty in facts components (#1569)
* 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 <noreply@anthropic.com> * test(manifest): pin firstParty to sidecar components, never projects Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(manifest): type firstParty as true-only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3840719 commit 6c64cc2

3 files changed

Lines changed: 46 additions & 11 deletions

File tree

‎src/commands/manifest/scripts/assemble.mts‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ export function assembleFacts(
5454
const { directByRoot, finalNodes } = mergeByCoordinate(perRoot)
5555

5656
const tool = (parsed.tool || 'gradle') as SocketFactsSbomMetadata['tool']
57-
const components = buildComponents(finalNodes)
57+
const projectsByGav = new Map<string, RawProject>()
58+
for (const p of parsed.projects.values()) {
59+
projectsByGav.set(gav(p.group, p.name, p.version), p)
60+
}
61+
const components = buildComponents(finalNodes, projectsByGav)
5862
const projects =
5963
opts.emitProjects === false
6064
? []
@@ -77,6 +81,7 @@ export function assembleFacts(
7781
artifactPaths: buildArtifactPaths(
7882
finalNodes,
7983
[...parsed.projects.values()],
84+
projectsByGav,
8085
perRoot,
8186
fileExists,
8287
),
@@ -173,6 +178,7 @@ function mergeByCoordinate(perRoot: Map<string, PerRoot>): {
173178

174179
function buildComponents(
175180
finalNodes: Map<string, MergedNode>,
181+
projectsByGav: Map<string, RawProject>,
176182
): SocketFactsSbomComponent[] {
177183
return [...finalNodes.keys()].sort().map(id => {
178184
const fn = finalNodes.get(id)!
@@ -200,6 +206,9 @@ function buildComponents(
200206
if (!fn.prod) {
201207
comp.dev = true
202208
}
209+
if (projectsByGav.has(gav(c.group, c.name, c.version ?? ''))) {
210+
comp.firstParty = true
211+
}
203212
if (fn.children.size) {
204213
comp.dependencies = [...fn.children].sort()
205214
}
@@ -303,19 +312,10 @@ function buildClasspathByProject(
303312
function buildArtifactPaths(
304313
finalNodes: Map<string, MergedNode>,
305314
projects: RawProject[],
315+
projectsByGav: Map<string, RawProject>,
306316
perRoot: Map<string, PerRoot>,
307317
fileExists: (path: string) => boolean,
308318
): ResolvedArtifactPaths {
309-
const projectsByGav = new Map<
310-
string,
311-
{ sources: string[]; targets: string[] }
312-
>()
313-
for (const p of projects) {
314-
projectsByGav.set(gav(p.group, p.name, p.version), {
315-
sources: p.sources,
316-
targets: p.targets,
317-
})
318-
}
319319
const targetsByCoord = new Map<string, string[]>()
320320
const targetsByGav = new Map<string, string[]>()
321321
const sourcesByCoord = new Map<string, string[]>()

‎src/commands/manifest/scripts/assemble.test.mts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,37 @@ describe('records → assemble → sidecar', () => {
116116
'g:lib:jar:1',
117117
])
118118
})
119+
it('marks only components with the exact coordinate of a build module as firstParty', () => {
120+
const records = [
121+
'meta\tmaven\t3.9.6\t17',
122+
'project\t:a\tg\ta\t1.0-SNAPSHOT\ta',
123+
'project\t:b\tg\tb\t1.0-SNAPSHOT\tb',
124+
'root\tr1\t:a\truntimeClasspath\t1',
125+
'node\tr1\tg:ext:jar:2\tg\text\t2\tjar\t\t1',
126+
'root\tr2\t:b\truntimeClasspath\t1',
127+
'node\tr2\tg:a:jar:1.0-SNAPSHOT\tg\ta\t1.0-SNAPSHOT\tjar\t\t1',
128+
'node\tr2\tg:ext:jar:2\tg\text\t2\tjar\t\t0',
129+
'edge\tr2\tg:a:jar:1.0-SNAPSHOT\tg:ext:jar:2',
130+
'node\tr2\tg:b:jar:0.9\tg\tb\t0.9\tjar\t\t1',
131+
].join('\n')
132+
const { artifactPaths, facts } = assembleFacts(parseRecords(records))
133+
134+
expect(facts.components.map(c => [c.id, c.firstParty ?? 'absent'])).toEqual(
135+
[
136+
['g:a:jar:1.0-SNAPSHOT', true],
137+
['g:b:jar:0.9', 'absent'],
138+
['g:ext:jar:2', 'absent'],
139+
],
140+
)
141+
142+
const acc: SidecarAccumulator = new Map()
143+
accumulateSidecar(acc, facts, artifactPaths, '/abs/.socket.facts.json')
144+
const bucket = serializeSidecar(acc)['/abs/.socket.facts.json']!
145+
expect(
146+
bucket.components.find(c => c.id === 'g:a:jar:1.0-SNAPSHOT')?.firstParty,
147+
).toBe(true)
148+
for (const project of bucket.projects) {
149+
expect(project).not.toHaveProperty('firstParty')
150+
}
151+
})
119152
})

‎src/commands/manifest/scripts/facts.mts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export type SocketFactsSbomComponent = AnyPURL & {
2727
id: string
2828
direct?: boolean | undefined
2929
dev?: boolean | undefined
30+
// A module of the scanned build itself (same GAV as a projects[] entry).
31+
firstParty?: true | undefined
3032
dependencies?: string[] | undefined
3133
}
3234

0 commit comments

Comments
 (0)