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:

FactorWeightDescription
in_sbom0.4Advisory affects a component in registered SBOMs
reachable0.25Affected code is reachable in call graph
deployed0.15Component is deployed in production
no_vex_na0.1No VEX “not_affected” statement exists
recent0.1Advisory 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:

Score Tiers

TierScore RangeCache TTLBehavior
High>= 0.724 hoursFull advisory cached, prioritized in queries
Medium0.3 - 0.74 hoursFull advisory cached
Low0.1 - 0.31 hourFull advisory, eligible for compaction
Negligible< 0.1noneCompacted 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

FieldFullStub
idyesyes
cveyesyes
affects_keyyesyes
merge_hashyesyes
severityyesyes
titleyesno
summaryyesno
referencesyesno
weaknessesyesno
source_edgespreservedpreserved

Restoration

Stubs are restored to full advisories when:

// 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

MetricTypeDescription
interest_score_computed_totalCounterScores computed
interest_score_distributionHistogramScore distribution
interest_stubs_created_totalCounterAdvisories compacted to stub representation
interest_stubs_restored_totalCounterStubs restored to full
interest_job_duration_secondsHistogramRecalculation 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;