Add a cop for ENV reads in library code (A6) #1506

Open
opened 2026-11-02 16:01:01 +00:00 by jared · 0 comments
Owner

Problem

No cop flags an ENV read inside a library method body. Sdlc/RequireInsideCode/LoadTimeState flags only load-time ENV. Principle 9 names ENV as the outside world. Only the entry script reads the outside world.

Related: #1407, #1386, #1041 each fix one ENV site by hand; the cop's fixtures overlap their sites.

Reproduce

n/a: design ticket. The cop does not exist yet.

Approach

Jared ruled on 2026-11-02 that an env: ENV default argument is a library ENV read. Only bin/ reads ENV and passes it in. A keyword default that builds a collaborator object stays allowed. The lens rule is A6 in plugins/os-sdlc/skills/lens-architecture/SKILL.md. LoadTimeState has CONSTANTS = Set[:ENV, :Warning].freeze at plugins/os-sdlc/lib/os_sdlc/cops/require_inside_code/load_time_state.rb:8.

The cop must fire on these known sites:

  • plugins/os-backlog/lib/backlog/forgejo_backend.rb:8: def initialize(client: nil, env: ENV)
  • plugins/os-backlog/lib/backlog/cli.rb:17: @env = config.fetch(:env, ENV)
  • plugins/os-backlog/lib/backlog/inspector/executable_path.rb:6: def initialize(path = ENV.fetch("PATH", ""))
  • plugins/os-sdlc/lib/os_sdlc/tracker/forgejo_api.rb:11: ::Forgejo::Client.new(url: "https://#{HOST}", api_key: ENV.fetch("FORGEJO_TOKEN"))
  • plugins/os-sdlc/lib/os_sdlc/tracker/issue_source/github.rb:23: @client ||= Octokit::Client.new(access_token: ENV.fetch("GITHUB_TOKEN"))
  • plugins/os-sdlc/lib/os_sdlc/process_io/invocation.rb:7: { cwd: Dir.pwd, stdin: $stdin, stdout: $stdout, stderr: $stderr, env: ENV.to_h }
  • plugins/os-sdlc/lib/os_sdlc/system_model/recorder/install.rb:20: install.call(root: ENV.fetch(install::ROOT), output: ENV.fetch(install::OUTPUT)) if ENV.key?(install::OUTPUT)

Open question: the first lines of the plugin lib root files assign ENV["BUNDLE_GEMFILE"] ||=. The sites are plugins/os-adr/lib/adr.rb:3, plugins/os-backlog/lib/backlog.rb:3, plugins/os-me/lib/os_me.rb:3, and plugins/os-sdlc/lib/os_sdlc.rb:7. The ticket must rule whether the cop flags these lines or whether the cop matches them by exact value.

Cop criteria. All criteria are required.

  • The cop scopes to the whole issue, not to one site.
  • Each known site is a must-fire fixture.
  • The cop catches unseen cases.
  • The cop has zero false positives. Each false hit is made exact by value. The cop has no exclude list.

Required work:

  1. Add the cop file for ENV reads in library code — check: present LibraryEnvRead in plugins/os-sdlc/lib/os_sdlc/cops/require_inside_code/library_env_read.rb
  2. Add one must-fire fixture for each known site — check: present FORGEJO_TOKEN in plugins/os-sdlc/tests/cops/**/*.rb

Orchestrator work: Wire the cop in plugins/os-sdlc/.rubocop.yml, and add a lesson for A6.

Acceptance criteria

  1. Given library code with env: ENV as a default argument, when the cop runs, then it reports an offense.
  2. Given library code with ENV.fetch in a method body, when the cop runs, then it reports an offense.
  3. Given each of the seven known sites, when the cop runs, then it reports an offense.
  4. Given a keyword default that builds a collaborator object, when the cop runs, then it reports no offense.
  5. Given a bin/ script that reads ENV, when the cop runs, then it reports no offense.
  6. Given the ENV["BUNDLE_GEMFILE"] ||= lines, when the cop runs, then the result matches the ruling that the ticket records.

Owner sketch

class RuboCop::Cop::Sdlc::RequireInsideCode::LibraryEnvRead < RuboCop::Cop::Base
  def on_const(node)
end

Origin

  • Session: f1340f6a-3a9c-4161-984d-16e6863438c7
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/f1340f6a-3a9c-4161-984d-16e6863438c7.jsonl
  • Where: plugins/os-sdlc/lib/os_sdlc/cops/require_inside_code/load_time_state.rb
  • Root candidate: none found; searched ENV, A6, LoadTimeState, library code, outside world
    VERDICT: CREATE
## Problem No cop flags an ENV read inside a library method body. `Sdlc/RequireInsideCode/LoadTimeState` flags only load-time ENV. Principle 9 names ENV as the outside world. Only the entry script reads the outside world. Related: #1407, #1386, #1041 each fix one ENV site by hand; the cop's fixtures overlap their sites. ## Reproduce n/a: design ticket. The cop does not exist yet. ## Approach Jared ruled on 2026-11-02 that an `env: ENV` default argument is a library ENV read. Only `bin/` reads ENV and passes it in. A keyword default that builds a collaborator object stays allowed. The lens rule is A6 in `plugins/os-sdlc/skills/lens-architecture/SKILL.md`. `LoadTimeState` has `CONSTANTS = Set[:ENV, :Warning].freeze` at `plugins/os-sdlc/lib/os_sdlc/cops/require_inside_code/load_time_state.rb:8`. The cop must fire on these known sites: - `plugins/os-backlog/lib/backlog/forgejo_backend.rb:8`: `def initialize(client: nil, env: ENV)` - `plugins/os-backlog/lib/backlog/cli.rb:17`: `@env = config.fetch(:env, ENV)` - `plugins/os-backlog/lib/backlog/inspector/executable_path.rb:6`: `def initialize(path = ENV.fetch("PATH", ""))` - `plugins/os-sdlc/lib/os_sdlc/tracker/forgejo_api.rb:11`: `::Forgejo::Client.new(url: "https://#{HOST}", api_key: ENV.fetch("FORGEJO_TOKEN"))` - `plugins/os-sdlc/lib/os_sdlc/tracker/issue_source/github.rb:23`: `@client ||= Octokit::Client.new(access_token: ENV.fetch("GITHUB_TOKEN"))` - `plugins/os-sdlc/lib/os_sdlc/process_io/invocation.rb:7`: `{ cwd: Dir.pwd, stdin: $stdin, stdout: $stdout, stderr: $stderr, env: ENV.to_h }` - `plugins/os-sdlc/lib/os_sdlc/system_model/recorder/install.rb:20`: `install.call(root: ENV.fetch(install::ROOT), output: ENV.fetch(install::OUTPUT)) if ENV.key?(install::OUTPUT)` Open question: the first lines of the plugin lib root files assign `ENV["BUNDLE_GEMFILE"] ||=`. The sites are `plugins/os-adr/lib/adr.rb:3`, `plugins/os-backlog/lib/backlog.rb:3`, `plugins/os-me/lib/os_me.rb:3`, and `plugins/os-sdlc/lib/os_sdlc.rb:7`. The ticket must rule whether the cop flags these lines or whether the cop matches them by exact value. Cop criteria. All criteria are required. - The cop scopes to the whole issue, not to one site. - Each known site is a must-fire fixture. - The cop catches unseen cases. - The cop has zero false positives. Each false hit is made exact by value. The cop has no exclude list. Required work: 1. Add the cop file for ENV reads in library code — check: present `LibraryEnvRead` in `plugins/os-sdlc/lib/os_sdlc/cops/require_inside_code/library_env_read.rb` 2. Add one must-fire fixture for each known site — check: present `FORGEJO_TOKEN` in `plugins/os-sdlc/tests/cops/**/*.rb` Orchestrator work: Wire the cop in `plugins/os-sdlc/.rubocop.yml`, and add a lesson for A6. ## Acceptance criteria 1. Given library code with `env: ENV` as a default argument, when the cop runs, then it reports an offense. 2. Given library code with `ENV.fetch` in a method body, when the cop runs, then it reports an offense. 3. Given each of the seven known sites, when the cop runs, then it reports an offense. 4. Given a keyword default that builds a collaborator object, when the cop runs, then it reports no offense. 5. Given a `bin/` script that reads ENV, when the cop runs, then it reports no offense. 6. Given the `ENV["BUNDLE_GEMFILE"] ||=` lines, when the cop runs, then the result matches the ruling that the ticket records. ## Owner sketch ```ruby class RuboCop::Cop::Sdlc::RequireInsideCode::LibraryEnvRead < RuboCop::Cop::Base def on_const(node) end ``` ## Origin - Session: f1340f6a-3a9c-4161-984d-16e6863438c7 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/f1340f6a-3a9c-4161-984d-16e6863438c7.jsonl - Where: plugins/os-sdlc/lib/os_sdlc/cops/require_inside_code/load_time_state.rb - Root candidate: none found; searched ENV, A6, LoadTimeState, library code, outside world VERDICT: CREATE
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1506
No description provided.