Make the os-sdlc suite pass without bundler and stop ProcessStartGuard hiding assertion failures #1669

Closed
opened 2026-11-10 14:54:25 +00:00 by jared · 4 comments
Owner

Problem

The os-sdlc cop tests depend on the directory that the suite runs from. The takedown green-suite step runs ruby plugins/os-sdlc/test/all.rb from the cc-os root, and 12 cop tests fail there. From /tmp, 23 cop tests fail. From plugins/os-sdlc, all pass. A second fault hides the real failure: Minitest probes for gdiff when assert_equal fails, and ProcessStartGuard raises on that probe. So each failure reads as a process-start error, not as an assertion failure.

Correction (2026-11-10): the first body named RuboCop 1.92.2 and bundler as the cause. That is wrong. lib/os_sdlc.rb sets BUNDLE_GEMFILE and requires bundler/setup, so every run loads RuboCop 1.90.0. The cause is the working directory. CopSource.parse(source, path) asks RuboCop::ConfigStore#for_file(path || Dir.pwd) for the Ruby version. For a path with no .rubocop.yml above it, RuboCop takes the version from the working directory: 4.0 from plugins/os-sdlc/Gemfile.lock, the 3.4 of the cc-os root .rubocop.yml for no-path calls from the root, or the 2.7 default elsewhere. Ruby 2.7 cannot parse an endless def, so the AST is nil and the cop reports nothing.

Observed

cd /home/jared/dev/cc-os && ruby plugins/os-sdlc/test/all.rb
ProcessStartGuard::ProcessStarted: a test started the program "gdiff"; pass a recorded or fake adapter through ProcessIO instead
    plugins/os-sdlc/test/test_helper.rb:48:in 'ProcessStartGuard.check!'

With Minitest's diff replaced by inspect, process_start_outside_adapter_test.rb:162 shows the real failure: EXPECTED: ["Move this process start into an adapter: ..."], ACTUAL: [].
Probe from the cc-os root: CopSource.target_version("/x/lib/acme/subject.rb") gives 2.7 (TargetRuby::Default); parse("module T\n def self.f = 1\nend\n") at 2.7 gives valid=false, ast=nil, unexpected token tEQL. From plugins/os-sdlc it gives 4.0 (TargetRuby::BundlerLockFile) and a valid AST.
Failing from the cc-os root: AdapterBuiltOutsideEntryTest (5), ProcessStartOutsideAdapterTest (7). Also failing from /tmp: DomainEffectInEdge, PrecedenceInEdge, RepeatedPathLiteral (8), RequireInsideCode.

Reproduce

cd /home/jared/dev/cc-os && ruby plugins/os-sdlc/test/all.rb -n '/beside_an_unrelated_public_method_is_one_offense/'
cd /tmp && ruby /home/jared/dev/cc-os/plugins/os-sdlc/test/all.rb

Approach

A cop test states the Ruby version of its fixture source. It does not take the version from the working directory. CopSource.parse gets the same optional version argument that CopSource.from_file already has. The test support passes one fixed version, read once from the plugin's own config. A tmpdir fixture that models a project on disk declares its Ruby version in its own .rubocop.yml, as a real linted project does. Minitest's external diff is turned off, so a failed assert_equal prints Expected:/Actual: with inspect and starts no process. ProcessStartGuard stays strict (ruling 88).

class CopSource
  def self.parse(source, path = nil, version = target_version(path)) = RuboCop::ProcessedSource.new(source, version, path)
end

module CopHelpers
  SOURCE_PATH = File.expand_path("../../source.rb", __dir__)
  RUBY_VERSION = RuboCop::Cop::Sdlc::Structural::CopSource.target_version(SOURCE_PATH)
end

Required work:

  1. CopSource.parse takes an optional third version argument that defaults to target_version(path) — check: present def self.parse(source, path = nil, version = target_version(path)) in plugins/os-sdlc/lib/os_sdlc/cops/structural/cop_source.rb
  2. CopHelpers holds the fixed fixture version and passes it from offenses_for and autocorrected_source_for — check: present CopSource.target_version(SOURCE_PATH) in plugins/os-sdlc/test/support/cop_helpers.rb
  3. test/support/rescue_source.rb and every direct CopSource.parse call in test/ pass the fixed version: reach_through_chain_test.rb:174, rescue_site_test.rb:145, detector/recording_cop_test.rb:18, unlayered_constant_test.rb:83, inward_dependency_test.rb:255, feature_envy_test.rb:162, instance_variable_assumption_test.rb:333, primitive_obsessed_ivar_test.rb:135, require_relative_density_test.rb:74, unused_private_method_test.rb:208, unreachable_branch_after_guard_test.rb:265. Leave cop_source_test.rb calls as they are; they test the default.
  4. Each tmpdir fixture whose files a cop reads through CopSource.from_file writes .rubocop.yml with AllCops: TargetRubyVersion: 3.4 in its project root: adapter_built_outside_entry_test.rb setup, and unused_private_method_test.rb:147-151 if its fixtures fail from /tmp — check: present TargetRubyVersion in plugins/os-sdlc/test/cops/adapter_built_outside_entry_test.rb
  5. test/test_helper.rb sets Minitest::Assertions.diff = nil after require "minitest/autorun". This is a permitted comment site: one line may say that it stops Minitest from starting diff/gdiff (ruling 88). — check: present Minitest::Assertions.diff = nil in plugins/os-sdlc/test/test_helper.rb

Rejected: run the suite under bundle exec or cd into the plugin in .sdlc/project.yaml — bundler is already active through lib/os_sdlc.rb, and a cd hides the fault; the cops lint other projects from other directories.
Rejected: let ProcessStartGuard allow diff/gdiff — Minitest then runs diff -u through backticks, a real process start (ruling 88).
Rejected: fix CopSource.from_file in production to use the running cop's config — open ticket #1226 owns that fault.

Acceptance criteria

  1. Given main with this change, when ruby plugins/os-sdlc/test/all.rb runs from /home/jared/dev/cc-os, then it reports 0 failures and 0 errors.
  2. Given main with this change, when ruby /home/jared/dev/cc-os/plugins/os-sdlc/test/all.rb runs from /tmp, then it reports 0 failures and 0 errors.
  3. Given main with this change, when bundle exec ruby -Itest test/all.rb runs in plugins/os-sdlc, then it reports 0 failures and 0 errors.
  4. Given a source with an endless def and a path with no .rubocop.yml above it, when CopSource.parse(source, path, 3.4) runs, then the processed source is valid and has an AST.
  5. Given a test whose assert_equal fails, when the suite reports it, then the message shows Expected: and Actual: with both values, and no ProcessStartGuard::ProcessStarted error appears.
  6. Given a test that calls system("true"), when it runs, then ProcessStartGuard::ProcessStarted is raised and no process starts.

Owner sketch

class RuboCop::Cop::Sdlc::Structural::CopSource
  def self.parse(source, path = nil, version = target_version(path))
end

module CopHelpers
  RUBY_VERSION = CopSource.target_version(SOURCE_PATH)
  def offenses_for(cop_class, source, path: SOURCE_PATH, config: nil)
  def autocorrected_source_for(cop_class, source, path: SOURCE_PATH)
end

Non-goals

  • CopSource.from_file and target_version in production still use a fresh ConfigStore and the working directory. #1226 owns that.
  • The suite command in .sdlc/project.yaml:27 does not change.

Origin

  • Trigger: takedown-run --live 1668, step 19-green-suite
  • Improvised this session: none; the #1668 takedown is not rerun
  • Chain: takedown green-suite red on main ← 12 cop tests fail from the cc-os root ← fixture paths have no .rubocop.yml above them, so RuboCop picks Ruby 2.7 from the working directory and endless def does not parse (DESIGN: plugins/os-sdlc/lib/os_sdlc/cops/structural/cop_source.rb:12, test/support/cop_helpers.rb:7); and: real failure hidden ← Minitest diff probes gdiff ← ProcessStartGuard raises (DESIGN: plugins/os-sdlc/test/test_helper.rb:48)
  • Root candidate: none found; searched ProcessStartGuard, gdiff, bundler, bundle exec, rubocop 1.92, process_start_outside_adapter
  • Where: plugins/os-sdlc/lib/os_sdlc/cops/structural/cop_source.rb; plugins/os-sdlc/test/support/cop_helpers.rb; plugins/os-sdlc/test/test_helper.rb
  • Session: b9b5b401-b4bc-427d-b709-3fdef69fff5c (filed); 3f3909e3-ad19-4d05-8792-564a9aa15708 (diagnosis and decomposition)
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os-plugins-os-sdlc/b9b5b401-b4bc-427d-b709-3fdef69fff5c.jsonl

VERDICT: CREATE (ticket-skeptic, 2026-11-10): no open ticket has the same mechanism and fault.

## Problem The os-sdlc cop tests depend on the directory that the suite runs from. The takedown `green-suite` step runs `ruby plugins/os-sdlc/test/all.rb` from the cc-os root, and 12 cop tests fail there. From `/tmp`, 23 cop tests fail. From `plugins/os-sdlc`, all pass. A second fault hides the real failure: Minitest probes for `gdiff` when `assert_equal` fails, and `ProcessStartGuard` raises on that probe. So each failure reads as a process-start error, not as an assertion failure. Correction (2026-11-10): the first body named RuboCop 1.92.2 and bundler as the cause. That is wrong. `lib/os_sdlc.rb` sets `BUNDLE_GEMFILE` and requires `bundler/setup`, so every run loads RuboCop 1.90.0. The cause is the working directory. `CopSource.parse(source, path)` asks `RuboCop::ConfigStore#for_file(path || Dir.pwd)` for the Ruby version. For a path with no `.rubocop.yml` above it, RuboCop takes the version from the working directory: 4.0 from `plugins/os-sdlc/Gemfile.lock`, the 3.4 of the cc-os root `.rubocop.yml` for no-path calls from the root, or the 2.7 default elsewhere. Ruby 2.7 cannot parse an endless `def`, so the AST is nil and the cop reports nothing. ## Observed ``` cd /home/jared/dev/cc-os && ruby plugins/os-sdlc/test/all.rb ``` ``` ProcessStartGuard::ProcessStarted: a test started the program "gdiff"; pass a recorded or fake adapter through ProcessIO instead plugins/os-sdlc/test/test_helper.rb:48:in 'ProcessStartGuard.check!' ``` With Minitest's diff replaced by `inspect`, `process_start_outside_adapter_test.rb:162` shows the real failure: `EXPECTED: ["Move this process start into an adapter: ..."]`, `ACTUAL: []`. Probe from the cc-os root: `CopSource.target_version("/x/lib/acme/subject.rb")` gives 2.7 (`TargetRuby::Default`); `parse("module T\n def self.f = 1\nend\n")` at 2.7 gives `valid=false`, `ast=nil`, `unexpected token tEQL`. From `plugins/os-sdlc` it gives 4.0 (`TargetRuby::BundlerLockFile`) and a valid AST. Failing from the cc-os root: `AdapterBuiltOutsideEntryTest` (5), `ProcessStartOutsideAdapterTest` (7). Also failing from `/tmp`: `DomainEffectInEdge`, `PrecedenceInEdge`, `RepeatedPathLiteral` (8), `RequireInsideCode`. ## Reproduce ``` cd /home/jared/dev/cc-os && ruby plugins/os-sdlc/test/all.rb -n '/beside_an_unrelated_public_method_is_one_offense/' cd /tmp && ruby /home/jared/dev/cc-os/plugins/os-sdlc/test/all.rb ``` ## Approach A cop test states the Ruby version of its fixture source. It does not take the version from the working directory. `CopSource.parse` gets the same optional `version` argument that `CopSource.from_file` already has. The test support passes one fixed version, read once from the plugin's own config. A tmpdir fixture that models a project on disk declares its Ruby version in its own `.rubocop.yml`, as a real linted project does. Minitest's external diff is turned off, so a failed `assert_equal` prints `Expected:`/`Actual:` with `inspect` and starts no process. `ProcessStartGuard` stays strict (ruling 88). ```ruby class CopSource def self.parse(source, path = nil, version = target_version(path)) = RuboCop::ProcessedSource.new(source, version, path) end module CopHelpers SOURCE_PATH = File.expand_path("../../source.rb", __dir__) RUBY_VERSION = RuboCop::Cop::Sdlc::Structural::CopSource.target_version(SOURCE_PATH) end ``` Required work: 1. `CopSource.parse` takes an optional third `version` argument that defaults to `target_version(path)` — check: present `def self.parse(source, path = nil, version = target_version(path))` in `plugins/os-sdlc/lib/os_sdlc/cops/structural/cop_source.rb` 2. `CopHelpers` holds the fixed fixture version and passes it from `offenses_for` and `autocorrected_source_for` — check: present `CopSource.target_version(SOURCE_PATH)` in `plugins/os-sdlc/test/support/cop_helpers.rb` 3. `test/support/rescue_source.rb` and every direct `CopSource.parse` call in `test/` pass the fixed version: `reach_through_chain_test.rb:174`, `rescue_site_test.rb:145`, `detector/recording_cop_test.rb:18`, `unlayered_constant_test.rb:83`, `inward_dependency_test.rb:255`, `feature_envy_test.rb:162`, `instance_variable_assumption_test.rb:333`, `primitive_obsessed_ivar_test.rb:135`, `require_relative_density_test.rb:74`, `unused_private_method_test.rb:208`, `unreachable_branch_after_guard_test.rb:265`. Leave `cop_source_test.rb` calls as they are; they test the default. 4. Each tmpdir fixture whose files a cop reads through `CopSource.from_file` writes `.rubocop.yml` with `AllCops: TargetRubyVersion: 3.4` in its project root: `adapter_built_outside_entry_test.rb` setup, and `unused_private_method_test.rb:147-151` if its fixtures fail from `/tmp` — check: present `TargetRubyVersion` in `plugins/os-sdlc/test/cops/adapter_built_outside_entry_test.rb` 5. `test/test_helper.rb` sets `Minitest::Assertions.diff = nil` after `require "minitest/autorun"`. This is a permitted comment site: one line may say that it stops Minitest from starting `diff`/`gdiff` (ruling 88). — check: present `Minitest::Assertions.diff = nil` in `plugins/os-sdlc/test/test_helper.rb` Rejected: run the suite under `bundle exec` or `cd` into the plugin in `.sdlc/project.yaml` — bundler is already active through `lib/os_sdlc.rb`, and a `cd` hides the fault; the cops lint other projects from other directories. Rejected: let `ProcessStartGuard` allow `diff`/`gdiff` — Minitest then runs `diff -u` through backticks, a real process start (ruling 88). Rejected: fix `CopSource.from_file` in production to use the running cop's config — open ticket #1226 owns that fault. ## Acceptance criteria 1. Given main with this change, when `ruby plugins/os-sdlc/test/all.rb` runs from `/home/jared/dev/cc-os`, then it reports 0 failures and 0 errors. 2. Given main with this change, when `ruby /home/jared/dev/cc-os/plugins/os-sdlc/test/all.rb` runs from `/tmp`, then it reports 0 failures and 0 errors. 3. Given main with this change, when `bundle exec ruby -Itest test/all.rb` runs in `plugins/os-sdlc`, then it reports 0 failures and 0 errors. 4. Given a source with an endless `def` and a path with no `.rubocop.yml` above it, when `CopSource.parse(source, path, 3.4)` runs, then the processed source is valid and has an AST. 5. Given a test whose `assert_equal` fails, when the suite reports it, then the message shows `Expected:` and `Actual:` with both values, and no `ProcessStartGuard::ProcessStarted` error appears. 6. Given a test that calls `system("true")`, when it runs, then `ProcessStartGuard::ProcessStarted` is raised and no process starts. ## Owner sketch ```ruby class RuboCop::Cop::Sdlc::Structural::CopSource def self.parse(source, path = nil, version = target_version(path)) end module CopHelpers RUBY_VERSION = CopSource.target_version(SOURCE_PATH) def offenses_for(cop_class, source, path: SOURCE_PATH, config: nil) def autocorrected_source_for(cop_class, source, path: SOURCE_PATH) end ``` ## Non-goals - `CopSource.from_file` and `target_version` in production still use a fresh `ConfigStore` and the working directory. #1226 owns that. - The suite command in `.sdlc/project.yaml:27` does not change. ## Origin - Trigger: `takedown-run --live 1668`, step `19-green-suite` - Improvised this session: none; the #1668 takedown is not rerun - Chain: takedown green-suite red on main ← 12 cop tests fail from the cc-os root ← fixture paths have no `.rubocop.yml` above them, so RuboCop picks Ruby 2.7 from the working directory and endless `def` does not parse (DESIGN: `plugins/os-sdlc/lib/os_sdlc/cops/structural/cop_source.rb:12`, `test/support/cop_helpers.rb:7`); and: real failure hidden ← Minitest `diff` probes `gdiff` ← `ProcessStartGuard` raises (DESIGN: `plugins/os-sdlc/test/test_helper.rb:48`) - Root candidate: none found; searched ProcessStartGuard, gdiff, bundler, bundle exec, rubocop 1.92, process_start_outside_adapter - Where: `plugins/os-sdlc/lib/os_sdlc/cops/structural/cop_source.rb`; `plugins/os-sdlc/test/support/cop_helpers.rb`; `plugins/os-sdlc/test/test_helper.rb` - Session: b9b5b401-b4bc-427d-b709-3fdef69fff5c (filed); 3f3909e3-ad19-4d05-8792-564a9aa15708 (diagnosis and decomposition) - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os-plugins-os-sdlc/b9b5b401-b4bc-427d-b709-3fdef69fff5c.jsonl VERDICT: CREATE (ticket-skeptic, 2026-11-10): no open ticket has the same mechanism and fault.
Author
Owner

Follow-up dropped by ticket-skeptic: the production version risk in CopSource.from_file/target_version is a depth-1 follow-up of this AI-created ticket, and open #1226 already covers it. #1669 fixes only the test fixtures and the Minitest diff probe.

Follow-up dropped by ticket-skeptic: the production version risk in CopSource.from_file/target_version is a depth-1 follow-up of this AI-created ticket, and open #1226 already covers it. #1669 fixes only the test fixtures and the Minitest diff probe.
Author
Owner

Occurrence: A failed assert_equal shows a process-guard error, because Minitest starts gdiff to print the difference
Seen: plugins/os-sdlc/test/test_helper.rb
Run: Session 720a7477-bb8e-4373-8d74-6bbdb56f0200 (#1665, branch ticket-1665)
Adds: two agents hit it on 2026-11-10. One read the guard error as a production defect (it reported that Boot::Inputs.collaborator_pass starts gdiff, which is false). The other changed its assertions to compare two fields in place of two full payloads.

Occurrence: A failed assert_equal shows a process-guard error, because Minitest starts gdiff to print the difference Seen: plugins/os-sdlc/test/test_helper.rb Run: Session 720a7477-bb8e-4373-8d74-6bbdb56f0200 (#1665, branch ticket-1665) Adds: two agents hit it on 2026-11-10. One read the guard error as a production defect (it reported that Boot::Inputs.collaborator_pass starts gdiff, which is false). The other changed its assertions to compare two fields in place of two full payloads.
Author
Owner

Work started on branch ticket-1669 (os-sdlc implement pipeline, map poodr-ticket-implementation).

Work started on branch ticket-1669 (os-sdlc implement pipeline, map poodr-ticket-implementation).
Author
Owner

Resolution

Done: CopSource.parse takes an optional version argument (default unchanged). Cop test support and every direct CopSource.parse call in test/ pass a fixed version read from the plugin config; the adapter_built_outside_entry tmpdir fixture declares TargetRubyVersion in its own .rubocop.yml. test_helper sets Minitest::Assertions.diff = nil, so a failed assert_equal prints Expected/Actual and starts no process; ProcessStartGuard stays strict.

Evidence: Implementation 268, commit 879905543 on ticket-1669. Suite 3894 runs, 0 failures, 0 errors from the cc-os root, from /tmp, and under bundle exec in plugins/os-sdlc. Gates: required-work, suite, survivors, behavior-verifier, contract-auditor all pass.

Follow-ups: CopSource.from_file production version choice: existing #1226 (evidence added 2026-11-10). Rerun /os-sdlc:ticket-takedown 1668, which this unblocks: tracked on #1668.

## Resolution **Done:** CopSource.parse takes an optional version argument (default unchanged). Cop test support and every direct CopSource.parse call in test/ pass a fixed version read from the plugin config; the adapter_built_outside_entry tmpdir fixture declares TargetRubyVersion in its own .rubocop.yml. test_helper sets Minitest::Assertions.diff = nil, so a failed assert_equal prints Expected/Actual and starts no process; ProcessStartGuard stays strict. **Evidence:** Implementation 268, commit 879905543 on ticket-1669. Suite 3894 runs, 0 failures, 0 errors from the cc-os root, from /tmp, and under bundle exec in plugins/os-sdlc. Gates: required-work, suite, survivors, behavior-verifier, contract-auditor all pass. **Follow-ups:** CopSource.from_file production version choice: existing #1226 (evidence added 2026-11-10). Rerun /os-sdlc:ticket-takedown 1668, which this unblocks: tracked on #1668. <!-- os-backlog kind: resolution -->
jared closed this issue 2026-11-10 15:32:17 +00:00
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#1669
No description provided.