Module: StillActive::Workflow

Extended by:
Workflow
Included in:
Workflow
Defined in:
lib/still_active/workflow.rb

Instance Method Summary collapse

Instance Method Details

#call(&on_progress) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/still_active/workflow.rb', line 34

def call(&on_progress)
  task = Async do
    # Load the optional ruby-advisory-db once, before the fan-out, so the
    # read-only Database is shared across fibers rather than reloaded per gem.
    advisory_db = RubyAdvisoryDb.load
    catalog = StillActive.config.alternatives ? CatalogIndex.load : nil
    # The Ruby support window (oldest-supported / latest-stable / EOL cycles)
    # is a per-run constant, so fetch it once here rather than per gem. nil when
    # the endoflife feed is unavailable -> the language-ceiling signal quietly
    # sits out, exactly like a missing advisory_db. This runs OUTSIDE the
    # per-gem rescue, so a defensive rescue keeps a best-effort enrichment from
    # ever aborting the whole audit (the "never crash the core" contract).
    ruby_range =
      begin
        RubyHelper.supported_ruby_range
      rescue StandardError => e
        $stderr.puts("warning: Ruby support window lookup failed: #{e.class} (#{e.message}); skipping language-ceiling checks")
        nil
      end
    # Resolve the GitHub token once here, single-fibered, before the fan-out:
    # provider_for reads it per gem across fibers and gh_cli_token shells out,
    # so resolving eagerly keeps that off the concurrent path and guarantees
    # every fiber sees one consistent value (token -> live GitHub, incl. private
    # repos; only a genuinely absent token falls back to ecosyste.ms).
    StillActive.config.github_oauth_token
    barrier = Async::Barrier.new
    semaphore = Async::Semaphore.new(StillActive.config.parallelism, parent: barrier)
    result_object = {}
    # Memoizes each capped dep's latest version across gems for the run, so a
    # dep pinned by several dormant gems is resolved once. Shared, single-writer
    # under Async's cooperative scheduling (a duplicate concurrent miss just
    # refetches the same value; it can't corrupt the map).
    constraint_cache = {}
    total = StillActive.config.gems.size
    completed = 0
    StillActive.config.gems.each_with_object(result_object) do |gem, hash|
      semaphore.async do
        gem_info(
          gem_name: gem[:name],
          result_object: hash,
          gem_version: gem[:version],
          source_type: gem[:source_type] || :rubygems,
          source_uri: gem[:source_uri],
          direct: gem.fetch(:direct, true),
          dependency_path: gem[:dependency_path],
          advisory_db: advisory_db,
          catalog: catalog,
          constraint_cache: constraint_cache,
          ruby_range: ruby_range,
        )
      rescue Octokit::TooManyRequests
        $stderr.print("\r\e[K") if on_progress
        $stderr.puts("rate limited checking #{gem[:name]}: set GITHUB_TOKEN to increase your limit")
      rescue StandardError => e
        $stderr.print("\r\e[K") if on_progress
        $stderr.puts("error occurred for #{gem[:name]}: #{e.class}\n\t#{e.message}")
      ensure
        completed += 1
        on_progress&.call(completed, total)
      end
    end
    barrier.wait
    # Whole-tree correlation, once every gem's signals are in: a ceiling's
    # "upgrade to lift it" must not contradict a poison finding that caps the
    # same gem below that upgrade.
    CeilingReconciler.reconcile_ceiling_with_poison(result_object)
    # Flag poison caps that pin a vulnerable dependency: "a dormant package is
    # holding you on a known-vulnerable dep, below the fix." No extra fetches.
    PoisonSecurityCorrelator.correlate(result_object)
    # Gems are inserted as their async tasks finish, so the natural order is
    # nondeterministic completion order. Sort by name once here so every
    # consumer (JSON, SARIF, the baseline diff) gets a stable, diffable order.
    result_object.sort_by { |name, _| name }.to_h
  end
  task.wait
end

#ruby_freshnessObject



111
112
113
# File 'lib/still_active/workflow.rb', line 111

def ruby_freshness
  RubyHelper.ruby_freshness
end