Interest Scoring
Per SPRINT_8200_0013_0002.
Overview
Interest scoring learns which advisories matter to your organization by analyzing SBOM intersections, reachability data, VEX status, and deployment signals. High-interest advisories are prioritized and cached, while low-interest ones can be compacted to lightweight stub representation to save resources without losing provenance.
Score Components
The interest score is computed from weighted factors:
| Factor | Weight | Description |
|---|---|---|
in_sbom | 0.4 | Advisory affects a component in registered SBOMs |
reachable | 0.25 | Affected code is reachable in call graph |
deployed | 0.15 | Component is deployed in production |
no_vex_na | 0.1 | No VEX “not_affected” statement exists |
recent | 0.1 | Advisory is recent (age decay applied) |
Score Calculation
score = (in_sbom x 0.4) + (reachable x 0.25) + (deployed x 0.15)
+ (no_vex_na x 0.1) + (recent x 0.1)
Each factor is binary (0.0 or 1.0), except recent which decays over time.
Configuration
Configure in concelier.yaml:
InterestScoring:
Enabled: true
# Factor weights (must sum to 1.0)
Weights:
InSbom: 0.4
Reachable: 0.25
Deployed: 0.15
NoVexNa: 0.1
Recent: 0.1
# Age decay for recent factor
RecentDecay:
HalfLifeDays: 90
MinScore: 0.1
# Low-interest advisory compaction policy
Degradation:
Enabled: true
ThresholdScore: 0.1
GracePeriodDays: 30
RetainCve: true # Keep CVE ID even in stub form
# Recalculation job
RecalculationJob:
Enabled: true
IncrementalIntervalMinutes: 15
FullRecalculationHour: 3 # 3 AM daily
API Endpoints
Endpoint shape is generated in Concelier API Reference. Relevant operations:
GET /api/v1/canonical/{id}/scorePOST /api/v1/scores/recalculatePOST /api/v1/learn/sbom
Score Tiers
| Tier | Score Range | Cache TTL | Behavior |
|---|---|---|---|
| High | >= 0.7 | 24 hours | Full advisory cached, prioritized in queries |
| Medium | 0.3 - 0.7 | 4 hours | Full advisory cached |
| Low | 0.1 - 0.3 | 1 hour | Full advisory, eligible for compaction |
| Negligible | < 0.1 | none | Compacted to stub representation after grace period |
Advisory Stub Compaction
Low-interest advisories are compacted to lightweight stubs to save storage and attention. Stub is a domain representation state, not a fake implementation. Compaction must preserve canonical identity, source edges, provenance scopes, raw document hashes, and evidence references so audit, replay, and restoration remain deterministic.
Full Advisory vs Stub
| Field | Full | Stub |
|---|---|---|
| id | yes | yes |
| cve | yes | yes |
| affects_key | yes | yes |
| merge_hash | yes | yes |
| severity | yes | yes |
| title | yes | no |
| summary | yes | no |
| references | yes | no |
| weaknesses | yes | no |
| source_edges | preserved | preserved |
Restoration
Stubs are restored to full advisories when:
- Interest score increases above threshold
- Advisory is directly queried by ID
- Advisory appears in scan results
// Automatic restoration
await scoringService.RestoreFromStubAsync(canonicalId, ct);
Integration Points
SBOM Registration
When an SBOM is registered, interest scores are updated:
See the generated endpoint block above for the SBOM-learning HTTP shape.
Scan Events
Subscribe to ScanCompleted events:
// In event handler
public async Task HandleScanCompletedAsync(ScanCompletedEvent evt)
{
await sbomService.LearnFromScanAsync(evt.SbomDigest, ct);
// This triggers interest score updates for affected advisories
}
VEX Updates
VEX imports trigger score recalculation:
// In VEX ingestion pipeline
await interestService.RecalculateForCanonicalAsync(canonicalId, ct);
CLI Commands
# View score for advisory
stella scores get sha256:mergehash...
# Trigger recalculation
stella scores recalculate --mode incremental
# Full recalculation
stella scores recalculate --mode full
# List high-interest advisories
stella scores list --tier high --limit 100
# Force restore from stub
stella scores restore sha256:mergehash...
Monitoring
Metrics
| Metric | Type | Description |
|---|---|---|
interest_score_computed_total | Counter | Scores computed |
interest_score_distribution | Histogram | Score distribution |
interest_stubs_created_total | Counter | Advisories compacted to stub representation |
interest_stubs_restored_total | Counter | Stubs restored to full |
interest_job_duration_seconds | Histogram | Recalculation job duration |
Alerts
# Example Prometheus alert
- alert: InterestScoreJobStale
expr: time() - interest_score_last_full_recalc > 172800
for: 1h
labels:
severity: warning
annotations:
summary: "Interest score full recalculation hasn't run in 2 days"
Database Schema
CREATE TABLE vuln.interest_score (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
canonical_id UUID NOT NULL REFERENCES vuln.advisory_canonical(id),
score NUMERIC(3,2) NOT NULL CHECK (score >= 0 AND score <= 1),
reasons JSONB NOT NULL DEFAULT '[]',
last_seen_in_build UUID,
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_interest_score_canonical UNIQUE (canonical_id)
);
CREATE INDEX idx_interest_score_score ON vuln.interest_score(score DESC);
CREATE INDEX idx_interest_score_high ON vuln.interest_score(canonical_id)
WHERE score >= 0.7;
