Module: StillActive::StatusHelper

Extended by:
StatusHelper
Included in:
StatusHelper
Defined in:
lib/helpers/status_helper.rb

Overview

Collapses a gem's several maintenance signals into one categorical verdict, so a machine/LLM consumer (or another tool's report) can display and threshold a single value instead of re-deriving it from activity_level + archived + vulnerability_count. This is deliberately NOT a numeric composite: an earlier 0-100 score was removed because a weighted average let missing data read as "perfect health". Here, :unknown stays :unknown -- absence of data is never rendered as :ok.

Constant Summary collapse

SEVERITY =

Worst-first lifecycle verdict. The key distinction (from the "done gems" critique and validated against the maintenance-tooling landscape): a clean, long-dormant gem is :legacy ("done", low risk), NOT a problem -- whereas a dormant or archived gem carrying an unpatched advisory is :dead (no one is going to fix it, migrate). :unknown is least severe -- an absence, not a finding -- so missing data never reads as :ok.

[:unknown, :ok, :legacy, :stale, :archived, :vulnerable, :dead].freeze

Instance Method Summary collapse

Instance Method Details

#gem_status(gem_data) ⇒ Object

Returns :dead, :vulnerable, :archived, :legacy, :stale, :ok, or :unknown.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/helpers/status_helper.rb', line 25

def gem_status(gem_data)
  vulnerable = gem_data[:vulnerability_count].to_i.positive?

  # A pinned version the registry can't resolve (yanked, typo, or nonexistent)
  # has no version-specific data to judge, so package-level health must not read
  # it as :ok. Absence of data is :unknown, never a false all-clear. Guarded on
  # `!vulnerable` so a detected advisory always wins, in case a future source
  # ever attaches one to an otherwise-unresolved version.
  return :unknown if gem_data[:version_unresolved] && !vulnerable

  level = ActivityHelper.activity_level(gem_data)

  if vulnerable
    # A vulnerability in a dormant or archived gem won't be patched -> :dead;
    # in an actively-released gem a fix is plausible -> :vulnerable.
    return [:critical, :archived].include?(level) ? :dead : :vulnerable
  end

  case level
  when :archived
    # archived != EOL: a repo archived while the gem still publishes recent
    # releases (development moved to a monorepo) isn't dead -- let the
    # releases speak, but keep :stale so the archived repo stays a yellow flag.
    ActivityHelper.release_recency_level(gem_data) == :ok ? :stale : :archived
  when :critical then :legacy # long-dormant but clean: feature-complete, not a fire
  else level # :stale / :ok / :unknown
  end
end

#project_status(result, ruby_info: nil) ⇒ Object

The single worst gem status across the audit. An EOL Ruby floors the project at :vulnerable (the runtime itself is a live, actionable risk). :unknown only wins when nothing better is known.



57
58
59
60
61
62
63
64
65
66
# File 'lib/helpers/status_helper.rb', line 57

def project_status(result, ruby_info: nil)
  statuses = result.each_value.map { |data| gem_status(data) }
  statuses << :vulnerable if ruby_info&.dig(:eol) == true
  return :unknown if statuses.empty?

  # Every value gem_status returns is in SEVERITY today; if a future status
  # isn't, rank it most-severe so it surfaces in the rollup rather than being
  # silently masked by a milder finding.
  statuses.max_by { |status| SEVERITY.index(status) || SEVERITY.length }
end