Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
65 changes: 51 additions & 14 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 @@ -545,7 +578,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 +658,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
15 changes: 12 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 { 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(['excellent', 'healthy', 'fair', 'concerning', 'critical']).optional(),
vulnSeverity: z.enum(['any', 'high', 'critical', 'none']).optional(),
staleOnly: boolParam,
unstewardedOnly: boolParam,
Expand Down Expand Up @@ -73,13 +75,20 @@ 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 ?? 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
24 changes: 16 additions & 8 deletions backend/src/api/public/v1/packages/listPackages.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
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 { 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 healthBandValues = ['excellent', 'healthy', 'fair', 'concerning', 'critical'] as const
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(),
Expand Down Expand Up @@ -84,9 +88,13 @@ 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 ?? computeHealthBand(r.scorecardScore),
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
Comment thread
ulemons marked this conversation as resolved.
Outdated
},
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
Comment thread
ulemons marked this conversation as resolved.
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
4 changes: 3 additions & 1 deletion backend/src/api/public/v1/packages/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export const STEWARDSHIP_STATUS_VALUES = [

export type StewardshipStatus = (typeof STEWARDSHIP_STATUS_VALUES)[number]

export type Lifecycle = 'active' | 'stable' | 'declining' | 'abandoned'
export const LIFECYCLE_VALUES = ['active', 'stable', 'declining', 'abandoned', 'archived'] as const

export type Lifecycle = (typeof LIFECYCLE_VALUES)[number]

export type SeverityLevel = 'critical' | 'high' | 'medium' | 'low'

Expand Down
Loading
Loading