Skip to content

Commit 6b15a42

Browse files
committed
feat: adding stewardship tables and small fixis
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1 parent 61896b1 commit 6b15a42

5 files changed

Lines changed: 211 additions & 31 deletions

File tree

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

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ok } from '@/utils/api'
55
import { validateOrThrow } from '@/utils/validation'
66

77
import { MOCK_DETAILS } from './mockData'
8+
import type { OpenVulns, StewardshipSummary } from './types'
89

910
const MAX_PURLS = 100
1011

@@ -15,19 +16,6 @@ const bodySchema = z.object({
1516
.max(MAX_PURLS, `Maximum ${MAX_PURLS} purls per request`),
1617
})
1718

18-
interface StewardshipSummary {
19-
name: string
20-
ecosystem: string
21-
lifecycle: string
22-
health: number
23-
impact: number
24-
openVulns: { low: number; medium: number; high: number; critical: number }
25-
stewardship: string
26-
stewards: null
27-
lastActivityAt: null
28-
lastActivityDescription: null
29-
}
30-
3119
// TODO: replace with real DB queries once stewardship tables land
3220
export async function batchGetStewardship(req: Request, res: Response): Promise<void> {
3321
const { purls } = validateOrThrow(bodySchema, req.body)
@@ -38,11 +26,9 @@ export async function batchGetStewardship(req: Request, res: Response): Promise<
3826
if (!detail) {
3927
packages[purl] = null
4028
} else {
41-
const openVulns = { low: 0, medium: 0, high: 0, critical: 0 }
29+
const openVulns: OpenVulns = { low: 0, medium: 0, high: 0, critical: 0 }
4230
for (const advisory of detail.security.advisories) {
43-
if (advisory.severity in openVulns) {
44-
openVulns[advisory.severity] += 1
45-
}
31+
openVulns[advisory.severity] += 1
4632
}
4733
packages[purl] = {
4834
name: detail.name,
@@ -51,7 +37,7 @@ export async function batchGetStewardship(req: Request, res: Response): Promise<
5137
health: detail.general.healthScore.total,
5238
impact: detail.general.impact.impactScore,
5339
openVulns,
54-
stewardship: 'unassigned',
40+
stewardship: detail.stewardship.status,
5541
stewards: null,
5642
lastActivityAt: null,
5743
lastActivityDescription: null,

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import type { Lifecycle, OpenVulns, SeverityLevel, Steward, StewardshipStatus } from './types'
2+
13
export interface MockPackageListItem {
24
purl: string
35
name: string
46
ecosystem: string
57
health: number
68
impact: number
7-
lifecycle: 'active' | 'stable' | 'declining' | 'abandoned'
9+
lifecycle: Lifecycle
810
maintainerBusFactor: number
9-
openVulns: { low: number; medium: number; high: number; critical: number }
10-
stewardship: string
11-
steward: null
11+
openVulns: OpenVulns
12+
stewardship: StewardshipStatus
13+
stewards: Steward | null
1214
}
1315

1416
export interface MockPackageDetail {
@@ -30,19 +32,19 @@ export interface MockPackageDetail {
3032
transitiveReach: string
3133
}
3234
riskSignals: {
33-
lifecycle: string
35+
lifecycle: Lifecycle
3436
maintainerBusFactor: number
3537
lastRelease: string
3638
hasSecurityFile: null
3739
openSSFScorecard: number
3840
}
3941
}
40-
assessment: Record<string, never>
42+
assessment: Record<string, unknown>
4143
security: {
4244
securityContacts: null
4345
advisories: Array<{
4446
osvId: string
45-
severity: 'critical' | 'high' | 'medium' | 'low'
47+
severity: SeverityLevel
4648
resolution: null
4749
}>
4850
cvd: {
@@ -56,7 +58,12 @@ export interface MockPackageDetail {
5658
repositoryMapping: { declaredRepo: string; mappingConfidence: number; lastCommitAt: string }
5759
supplyChainIntegrity: { buildProvenance: null; signedReleases: null }
5860
}
59-
history: Record<string, never>
61+
stewardship: {
62+
status: StewardshipStatus
63+
stewards: Steward | null
64+
lastActivityAt: string | null
65+
}
66+
history: Record<string, unknown>
6067
}
6168

6269
export const MOCK_PACKAGES: MockPackageListItem[] = [
@@ -70,7 +77,7 @@ export const MOCK_PACKAGES: MockPackageListItem[] = [
7077
maintainerBusFactor: 1,
7178
openVulns: { low: 0, medium: 0, high: 1, critical: 0 },
7279
stewardship: 'unassigned',
73-
steward: null,
80+
stewards: null,
7481
},
7582
{
7683
purl: 'pkg:maven/org.apache.commons/commons-lang3@3.12.0',
@@ -82,7 +89,7 @@ export const MOCK_PACKAGES: MockPackageListItem[] = [
8289
maintainerBusFactor: 3,
8390
openVulns: { low: 0, medium: 0, high: 0, critical: 0 },
8491
stewardship: 'unassigned',
85-
steward: null,
92+
stewards: null,
8693
},
8794
{
8895
purl: 'pkg:npm/minimist@1.2.6',
@@ -94,7 +101,7 @@ export const MOCK_PACKAGES: MockPackageListItem[] = [
94101
maintainerBusFactor: 1,
95102
openVulns: { low: 0, medium: 1, high: 0, critical: 1 },
96103
stewardship: 'unassigned',
97-
steward: null,
104+
stewards: null,
98105
},
99106
]
100107

@@ -144,6 +151,7 @@ export const MOCK_DETAILS: Record<string, MockPackageDetail> = {
144151
},
145152
supplyChainIntegrity: { buildProvenance: null, signedReleases: null },
146153
},
154+
stewardship: { status: 'unassigned', stewards: null, lastActivityAt: null },
147155
history: {},
148156
},
149157
'pkg:maven/org.apache.commons/commons-lang3@3.12.0': {
@@ -191,6 +199,7 @@ export const MOCK_DETAILS: Record<string, MockPackageDetail> = {
191199
},
192200
supplyChainIntegrity: { buildProvenance: null, signedReleases: null },
193201
},
202+
stewardship: { status: 'unassigned', stewards: null, lastActivityAt: null },
194203
history: {},
195204
},
196205
'pkg:npm/minimist@1.2.6': {
@@ -241,6 +250,7 @@ export const MOCK_DETAILS: Record<string, MockPackageDetail> = {
241250
},
242251
supplyChainIntegrity: { buildProvenance: null, signedReleases: null },
243252
},
253+
stewardship: { status: 'unassigned', stewards: null, lastActivityAt: null },
244254
history: {},
245255
},
246256
}

backend/src/api/public/v1/packages/openapi.yaml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ components:
105105
StewardshipSummary:
106106
type: object
107107
description: Null if the purl is not found in CDP.
108-
required: [name, ecosystem, stewardship, stewards, lastActivityAt, lastActivityDescription]
108+
required: [name, ecosystem, stewardship, stewards, openVulns, lastActivityAt, lastActivityDescription]
109109
properties:
110110
name:
111111
type: string
@@ -193,7 +193,7 @@ components:
193193
- type: 'null'
194194
stewardship:
195195
$ref: '#/components/schemas/StewardshipStatus'
196-
steward:
196+
stewards:
197197
description: Single assigned steward or null.
198198
oneOf:
199199
- $ref: '#/components/schemas/Steward'
@@ -411,6 +411,23 @@ components:
411411
type:
412412
- string
413413
- 'null'
414+
stewardship:
415+
type: object
416+
description: Stewardship state. In v1 always unassigned with no stewards or activity.
417+
properties:
418+
status:
419+
$ref: '#/components/schemas/StewardshipStatus'
420+
stewards:
421+
description: Single assigned steward or null. Null in v1.
422+
oneOf:
423+
- $ref: '#/components/schemas/Steward'
424+
- type: 'null'
425+
lastActivityAt:
426+
type:
427+
- string
428+
- 'null'
429+
format: date-time
430+
description: Null in v1.
414431
history:
415432
type: object
416433
description: Package history data. Empty in v1.
@@ -657,6 +674,10 @@ paths:
657674
supplyChainIntegrity:
658675
buildProvenance: null
659676
signedReleases: null
677+
stewardship:
678+
status: unassigned
679+
stewards: null
680+
lastActivityAt: null
660681
history: {}
661682
'404':
662683
description: Package not found.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export type StewardshipStatus =
2+
| 'unassigned'
3+
| 'open'
4+
| 'assessing'
5+
| 'active'
6+
| 'needs_attention'
7+
| 'escalated'
8+
| 'blocked'
9+
| 'inactive'
10+
11+
export type Lifecycle = 'active' | 'stable' | 'declining' | 'abandoned'
12+
13+
export type SeverityLevel = 'critical' | 'high' | 'medium' | 'low'
14+
15+
export interface OpenVulns {
16+
low: number
17+
medium: number
18+
high: number
19+
critical: number
20+
}
21+
22+
export interface Steward {
23+
userId: string
24+
name: string
25+
role: 'lead' | 'co_steward'
26+
assignedAt: string
27+
}
28+
29+
export interface StewardshipSummary {
30+
name: string
31+
ecosystem: string
32+
lifecycle: Lifecycle | null
33+
health: number | null
34+
impact: number | null
35+
openVulns: OpenVulns | null
36+
stewardship: StewardshipStatus
37+
stewards: Steward | null
38+
lastActivityAt: string | null
39+
lastActivityDescription: string | null
40+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
-- Stewardship tables for the OSSPREY Self Serve program (v1).
2+
-- In v1: only `stewardships` is populated (one unassigned row per critical package).
3+
-- All other tables are schema-only — empty until v2 write flows land.
4+
5+
CREATE TABLE IF NOT EXISTS stewardships (
6+
id BIGSERIAL PRIMARY KEY,
7+
package_id BIGINT NOT NULL REFERENCES packages(id),
8+
status TEXT NOT NULL, -- 'unassigned'|'open'|'assessing'|'active'|'needs_attention'|'escalated'|'blocked'|'inactive'
9+
origin TEXT NOT NULL, -- 'auto_imported'|'self_claimed'|'assigned'|'opened_for_claim'
10+
version INT NOT NULL DEFAULT 1,
11+
opened_at TIMESTAMPTZ,
12+
last_status_at TIMESTAMPTZ,
13+
inactive_reason TEXT, -- 'quarterly_cadence_missed'|'stepped_down'|'no_longer_critical'
14+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
15+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
16+
UNIQUE (package_id)
17+
);
18+
19+
CREATE INDEX IF NOT EXISTS stewardships_status_idx
20+
ON stewardships (status);
21+
CREATE INDEX IF NOT EXISTS stewardships_last_status_at_active_idx
22+
ON stewardships (last_status_at) WHERE status = 'active';
23+
24+
-- Many-to-many stewards. Empty in v1; soft-delete preserves historical membership.
25+
CREATE TABLE IF NOT EXISTS stewardship_stewards (
26+
id BIGSERIAL PRIMARY KEY,
27+
stewardship_id BIGINT NOT NULL REFERENCES stewardships(id),
28+
user_id TEXT NOT NULL,
29+
role TEXT NOT NULL, -- 'lead'|'co_steward'
30+
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
31+
assigned_by TEXT,
32+
deleted_at TIMESTAMPTZ
33+
);
34+
35+
CREATE UNIQUE INDEX IF NOT EXISTS stewardship_stewards_active_unique
36+
ON stewardship_stewards (stewardship_id, user_id)
37+
WHERE deleted_at IS NULL;
38+
CREATE INDEX IF NOT EXISTS stewardship_stewards_user_id_active_idx
39+
ON stewardship_stewards (user_id) WHERE deleted_at IS NULL;
40+
41+
-- Append-only audit log. Empty in v1.
42+
CREATE TABLE IF NOT EXISTS stewardship_activity (
43+
id BIGSERIAL PRIMARY KEY,
44+
stewardship_id BIGINT NOT NULL REFERENCES stewardships(id),
45+
actor_user_id TEXT, -- NULL for system events
46+
actor_type TEXT NOT NULL, -- 'user'|'system'
47+
activity_type TEXT NOT NULL, -- 'state_changed'|'assessment_completed'|'assessment_flagged'|
48+
-- 'remediation_logged'|'status_update'|'escalation'|
49+
-- 'escalation_resolved'|'blocker_added'|'blocker_resolved'|
50+
-- 'steward_added'|'steward_removed'
51+
content TEXT,
52+
metadata JSONB,
53+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
54+
);
55+
56+
CREATE INDEX IF NOT EXISTS stewardship_activity_stewardship_id_created_at_idx
57+
ON stewardship_activity (stewardship_id, created_at DESC);
58+
59+
-- One current assessment per stewardship; historical ones preserved via superseded_at.
60+
CREATE TABLE IF NOT EXISTS stewardship_assessments (
61+
id BIGSERIAL PRIMARY KEY,
62+
stewardship_id BIGINT NOT NULL REFERENCES stewardships(id),
63+
posture TEXT,
64+
summary TEXT,
65+
security_contact TEXT,
66+
disclosure_preference TEXT,
67+
tier_0_ready BOOL NOT NULL DEFAULT FALSE,
68+
monitoring_plan TEXT,
69+
draft BOOL NOT NULL DEFAULT TRUE,
70+
completed_at TIMESTAMPTZ,
71+
completed_by TEXT,
72+
reviewed BOOL NOT NULL DEFAULT FALSE,
73+
reviewed_at TIMESTAMPTZ,
74+
reviewed_by TEXT,
75+
flagged BOOL NOT NULL DEFAULT FALSE,
76+
flag_note TEXT,
77+
superseded_at TIMESTAMPTZ,
78+
superseded_by_id BIGINT REFERENCES stewardship_assessments(id),
79+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
80+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -- tracks mutations: reviewed, flagged, superseded_at
81+
);
82+
83+
CREATE INDEX IF NOT EXISTS stewardship_assessments_stewardship_id_superseded_at_idx
84+
ON stewardship_assessments (stewardship_id, superseded_at);
85+
CREATE UNIQUE INDEX IF NOT EXISTS stewardship_assessments_one_current
86+
ON stewardship_assessments (stewardship_id)
87+
WHERE superseded_at IS NULL;
88+
89+
-- Per-dimension findings. assessment_id links a finding to the assessment that produced it.
90+
CREATE TABLE IF NOT EXISTS stewardship_findings (
91+
id BIGSERIAL PRIMARY KEY,
92+
stewardship_id BIGINT NOT NULL REFERENCES stewardships(id),
93+
assessment_id BIGINT REFERENCES stewardship_assessments(id), -- NULL until assessment flow lands in v2
94+
dimension TEXT NOT NULL, -- 'maintainer_health'|'security_posture'|'vulnerability_exposure'|
95+
-- 'dependency_risk'|'supply_chain_integrity'|'release_health'
96+
severity TEXT NOT NULL, -- 'critical'|'high'|'medium'|'low'|'informational'
97+
finding TEXT NOT NULL,
98+
evidence TEXT,
99+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
100+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
101+
);
102+
103+
CREATE INDEX IF NOT EXISTS stewardship_findings_stewardship_id_idx
104+
ON stewardship_findings (stewardship_id);
105+
CREATE INDEX IF NOT EXISTS stewardship_findings_dimension_severity_idx
106+
ON stewardship_findings (dimension, severity);
107+
108+
-- Concrete remediation actions. Empty in v1.
109+
CREATE TABLE IF NOT EXISTS stewardship_remediation_actions (
110+
id BIGSERIAL PRIMARY KEY,
111+
stewardship_id BIGINT NOT NULL REFERENCES stewardships(id),
112+
finding_id BIGINT REFERENCES stewardship_findings(id),
113+
action TEXT NOT NULL,
114+
status TEXT NOT NULL, -- 'pending'|'in_progress'|'done'|'blocked'|'abandoned'
115+
url TEXT,
116+
notes TEXT,
117+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
118+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
119+
completed_at TIMESTAMPTZ
120+
);
121+
122+
CREATE INDEX IF NOT EXISTS stewardship_remediation_actions_stewardship_id_status_idx
123+
ON stewardship_remediation_actions (stewardship_id, status);

0 commit comments

Comments
 (0)