From b319c4e985aa8492be9c9c24f852c0392fcff849 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Wed, 24 Jun 2026 10:23:50 +0200 Subject: [PATCH 1/2] feat: add enriched fields Signed-off-by: Umberto Sgueglia --- backend/src/api/public/v1/packages/getPackage.ts | 14 ++++++++++++-- backend/src/api/public/v1/packages/openapi.yaml | 11 +++++++++++ .../libs/data-access-layer/src/osspckgs/api.ts | 15 +++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/backend/src/api/public/v1/packages/getPackage.ts b/backend/src/api/public/v1/packages/getPackage.ts index 8c4414e170..0841500bab 100644 --- a/backend/src/api/public/v1/packages/getPackage.ts +++ b/backend/src/api/public/v1/packages/getPackage.ts @@ -40,6 +40,8 @@ export async function getPackage(req: Request, res: Response): Promise { const scorecardScore = pkg.scorecardScore != null ? Number(pkg.scorecardScore) : null const mappingConfidence = pkg.repoMappingConfidence != null ? Number(pkg.repoMappingConfidence) : null + const healthScoreTotal = + pkg.healthScore ?? (scorecardScore !== null ? Math.round(scorecardScore * 10) : null) ok(res, { purl: pkg.purl, @@ -47,7 +49,14 @@ export async function getPackage(req: Request, res: Response): Promise { ecosystem: pkg.ecosystem, latestVersion: pkg.latestVersion ?? null, general: { - healthScore: scorecardScore !== null ? Math.round(scorecardScore * 10) : null, + healthScore: healthScoreTotal, + healthScoreDetails: { + total: healthScoreTotal, + label: pkg.healthLabel, + maintainerHealth: pkg.maintainerHealthScore, + securitySupplyChain: pkg.securitySupplyChainScore, + developmentActivity: pkg.developmentActivityScore, + }, healthBand: computeHealthBand(scorecardScore), impact: { impactScore: @@ -58,7 +67,7 @@ export async function getPackage(req: Request, res: Response): Promise { transitiveReach: pkg.transitiveReach, }, riskSignals: { - lifecycle: null, + lifecycle: pkg.lifecycleLabel ?? null, maintainerBusFactor: pkg.maintainerCount, lastRelease: pkg.latestReleaseAt ? pkg.latestReleaseAt.toISOString() : null, hasSecurityFile: pkg.hasSecurityFile, @@ -67,6 +76,7 @@ export async function getPackage(req: Request, res: Response): Promise { openSSFScorecard: scorecardScore, }, }, + signalCoverageHealth: pkg.signalCoverageHealth ?? null, assessment: null, security: { securityContacts: null, diff --git a/backend/src/api/public/v1/packages/openapi.yaml b/backend/src/api/public/v1/packages/openapi.yaml index b06a0cd291..09466129e8 100644 --- a/backend/src/api/public/v1/packages/openapi.yaml +++ b/backend/src/api/public/v1/packages/openapi.yaml @@ -291,6 +291,11 @@ components: - integer - 'null' example: 18 + label: + type: + - string + - 'null' + example: Good impact: type: - object @@ -466,6 +471,12 @@ components: history: type: object description: Package history data. Empty in v1. + signalCoverageHealth: + type: + - object + - 'null' + description: Signal coverage health data enriched by Tinybird. Null until enriched. + additionalProperties: true # ── Metrics ────────────────────────────────────────────────────────────────── diff --git a/services/libs/data-access-layer/src/osspckgs/api.ts b/services/libs/data-access-layer/src/osspckgs/api.ts index a1d3fa7a60..0d1e3fdd6c 100644 --- a/services/libs/data-access-layer/src/osspckgs/api.ts +++ b/services/libs/data-access-layer/src/osspckgs/api.ts @@ -618,6 +618,14 @@ export interface PackageDetailRow { downloadsLast30d: string | null maintainerCount: number transitiveReach: number | null + // Tinybird-enriched health fields + healthScore: number | null + healthLabel: string | null + maintainerHealthScore: number | null + securitySupplyChainScore: number | null + developmentActivityScore: number | null + lifecycleLabel: string | null + signalCoverageHealth: Record | null } export interface AdvisoryRow { @@ -672,6 +680,13 @@ export async function getPackageDetailByPurl( LIMIT 1 ) AS "downloadsLast30d", (SELECT COUNT(*)::int FROM package_maintainers pm WHERE pm.package_id = p.id) AS "maintainerCount", + p.health_score AS "healthScore", + p.health_label AS "healthLabel", + p.maintainer_health_score AS "maintainerHealthScore", + p.security_supply_chain_score AS "securitySupplyChainScore", + p.development_activity_score AS "developmentActivityScore", + p.lifecycle_label AS "lifecycleLabel", + p.signal_coverage_health AS "signalCoverageHealth", -- TODO: precompute and store in packages.transitive_reach_prank; full window scan is too slow at npm scale (~24s for npm) -- ( -- SELECT r.prank From 11fa261c41be2f6a9d009da67b0d5aa0b6f08ec8 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Wed, 24 Jun 2026 10:42:59 +0200 Subject: [PATCH 2/2] fix: use data from tb Signed-off-by: Umberto Sgueglia --- .../src/api/public/v1/packages/getPackage.ts | 13 +++--- .../src/api/public/v1/packages/openapi.yaml | 41 +++++++++++++------ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/backend/src/api/public/v1/packages/getPackage.ts b/backend/src/api/public/v1/packages/getPackage.ts index 0841500bab..69806af604 100644 --- a/backend/src/api/public/v1/packages/getPackage.ts +++ b/backend/src/api/public/v1/packages/getPackage.ts @@ -40,8 +40,7 @@ export async function getPackage(req: Request, res: Response): Promise { const scorecardScore = pkg.scorecardScore != null ? Number(pkg.scorecardScore) : null const mappingConfidence = pkg.repoMappingConfidence != null ? Number(pkg.repoMappingConfidence) : null - const healthScoreTotal = - pkg.healthScore ?? (scorecardScore !== null ? Math.round(scorecardScore * 10) : null) + const healthBandScore = pkg.healthScore !== null ? pkg.healthScore / 10 : scorecardScore ok(res, { purl: pkg.purl, @@ -49,15 +48,15 @@ export async function getPackage(req: Request, res: Response): Promise { ecosystem: pkg.ecosystem, latestVersion: pkg.latestVersion ?? null, general: { - healthScore: healthScoreTotal, + healthScore: pkg.healthScore, healthScoreDetails: { - total: healthScoreTotal, + total: pkg.healthScore, label: pkg.healthLabel, maintainerHealth: pkg.maintainerHealthScore, securitySupplyChain: pkg.securitySupplyChainScore, developmentActivity: pkg.developmentActivityScore, }, - healthBand: computeHealthBand(scorecardScore), + healthBand: computeHealthBand(healthBandScore), impact: { impactScore: pkg.criticalityScore != null ? Math.round(Number(pkg.criticalityScore) * 100) : null, @@ -67,7 +66,7 @@ export async function getPackage(req: Request, res: Response): Promise { transitiveReach: pkg.transitiveReach, }, riskSignals: { - lifecycle: pkg.lifecycleLabel ?? null, + lifecycle: pkg.lifecycleLabel, maintainerBusFactor: pkg.maintainerCount, lastRelease: pkg.latestReleaseAt ? pkg.latestReleaseAt.toISOString() : null, hasSecurityFile: pkg.hasSecurityFile, @@ -76,7 +75,7 @@ export async function getPackage(req: Request, res: Response): Promise { openSSFScorecard: scorecardScore, }, }, - signalCoverageHealth: pkg.signalCoverageHealth ?? null, + signalCoverageHealth: pkg.signalCoverageHealth, assessment: null, security: { securityContacts: null, diff --git a/backend/src/api/public/v1/packages/openapi.yaml b/backend/src/api/public/v1/packages/openapi.yaml index 09466129e8..cd5776693f 100644 --- a/backend/src/api/public/v1/packages/openapi.yaml +++ b/backend/src/api/public/v1/packages/openapi.yaml @@ -267,10 +267,27 @@ components: type: object properties: healthScore: + type: + - integer + - 'null' + description: Composite health score (0–100). Tinybird-enriched when available, null otherwise. + example: 18 + healthScoreDetails: type: - object - 'null' + description: Breakdown of the composite health score. Null until Tinybird enrichment runs. properties: + total: + type: + - integer + - 'null' + example: 18 + label: + type: + - string + - 'null' + example: Good maintainerHealth: type: - integer @@ -286,16 +303,13 @@ components: - integer - 'null' example: 6 - total: - type: - - integer - - 'null' - example: 18 - label: - type: - - string - - 'null' - example: Good + healthBand: + type: + - string + - 'null' + enum: [healthy, fair, concerning, critical, null] + description: Derived from Tinybird health score when available, OpenSSF Scorecard otherwise. + example: concerning impact: type: - object @@ -683,11 +697,14 @@ paths: name: lodash ecosystem: npm general: - healthScore: + healthScore: 18 + healthScoreDetails: + total: 18 + label: critical maintainerHealth: 4 securitySupplyChain: 8 developmentActivity: 6 - total: 18 + healthBand: critical impact: impactScore: 71 downloadsLastMonth: 52142891