Skip to content

Commit b2c18e1

Browse files
committed
Merge branch 'main' into feat/collections-v2-aggregate-pipes-IN-1193-IN-1195
2 parents bd5c2b1 + d320aa8 commit b2c18e1

102 files changed

Lines changed: 3833 additions & 750 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/.env.dist.local

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ OSSPCKGS_GCP_CREDENTIALS_B64=e30=
199199
# maven/all.zip 404s). The allowlist check and DB storage normalize to lowercase
200200
# internally per ADR-0001 §OSV "Ecosystem normalization", so downstream stays lowercase.
201201
OSV_BULK_BASE_URL=https://osv-vulnerabilities.storage.googleapis.com
202-
OSV_ECOSYSTEMS=npm,Maven,cargo,NuGet
202+
OSV_ECOSYSTEMS=npm,Maven,cargo,NuGet,RubyGems,Go
203203
OSV_TMP_DIR=/tmp/osv
204204
OSV_BATCH_SIZE=500
205205
OSV_DERIVE_BATCH_SIZE=1000

backend/src/api/public/v1/members/createMember.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { z } from 'zod'
33

44
import { captureApiChange, memberCreateAction, memberEditIdentitiesAction } from '@crowd/audit-logs'
55
import { getProperDisplayName } from '@crowd/common'
6-
import { insertManyMemberIdentities, createMember as insertMember } from '@crowd/data-access-layer'
6+
import { createMember as insertMember, insertMemberIdentities } from '@crowd/data-access-layer'
77
import { MemberIdentityType } from '@crowd/types'
88

99
import { optionsQx } from '@/database/sequelizeQueryExecutor'
@@ -49,7 +49,7 @@ export async function createMember(req: Request, res: Response): Promise<void> {
4949
manuallyCreated: true,
5050
})
5151

52-
const dbIdentities = await insertManyMemberIdentities(
52+
const dbIdentities = await insertMemberIdentities(
5353
tx,
5454
identities.map((identity) => ({
5555
...identity,

backend/src/api/public/v1/members/identities/createMemberIdentity.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
MemberField,
88
findMemberById,
99
findMemberIdentitiesByValue,
10-
createMemberIdentity as insertMemberIdentity,
10+
insertMemberIdentities,
1111
touchMemberUpdatedAt,
1212
updateMemberIdentity,
1313
} from '@crowd/data-access-layer'
@@ -69,20 +69,23 @@ export async function createMemberIdentity(req: Request, res: Response): Promise
6969
alreadyExisted = true
7070
result = exactMatch
7171
} else {
72-
result = await insertMemberIdentity(
72+
const [created] = await insertMemberIdentities(
7373
tx,
74-
{
75-
memberId,
76-
platform: data.platform,
77-
value: normalizedValue,
78-
type: data.type,
79-
source: data.source,
80-
verified: data.verified,
81-
verifiedBy: data.verifiedBy,
82-
},
74+
[
75+
{
76+
memberId,
77+
platform: data.platform,
78+
value: normalizedValue,
79+
type: data.type,
80+
source: data.source,
81+
verified: data.verified,
82+
verifiedBy: data.verifiedBy,
83+
},
84+
],
8385
true,
8486
true,
8587
)
88+
result = created
8689
}
8790

8891
// A verified identity confirms the same value for this member, so keep same-value

backend/src/api/public/v1/members/project-affiliations/patchProjectAffiliation.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,16 @@ export async function patchProjectAffiliation(req: Request, res: Response): Prom
8585
if (affiliations.length > 0) {
8686
await insertMemberSegmentAffiliations(
8787
tx,
88-
memberId,
89-
projectId,
9088
affiliations.map((a) => ({
89+
memberId,
90+
segmentId: projectId,
9191
organizationId: a.organizationId,
9292
dateStart: a.dateStart.toISOString(),
9393
dateEnd: a.dateEnd?.toISOString() ?? null,
94+
verified: true,
9495
verifiedBy: verifiedBy!,
9596
})),
97+
true,
9698
)
9799
}
98100
})

backend/src/api/public/v1/packages/getPackage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export async function getPackage(req: Request, res: Response): Promise<void> {
130130
},
131131
provenance: {
132132
repositoryMapping: {
133-
declaredRepo: pkg.repoUrl ?? pkg.repositoryUrl ?? pkg.declaredRepositoryUrl ?? null,
133+
declaredRepo: pkg.repoUrl ?? pkg.repositoryUrl ?? null,
134134
mappingConfidence,
135135
mappingLabel: repoMappingLabel(mappingConfidence),
136136
lastCommitAt: pkg.repoLastCommitAt ? pkg.repoLastCommitAt.toISOString() : null,

backend/src/database/repositories/member/memberAffiliationsRepository.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { OrganizationField, queryOrgs } from '@crowd/data-access-layer'
22
import {
33
deleteMemberSegmentAffiliations,
44
fetchMemberAffiliations,
5-
insertMemberAffiliations,
5+
insertMemberSegmentAffiliations,
66
} from '@crowd/data-access-layer/src/member_segment_affiliations'
77
import { fetchManySegments } from '@crowd/data-access-layer/src/segments'
88
import { IMemberAffiliation, IOrganization, SegmentData } from '@crowd/types'
@@ -89,8 +89,17 @@ class MemberAffiliationsRepository {
8989
await deleteMemberSegmentAffiliations(qx, { memberId })
9090

9191
if (data?.length > 0) {
92-
// Insert multiple member affiliations
93-
await insertMemberAffiliations(qx, memberId, data)
92+
await insertMemberSegmentAffiliations(
93+
qx,
94+
data.map((item) => ({
95+
memberId,
96+
segmentId: item.segmentId,
97+
organizationId: item.organizationId,
98+
dateStart: item.dateStart || null,
99+
dateEnd: item.dateEnd || null,
100+
})),
101+
true,
102+
)
94103
}
95104

96105
await SequelizeRepository.commitTransaction(transaction)

backend/src/database/repositories/memberRepository.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import {
1818
import { BotDetectionService, CommonMemberService } from '@crowd/common_services'
1919
import {
2020
OrganizationField,
21-
createMemberIdentity,
2221
deleteMemberIdentities,
2322
deleteMemberIdentitiesByCombinations,
2423
findAlreadyExistingVerifiedIdentities,
2524
getLastActivitiesForMembers,
25+
insertMemberIdentities,
2626
queryActivityRelations,
2727
queryOrgs,
2828
updateVerifiedFlag,
@@ -33,7 +33,7 @@ import { addMemberNoMerge, removeMemberToMerge } from '@crowd/data-access-layer/
3333
import {
3434
deleteMemberSegmentAffiliations,
3535
findMemberAffiliations,
36-
insertMemberAffiliations,
36+
insertMemberSegmentAffiliations,
3737
} from '@crowd/data-access-layer/src/member_segment_affiliations'
3838
import {
3939
MemberField,
@@ -159,8 +159,9 @@ class MemberRepository {
159159
const subprojectIds = await getSegmentSubprojectIds(qx, currentSegments)
160160

161161
if (data.identities) {
162-
for (const i of data.identities as IMemberIdentity[]) {
163-
await createMemberIdentity(qx, {
162+
await insertMemberIdentities(
163+
qx,
164+
(data.identities as IMemberIdentity[]).map((i) => ({
164165
memberId: record.id,
165166
platform: i.platform,
166167
type: i.type,
@@ -169,15 +170,16 @@ class MemberRepository {
169170
integrationId: i.integrationId || null,
170171
verified: i.verified,
171172
source: i.source,
172-
})
173-
}
173+
})),
174+
)
174175
} else if (data.username) {
175176
const username: PlatformIdentities = mapUsernameToIdentities(data.username)
176177

178+
const identitiesToInsert = []
177179
for (const platform of Object.keys(username) as PlatformType[]) {
178180
const identities: any[] = username[platform]
179181
for (const identity of identities) {
180-
await createMemberIdentity(qx, {
182+
identitiesToInsert.push({
181183
memberId: record.id,
182184
platform,
183185
value: identity.value ? identity.value : identity.username,
@@ -189,6 +191,10 @@ class MemberRepository {
189191
})
190192
}
191193
}
194+
195+
if (identitiesToInsert.length > 0) {
196+
await insertMemberIdentities(qx, identitiesToInsert)
197+
}
192198
}
193199

194200
await includeMemberToSegments(qx, record.id, subprojectIds)
@@ -1049,8 +1055,9 @@ class MemberRepository {
10491055
}
10501056

10511057
if (data.identitiesToCreate && data.identitiesToCreate.length > 0) {
1052-
for (const i of data.identitiesToCreate) {
1053-
await createMemberIdentity(qx, {
1058+
await insertMemberIdentities(
1059+
qx,
1060+
data.identitiesToCreate.map((i) => ({
10541061
memberId: record.id,
10551062
platform: i.platform,
10561063
value: i.value,
@@ -1059,8 +1066,8 @@ class MemberRepository {
10591066
integrationId: i.integrationId || null,
10601067
verified: i.verified !== undefined ? i.verified : !!manualChange,
10611068
source: i.source,
1062-
})
1063-
}
1069+
})),
1070+
)
10641071
}
10651072

10661073
if (data.identitiesToUpdate && data.identitiesToUpdate.length > 0) {
@@ -1094,6 +1101,7 @@ class MemberRepository {
10941101
const platformsToDelete: string[] = []
10951102
const valuesToDelete: string[] = []
10961103
const typesToDelete: MemberIdentityType[] = []
1104+
const identitiesToInsert = []
10971105

10981106
for (const platform of platforms) {
10991107
const identities = data.username[platform]
@@ -1112,7 +1120,7 @@ class MemberRepository {
11121120
(identity.username && identity.username !== '') ||
11131121
(identity.value && identity.value !== '')
11141122
) {
1115-
await createMemberIdentity(qx, {
1123+
identitiesToInsert.push({
11161124
memberId: record.id,
11171125
platform,
11181126
value: identity.value ? identity.value : identity.username,
@@ -1126,6 +1134,10 @@ class MemberRepository {
11261134
}
11271135
}
11281136

1137+
if (identitiesToInsert.length > 0) {
1138+
await insertMemberIdentities(qx, identitiesToInsert)
1139+
}
1140+
11291141
if (platformsToDelete.length > 0) {
11301142
await deleteMemberIdentitiesByCombinations(qx, {
11311143
memberId: record.id,
@@ -1207,7 +1219,17 @@ class MemberRepository {
12071219
return
12081220
}
12091221

1210-
await insertMemberAffiliations(qx, memberId, data)
1222+
await insertMemberSegmentAffiliations(
1223+
qx,
1224+
data.map((item) => ({
1225+
memberId,
1226+
segmentId: item.segmentId,
1227+
organizationId: item.organizationId,
1228+
dateStart: item.dateStart || null,
1229+
dateEnd: item.dateEnd || null,
1230+
})),
1231+
true,
1232+
)
12111233
}),
12121234
)
12131235
}

backend/src/database/repositories/organizationRepository.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
IDbOrganization,
1717
OrgIdentityField,
1818
OrganizationField,
19-
addOrgIdentity,
2019
addOrgsToSegments,
2120
cleanUpOrgIdentities,
2221
cleanupForOganization,
@@ -27,6 +26,7 @@ import {
2726
findManyOrgAttributes,
2827
findOrgAttributes,
2928
findOrgById,
29+
insertOrganizationIdentities,
3030
markOrgAttributeDefault,
3131
queryOrgIdentities,
3232
updateOrgIdentityVerifiedFlag,
@@ -619,16 +619,22 @@ class OrganizationRepository {
619619
): Promise<void> {
620620
const qx = SequelizeRepository.getQueryExecutor(options)
621621

622-
await addOrgIdentity(qx, {
623-
organizationId,
624-
platform: identity.platform,
625-
source: identity.source,
626-
sourceId: identity.sourceId || null,
627-
value: identity.value,
628-
type: identity.type,
629-
verified: identity.verified,
630-
integrationId: identity.integrationId || null,
631-
})
622+
await insertOrganizationIdentities(
623+
qx,
624+
[
625+
{
626+
organizationId,
627+
platform: identity.platform,
628+
source: identity.source,
629+
sourceId: identity.sourceId || null,
630+
value: identity.value,
631+
type: identity.type,
632+
verified: identity.verified,
633+
integrationId: identity.integrationId || null,
634+
},
635+
],
636+
false,
637+
)
632638
}
633639

634640
static async getIdentities(

backend/src/services/member/memberIdentityService.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import lodash from 'lodash'
33

44
import { captureApiChange, memberEditIdentitiesAction } from '@crowd/audit-logs'
55
import { Error404, Error409 } from '@crowd/common'
6-
import { createMemberIdentity, findIdentitiesForMembers } from '@crowd/data-access-layer'
6+
import { findIdentitiesForMembers, insertMemberIdentities } from '@crowd/data-access-layer'
77
import {
88
deleteMemberIdentity,
99
fetchMemberIdentities,
@@ -77,7 +77,7 @@ export default class MemberIdentityService extends LoggerBase {
7777
}
7878

7979
// Create member identity
80-
await createMemberIdentity(qx, { ...data, memberId })
80+
await insertMemberIdentities(qx, [{ ...data, memberId }])
8181

8282
await touchMemberUpdatedAt(qx, memberId)
8383

@@ -151,9 +151,10 @@ export default class MemberIdentityService extends LoggerBase {
151151
}
152152

153153
// Create member identities
154-
for (const identity of data) {
155-
await createMemberIdentity(qx, { ...identity, memberId })
156-
}
154+
await insertMemberIdentities(
155+
qx,
156+
data.map((identity) => ({ ...identity, memberId })),
157+
)
157158

158159
await touchMemberUpdatedAt(qx, memberId)
159160

docs/adr/0004-go-nuget-transitive-dependent-counts.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,14 @@ methodology. We chose exact over an HLL-sketch approximation, accepting the high
9797
- **Weekly trigger.** No new schedule — the `go`/`nuget` variants are already child workflows of
9898
`bootstrapOsspckgs`, which the existing `osspckgs-bootstrap-weekly` schedule runs incrementally.
9999

100+
## Addendum: RUBYGEMS (2026-07-13)
101+
RubyGems is also absent from deps.dev's `Dependents` reverse index (verified via BQ 2026-07-13:
102+
`Dependents` contains only NPM/MAVEN/PYPI/CARGO) and has no resolved/transitive graph table, only
103+
the raw manifest `RubyGemsRequirementsLatest` — same shape as GO/NUGET. It was added as a third
104+
`isScript` closure variant (`dependent_counts_rubygems`), reusing this ADR's design unchanged:
105+
same semi-naive fixpoint, same `isScript` pipeline integration, same guard/monitor pattern. Corpus
106+
is NUGET-scale (1.7M pkgs / 4.5M runtime edges), so cost/runtime are expected to track NUGET, not
107+
GO. This ADR's title and body still say "GO/NUGET" for the original decision; treat this addendum
108+
as extending scope to a third ecosystem rather than superseding the analysis above.
109+
100110
Related: ADR-0003 (deps BQ table selection — same root cause: GO/NUGET absent from the resolved-graph tables).

0 commit comments

Comments
 (0)