Skip to content

lifecycle assessor marks archived-but-still-published packages as EOL-Confirmed (no registry-liveness check) #451

@kotakanbe

Description

@kotakanbe

Summary

In internal/domain/analysis/lifecycle_assessor.go, the archive/disable branch of assessInternal returns EOL-Confirmed for any archived or disabled repository, without checking whether the package is still published on its registry. This produces false EOL classifications for packages whose repository was archived (e.g., monorepo consolidation) but whose package (PURL) is still actively published.

Where

// internal/domain/analysis/lifecycle_assessor.go (assessInternal)
// 1. Archive/disable check
if analysis != nil && (analysis.IsArchived() || analysis.IsDisabled()) {
    ...
    return &AssessmentResult{
        Axis: LifecycleAxis, Label: string(LabelEOLConfirmed),
        Reason: "Repository archived or disabled", ...}, nil
}

// 1.5 Primary-source EOL status override
if in.EOL.IsEOL() { ... }

The archive check runs before the primary-source EOL check (in.EOL.IsEOL()), so for an archived repo the assessor short-circuits to EOL-Confirmed regardless of registry signals.

Impact

FinalMaintenanceStatus() returns EOL-Confirmed via this lifecycle label even when there is no primary-source EOL signal (no npm deprecated, PyPI yanked, Packagist abandoned, Maven <relocation>). Consumers that treat EOL-Confirmed as a decisive EOL signal then mis-classify actively-maintained packages as end-of-life.

Real-world examples — repository archived (monorepo consolidation) but the package is still publishing new versions:

PURL repository (currently archived) still publishing?
pkg:npm/google-auth-library googleapis/google-auth-library-nodejs yes
pkg:npm/google-gax googleapis/gax-nodejs yes
pkg:golang/github.com/grafana/k6deps grafana/k6deps yes

For each of these, IsArchived() == true, EOL.IsEOL() == false, yet FinalMaintenanceStatus() == EOL-Confirmed (verified against the live pipeline with Evaluator.EvaluatePURLs).

Proposed fix

Gate the archive/disable → EOL-Confirmed branch on registry liveness, so EOL-Confirmed is reserved for genuine end-of-life. For example:

  • If ReleaseInfo.StableVersion exists, is not deprecated, and was published recently, do not return EOL-Confirmed from the archive branch — fall through to the normal lifecycle assessment (so the package lands on Stalled / Legacy-Safe instead).
  • Keep EOL-Confirmed for primary-source EOL (EOL.IsEOL()) and registry-declared deprecation; classify "repository archived but still published" as Stalled (dormant, not EOL).

Either approach keeps real EOL (deprecated / yanked / abandoned / relocation) as EOL-Confirmed while removing the false positives for archived-but-alive packages.

Workaround in place downstream

A downstream consumer currently compensates by downgrading EOL-Confirmed && !EOL.IsEOL() && (IsArchived() || IsDisabled()) to Stalled at ingestion time. That relies on the invariant that the lifecycle axis only emits EOL-Confirmed for archived repos when !EOL.IsEOL() (the archive check precedes the primary-source check). A registry-liveness gate in the assessor would make this downstream workaround unnecessary and fix the classification at the source.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions