Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions backend/src/api/public/v1/akrites-external/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,9 @@ components:
- vulnerabilityReportingUrl
- bugBountyUrl
- pvrEnabled
- declaredRepositoryUrl
- resolvedRepositoryUrl
- repoMappingConfidence
- targetOrganizationName
- bugBountyProgramFlag
- reportingMethods
Expand Down Expand Up @@ -716,6 +719,28 @@ components:
pvrEnabled:
type: boolean
nullable: true
declaredRepositoryUrl:
type: string
nullable: true
description: Raw repository URL as declared in the package's own metadata.
resolvedRepositoryUrl:
type: string
nullable: true
description: >
Best available repository URL, resolved via the confidence-ranked
package_repos → repos join — the repo CDP has actually resolved and
enriched (scorecard, branch protection, etc.). Null when the
package has no package_repos link yet.
repoMappingConfidence:
type: number
format: float
minimum: 0
maximum: 1
nullable: true
description: >
Confidence score of the package_repos mapping used to resolve
resolvedRepositoryUrl. Null when the package has no package_repos
link yet.
targetOrganizationName:
type: string
nullable: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ function baseRow(
vulnerabilityReportingUrl: null,
bugBountyUrl: null,
pvrEnabled: true,
declaredRepositoryUrl: 'https://github.com/lodash/lodash.git',
resolvedRepositoryUrl: 'https://github.com/lodash/lodash',
repoMappingConfidence: 0.9,
securityContacts: [
{
channel: 'email',
Expand Down Expand Up @@ -107,4 +110,31 @@ describe('toAkritesExternalContactDetail', () => {
expect(result.vulnerabilityReportingUrl).toBe('https://example.org/report')
expect(result.pvrEnabled).toBe(false)
})

it('passes through the repo provenance fields when present', () => {
const result = toAkritesExternalContactDetail(baseRow())
expect(result.declaredRepositoryUrl).toBe('https://github.com/lodash/lodash.git')
expect(result.resolvedRepositoryUrl).toBe('https://github.com/lodash/lodash')
expect(result.repoMappingConfidence).toBe(0.9)
})

it('casts repoMappingConfidence from a numeric string (pg-promise numeric type)', () => {
const result = toAkritesExternalContactDetail(
baseRow({ repoMappingConfidence: '0.9' as unknown as number }),
)
expect(result.repoMappingConfidence).toBe(0.9)
})

it('returns resolvedRepositoryUrl and repoMappingConfidence as null when there is no repo link', () => {
const result = toAkritesExternalContactDetail(
baseRow({ resolvedRepositoryUrl: null, repoMappingConfidence: null }),
)
expect(result.resolvedRepositoryUrl).toBeNull()
expect(result.repoMappingConfidence).toBeNull()
})

it('returns declaredRepositoryUrl as null when the package has no declared repository', () => {
const result = toAkritesExternalContactDetail(baseRow({ declaredRepositoryUrl: null }))
expect(result.declaredRepositoryUrl).toBeNull()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
securityContactConfidenceBand,
} from '@crowd/data-access-layer'

import { toNullableNumber } from './mappers'

// Both the per-contact band and the aggregate band mirror the internal
// SecurityContactConfidence enum verbatim (PRIMARY/SECONDARY/FALLBACK/NONE) —
// no external crosswalk (confirmed with product).
Expand All @@ -28,6 +30,9 @@ export interface AkritesExternalContactDetail {
vulnerabilityReportingUrl: string | null
bugBountyUrl: string | null
pvrEnabled: boolean | null
declaredRepositoryUrl: string | null
resolvedRepositoryUrl: string | null
repoMappingConfidence: number | null
// Reserved by the contract but not stored today — always null (see the akrites-external
// OpenAPI notes). Typed as null so a future non-null shape is an explicit, reviewed change.
targetOrganizationName: null
Expand Down Expand Up @@ -71,6 +76,9 @@ export function toAkritesExternalContactDetail(
vulnerabilityReportingUrl: row.vulnerabilityReportingUrl ?? null,
bugBountyUrl: row.bugBountyUrl ?? null,
pvrEnabled: row.pvrEnabled ?? null,
declaredRepositoryUrl: row.declaredRepositoryUrl ?? null,
resolvedRepositoryUrl: row.resolvedRepositoryUrl ?? null,
repoMappingConfidence: toNullableNumber(row.repoMappingConfidence),
targetOrganizationName: null,
bugBountyProgramFlag: null,
reportingMethods: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
computeHealthBand,
} from '@crowd/data-access-layer'

import { repoMappingLabel, snakeToCamelKeys } from './mappers'
import { repoMappingLabel, snakeToCamelKeys, toNullableNumber } from './mappers'
import { HEALTH_BAND_SET, LIFECYCLE_VALUES, type Lifecycle } from './types'

const LIFECYCLE_SET = new Set<string>(LIFECYCLE_VALUES)
Expand Down Expand Up @@ -102,8 +102,7 @@ export function toAkritesExternalPackageDetail(
row: AkritesExternalPackageDetailRow,
): AkritesExternalPackageDetail {
const scorecardScore = row.scorecardScore != null ? Number(row.scorecardScore) : null
const mappingConfidence =
row.repoMappingConfidence != null ? Number(row.repoMappingConfidence) : null
const mappingConfidence = toNullableNumber(row.repoMappingConfidence)

return {
purl: row.purl,
Expand Down
5 changes: 2 additions & 3 deletions backend/src/api/public/v1/packages/getPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { getPackagesQx } from '@/db/packagesDb'
import { ok } from '@/utils/api'
import { validateOrThrow } from '@/utils/validation'

import { repoMappingLabel, snakeToCamelKeys } from './mappers'
import { repoMappingLabel, snakeToCamelKeys, toNullableNumber } from './mappers'
import { purlQuerySchema } from './purl'
import { HEALTH_BAND_SET, LIFECYCLE_VALUES, type StewardshipStatus } from './types'

Expand All @@ -35,8 +35,7 @@ export async function getPackage(req: Request, res: Response): Promise<void> {
])

const scorecardScore = pkg.scorecardScore != null ? Number(pkg.scorecardScore) : null
const mappingConfidence =
pkg.repoMappingConfidence != null ? Number(pkg.repoMappingConfidence) : null
const mappingConfidence = toNullableNumber(pkg.repoMappingConfidence)

const securityContacts =
pkg.contactsLastRefreshed == null
Expand Down
5 changes: 5 additions & 0 deletions backend/src/api/public/v1/packages/mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ export function snakeToCamelKeys(
)
}

// pg-promise returns numeric columns as strings; this coerces without turning null into 0.
Comment thread
ulemons marked this conversation as resolved.
export function toNullableNumber(value: number | string | null): number | null {
return value != null ? Number(value) : null
}

export function repoMappingLabel(confidence: number | null): 'High' | 'Medium' | 'Low' | null {
if (confidence === null) return null
if (confidence >= 0.8) return 'High'
Expand Down
9 changes: 8 additions & 1 deletion services/libs/data-access-layer/src/osspckgs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -875,12 +875,16 @@ export async function getPackageDetailsByPurls(
// live on the repo, not the package). A missing purl yields no row → "not found"; a found
// package with no contacts yields a row with securityContacts null → resolves to [].
export interface AkritesExternalContactDetailRow
extends Pick<PackageDbRow, 'purl' | 'name' | 'ecosystem'>,
extends Pick<PackageDbRow, 'purl' | 'name' | 'ecosystem' | 'declaredRepositoryUrl'>,
Pick<
RepoDbRow,
'securityPolicyUrl' | 'vulnerabilityReportingUrl' | 'bugBountyUrl' | 'pvrEnabled'
> {
securityContacts: SecurityContactRow[] | null
// --- joined from repos via package_repos, same BEST_REPO_LINK_JOIN as
// AkritesExternalPackageDetailRow — not part of the packages or repos row.
resolvedRepositoryUrl: string | null
repoMappingConfidence: number | null
}

export async function getContactDetailsByPurls(
Expand All @@ -894,6 +898,9 @@ export async function getContactDetailsByPurls(
p.purl,
p.name,
p.ecosystem,
p.declared_repository_url AS "declaredRepositoryUrl",
r.url AS "resolvedRepositoryUrl",
pr.confidence AS "repoMappingConfidence",
Comment on lines +901 to +903
r.security_policy_url AS "securityPolicyUrl",
r.vulnerability_reporting_url AS "vulnerabilityReportingUrl",
r.bug_bounty_url AS "bugBountyUrl",
Expand Down
Loading