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
71 changes: 56 additions & 15 deletions backend/src/api/public/v1/ossprey/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ components:

HealthBand:
type: string
enum: [healthy, fair, concerning, critical]
enum: [excellent, healthy, fair, concerning, critical]
description: >
Server-derived from `scorecardScore` thresholds:
`null or < 3.0` → critical · `< 5.0` → concerning · `< 7.0` → fair · `≥ 7.0` → healthy
Tinybird band when enriched (excellent ≥85, healthy 70–84, fair 50–69,
concerning 30–49, critical <30). Falls back to scorecard thresholds:
`null or < 3.0` → critical · `< 5.0` → concerning · `< 7.0` → fair · `≥ 7.0` → healthy.

StewardshipStatus:
type: string
Expand Down Expand Up @@ -90,7 +91,7 @@ components:
- ecosystem
- openVulns
- maintainerCount
- healthBand
- health
- stewards
properties:
purl:
Expand All @@ -102,12 +103,6 @@ components:
ecosystem:
type: string
example: npm
criticalityScore:
type:
- number
- 'null'
description: packages.impact (0–1 float). Multiply × 100 for display score.
example: 0.94
stewardshipId:
type:
- string
Expand All @@ -131,14 +126,52 @@ components:
type: integer
description: Count of package_maintainers rows. Bus factor proxy.
example: 2
criticalityScore:
type:
- number
- 'null'
description: Raw criticality score (0–1 float). Use `impact` for display.
example: 0.94
impact:
type:
- integer
- 'null'
description: Display score (0–100). criticalityScore × 100, rounded. Null if no score.
example: 94
scorecardScore:
type:
- number
- 'null'
description: OpenSSF Scorecard score (0–10). Null if no repo mapped.
example: 5.2
healthBand:
$ref: '#/components/schemas/HealthBand'
health:
type: object
required: [score, label]
properties:
score:
type:
- integer
- 'null'
description: >
Health score (0–100). Tinybird composite score when enriched,
OpenSSF Scorecard × 10 otherwise. Null if neither is available.
example: 52
label:
type:
- string
- 'null'
enum: [excellent, healthy, fair, concerning, critical, null]
description: >
Tinybird band when enriched (excellent ≥85, healthy 70–84,
fair 50–69, concerning 30–49, critical <30), scorecard band otherwise.
example: fair
lifecycle:
type:
- string
- 'null'
enum: [active, stable, declining, abandoned, archived, null]
description: Tinybird-enriched lifecycle label. Null if not yet enriched.
example: active
latestReleaseAt:
type:
- string
Expand Down Expand Up @@ -230,7 +263,11 @@ components:
description: ROUND(scorecard_score × 10). X-axis position (0–100). 0 if no repo.
example: 52
healthBand:
$ref: '#/components/schemas/HealthBand'
type: string
enum: [healthy, fair, concerning, critical]
description: >
Falls back to scorecard thresholds — never 'excellent' (scatter uses
scorecard score only, not Tinybird health_label).
stewardshipStatus:
oneOf:
- $ref: '#/components/schemas/StewardshipStatus'
Expand Down Expand Up @@ -545,7 +582,7 @@ paths:
in: query
schema:
type: string
enum: [active, stable, declining, abandoned]
enum: [active, stable, declining, abandoned, archived]
- name: status
in: query
description: >
Expand Down Expand Up @@ -625,13 +662,17 @@ paths:
name: slf4j-api
ecosystem: maven
criticalityScore: 0.998
impact: 100
stewardshipId: '101'
stewardshipStatus: active
openVulns: 0
maxVulnSeverity: null
maintainerCount: 2
scorecardScore: 7.5
healthBand: healthy
health:
score: 75
label: healthy
lifecycle: active
latestReleaseAt: '2026-04-10T00:00:00Z'
lastActivity:
type: state_changed
Expand Down
18 changes: 15 additions & 3 deletions backend/src/api/public/v1/ossprey/packageList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ import { ok } from '@/utils/api'
import { validateOrThrow } from '@/utils/validation'

import { purlFilterSchema } from '../packages/purl'
import { HEALTH_BAND_SET, HEALTH_BAND_VALUES, LIFECYCLE_VALUES } from '../packages/types'

const MAX_PAGE_SIZE = 250
const LIFECYCLE_SET = new Set<string>(LIFECYCLE_VALUES)

const boolParam = z.preprocess((v) => v === 'true', z.boolean()).default(false)

const querySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
pageSize: z.coerce.number().int().min(1).max(MAX_PAGE_SIZE).default(25),
ecosystem: z.string().trim().optional(),
lifecycle: z.enum(['active', 'stable', 'declining', 'abandoned']).optional(),
lifecycle: z.enum(LIFECYCLE_VALUES).optional(),
name: z.string().trim().optional(),
purl: purlFilterSchema,
status: z
Expand All @@ -37,7 +39,7 @@ const querySchema = z.object({
'inactive',
])
.optional(),
healthBand: z.enum(['healthy', 'fair', 'concerning', 'critical']).optional(),
healthBand: z.enum(HEALTH_BAND_VALUES).optional(),
vulnSeverity: z.enum(['any', 'high', 'critical', 'none']).optional(),
staleOnly: boolParam,
unstewardedOnly: boolParam,
Expand Down Expand Up @@ -73,13 +75,23 @@ export async function packageListHandler(req: Request, res: Response): Promise<v
name: r.name,
ecosystem: r.ecosystem,
criticalityScore: r.criticalityScore,
impact: r.criticalityScore != null ? Math.round(r.criticalityScore * 100) : null,
stewardshipId: r.stewardshipId ?? null,
stewardshipStatus: r.stewardshipStatus ?? null,
openVulns: r.openVulns,
maxVulnSeverity: r.maxVulnSeverity ?? null,
maintainerCount: r.maintainerCount,
scorecardScore: r.scorecardScore,
healthBand: computeHealthBand(r.scorecardScore != null ? Number(r.scorecardScore) : null),
health: {
score:
r.healthScore ?? (r.scorecardScore != null ? Math.round(r.scorecardScore * 10) : null),
label:
r.healthLabel != null && HEALTH_BAND_SET.has(r.healthLabel)
? r.healthLabel
: computeHealthBand(r.scorecardScore),
},
lifecycle:
r.lifecycleLabel != null && LIFECYCLE_SET.has(r.lifecycleLabel) ? r.lifecycleLabel : null,
latestReleaseAt: r.latestReleaseAt ? r.latestReleaseAt.toISOString() : null,
lastActivity: r.lastActivityAt
? {
Expand Down
21 changes: 14 additions & 7 deletions backend/src/api/public/v1/packages/getPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import { ok } from '@/utils/api'
import { validateOrThrow } from '@/utils/validation'

import { purlQuerySchema } from './purl'
import type { StewardshipStatus } from './types'
import { HEALTH_BAND_SET, LIFECYCLE_VALUES, type StewardshipStatus } from './types'

const LIFECYCLE_SET = new Set<string>(LIFECYCLE_VALUES)

function snakeToCamelKeys(obj: Record<string, unknown> | null): Record<string, unknown> | null {
if (obj === null) return null
Expand Down Expand Up @@ -47,7 +49,6 @@ 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 healthBandScore = pkg.healthScore !== null ? pkg.healthScore / 10 : scorecardScore

ok(res, {
purl: pkg.purl,
Expand All @@ -58,22 +59,28 @@ export async function getPackage(req: Request, res: Response): Promise<void> {
healthScore: pkg.healthScore,
healthScoreDetails: {
total: pkg.healthScore,
label: pkg.healthLabel,
label:
pkg.healthLabel != null && HEALTH_BAND_SET.has(pkg.healthLabel) ? pkg.healthLabel : null,
maintainerHealth: pkg.maintainerHealthScore,
securitySupplyChain: pkg.securitySupplyChainScore,
developmentActivity: pkg.developmentActivityScore,
},
healthBand: computeHealthBand(healthBandScore),
healthBand:
pkg.healthLabel != null && HEALTH_BAND_SET.has(pkg.healthLabel)
? pkg.healthLabel
: computeHealthBand(scorecardScore),
Comment thread
ulemons marked this conversation as resolved.
impact: {
impactScore:
pkg.criticalityScore != null ? Math.round(Number(pkg.criticalityScore) * 100) : null,
impactScore: pkg.criticalityScore != null ? Math.round(pkg.criticalityScore * 100) : null,
downloadsLastMonth: pkg.downloadsLast30d ?? null,
dependentPackages: pkg.dependentPackagesCount ?? null,
dependentRepos: pkg.dependentReposCount ?? null,
transitiveReach: pkg.transitiveReach,
},
riskSignals: {
lifecycle: pkg.lifecycleLabel,
lifecycle:
pkg.lifecycleLabel != null && LIFECYCLE_SET.has(pkg.lifecycleLabel)
? pkg.lifecycleLabel
: null,
maintainerBusFactor: pkg.maintainerCount,
lastRelease: pkg.latestReleaseAt ? pkg.latestReleaseAt.toISOString() : null,
hasSecurityFile: pkg.hasSecurityFile,
Expand Down
34 changes: 25 additions & 9 deletions backend/src/api/public/v1/packages/listPackages.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import type { Request, Response } from 'express'
import { z } from 'zod'

import { getPackageStatusCounts, listPackagesForApi } from '@crowd/data-access-layer'
import {
computeHealthBand,
getPackageStatusCounts,
listPackagesForApi,
} from '@crowd/data-access-layer'

import { getPackagesQx } from '@/db/packagesDb'
import { ok } from '@/utils/api'
import { validateOrThrow } from '@/utils/validation'

import { purlFilterSchema } from './purl'
import { STEWARDSHIP_STATUS_VALUES, type StewardshipStatus } from './types'
import {
HEALTH_BAND_SET,
HEALTH_BAND_VALUES,
LIFECYCLE_VALUES,
STEWARDSHIP_STATUS_VALUES,
type StewardshipStatus,
} from './types'

const DEFAULT_PAGE_SIZE = 20
const MAX_PAGE_SIZE = 100

const booleanQueryParam = z.preprocess((v) => v === 'true', z.boolean()).default(false)

const lifecycleValues = ['active', 'stable', 'declining', 'abandoned'] as const
const healthBandValues = ['healthy', 'fair', 'concerning', 'critical'] as const
const LIFECYCLE_SET = new Set<string>(LIFECYCLE_VALUES)
const vulnSeverityValues = ['any', 'high', 'critical', 'none'] as const

const querySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
pageSize: z.coerce.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE),
ecosystem: z.string().trim().optional(),
lifecycle: z.enum(lifecycleValues).optional(),
lifecycle: z.enum(LIFECYCLE_VALUES).optional(),
name: z.string().trim().optional(),
purl: purlFilterSchema,
status: z.enum(STEWARDSHIP_STATUS_VALUES).optional(),
healthBand: z.enum(healthBandValues).optional(),
healthBand: z.enum(HEALTH_BAND_VALUES).optional(),
vulnSeverity: z.enum(vulnSeverityValues).optional(),
busFactor1Only: booleanQueryParam,
staleOnly: booleanQueryParam,
Expand Down Expand Up @@ -84,9 +93,16 @@ export async function listPackages(req: Request, res: Response): Promise<void> {
purl: r.purl,
name: r.name,
ecosystem: r.ecosystem,
health: r.scorecardScore != null ? Math.round(Number(r.scorecardScore) * 10) : null,
impact: r.criticalityScore != null ? Math.round(Number(r.criticalityScore) * 100) : null,
lifecycle: null,
health: {
score: r.healthScore ?? (r.scorecardScore != null ? Math.round(r.scorecardScore * 10) : null),
label:
r.healthLabel != null && HEALTH_BAND_SET.has(r.healthLabel)
? r.healthLabel
: computeHealthBand(r.scorecardScore),
},
impact: r.criticalityScore != null ? Math.round(r.criticalityScore * 100) : null,
lifecycle:
r.lifecycleLabel != null && LIFECYCLE_SET.has(r.lifecycleLabel) ? r.lifecycleLabel : null,
maintainerBusFactor: r.maintainerCount,
openVulns: r.openVulns,
stewardshipId: r.stewardshipId ?? null,
Expand Down
32 changes: 22 additions & 10 deletions backend/src/api/public/v1/packages/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ components:
type:
- string
- 'null'
enum: [active, stable, declining, abandoned, null]
enum: [active, stable, declining, abandoned, archived, null]
health:
type:
- integer
Expand Down Expand Up @@ -180,10 +180,20 @@ components:
type: string
example: npm
health:
type:
- integer
- 'null'
example: 18
type: object
required: [score, label]
properties:
score:
type:
- integer
- 'null'
description: Health score (0–100). Tinybird composite score when enriched, OpenSSF Scorecard × 10 otherwise. Null if neither is available.
example: 18
label:
type: string
enum: [excellent, healthy, fair, concerning, critical]
description: 'Tinybird band when enriched (excellent ≥85, healthy 70–84, fair 50–69, concerning 30–49, critical <30), scorecard band otherwise.'
example: critical
impact:
type:
- integer
Expand All @@ -193,7 +203,7 @@ components:
type:
- string
- 'null'
enum: [active, stable, declining, abandoned, null]
enum: [active, stable, declining, abandoned, archived, null]
maintainerBusFactor:
type:
- integer
Expand Down Expand Up @@ -307,7 +317,7 @@ components:
type:
- string
- 'null'
enum: [healthy, fair, concerning, critical, null]
enum: [excellent, healthy, fair, concerning, critical, null]
description: Derived from Tinybird health score when available, OpenSSF Scorecard otherwise.
example: concerning
impact:
Expand Down Expand Up @@ -350,7 +360,7 @@ components:
type:
- string
- 'null'
enum: [active, stable, declining, abandoned, null]
enum: [active, stable, declining, abandoned, archived, null]
maintainerBusFactor:
type:
- integer
Expand Down Expand Up @@ -541,7 +551,7 @@ paths:
in: query
schema:
type: string
enum: [active, stable, declining, abandoned]
enum: [active, stable, declining, abandoned, archived]
- name: busFactor1Only
in: query
schema:
Expand Down Expand Up @@ -814,7 +824,9 @@ paths:
name: lodash
ecosystem: npm
lifecycle: declining
health: 18
health:
score: 18
label: critical
Comment thread
ulemons marked this conversation as resolved.
impact: 71
openVulns:
low: 0
Expand Down
Loading
Loading