Sdlc/Structural/SimulatedPolymorphism: carve out dispatch on core classes #1384

Closed
opened 2026-10-25 21:23:45 +00:00 by jared · 5 comments
Owner

Problem

Rewritten 2026-10-27 from docs/lint-lessons/analyses/sdlc-structural-simulated-polymorphism.md.

Sdlc/Structural/SimulatedPolymorphism flags type switches where every type is a core or gem class (for example is_a?(Hash) / is_a?(Array) in a recursive walk). The fix it prescribes needs a monkey-patch. The cop also misses case R when ConstA ... when ConstB, so a real switch on owned types (implementation/initiation.rb:19) goes unreported.

Approach

Fix by shape, in the matcher. Use no list of core class names. Rule: docs/lint-lessons/analyses/sdlc-structural-simulated-polymorphism.md step 6.

Required work:

  1. Count a type as owned when it does not resolve during the lint run, or when Module#const_source_location points inside the project root. Report an offense only when at least one type is owned.
  2. Also read case R when its when clauses hold two or more constants.
  3. The message names only the owned types as the ones that take the shared method.
  4. Change implementation/initiation.rb open_error_label so that each error class answers label, so the gate stays green.

Acceptance criteria

  1. Given the #1384 Encoder, when the cop runs, then it reports no offense.
  2. Given case value when Hash ... when String ..., when the cop runs, then it reports no offense.
  3. Given case n when Prism::DefNode ... when Prism::CallNode ..., when the cop runs, then it reports no offense.
  4. Given an is_a?(Circle) / is_a?(Square) chain with both types undefined in the lint run, when the cop runs, then it reports an offense.
  5. Given case error when OwnedErrorA ... when OwnedErrorB ..., when the cop runs, then it reports an offense.
  6. Given the cop run over plugins/, when the run ends, then it reports no offenses.
  7. Given case error when OwnedErrorA ... when OwnedErrorB ... where both types are registered for autoload (Module#autoload, as Zeitwerk does) from files inside the project root and not yet loaded, when the cop runs, then it reports an offense (same result as when they are loaded).
  8. Given the AC5 switch inside a singleton method (def self.label(error)), when the cop runs, then it reports an offense (the real site implementation/initiation.rb open_error_label is a def self. method).

Owner sketch

class SimulatedPolymorphism::OwnedType
  def initialize(source, project_root) = ...
  def owned? = ...
end
class SimulatedPolymorphism::CaseDispatch   # gains the `case R when Const` form
end

Origin

  • Trigger: the #1325 zero-false-positive audit of the Sdlc cops (docs/plans/class-length-family/sdlc-cop-audit.md)
  • Improvised this session: none
  • Chain: false positive ← matcher accepts any literal type list ← DESIGN (plugins/os-sdlc/lib/os_sdlc/cops/simulated_polymorphism.rb)
  • Root candidate: none found; searched SimulatedPolymorphism, polymorph, is_a?, type dispatch
  • Where: plugins/os-sdlc/lib/os_sdlc/cops/simulated_polymorphism.rb
  • Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl

VERDICT: CREATE

## Problem Rewritten 2026-10-27 from docs/lint-lessons/analyses/sdlc-structural-simulated-polymorphism.md. `Sdlc/Structural/SimulatedPolymorphism` flags type switches where every type is a core or gem class (for example `is_a?(Hash)` / `is_a?(Array)` in a recursive walk). The fix it prescribes needs a monkey-patch. The cop also misses `case R when ConstA ... when ConstB`, so a real switch on owned types (`implementation/initiation.rb:19`) goes unreported. ## Approach Fix by shape, in the matcher. Use no list of core class names. Rule: `docs/lint-lessons/analyses/sdlc-structural-simulated-polymorphism.md` step 6. Required work: 1. Count a type as owned when it does not resolve during the lint run, or when `Module#const_source_location` points inside the project root. Report an offense only when at least one type is owned. 2. Also read `case R` when its `when` clauses hold two or more constants. 3. The message names only the owned types as the ones that take the shared method. 4. Change `implementation/initiation.rb` `open_error_label` so that each error class answers `label`, so the gate stays green. ## Acceptance criteria 1. Given the #1384 `Encoder`, when the cop runs, then it reports no offense. 2. Given `case value when Hash ... when String ...`, when the cop runs, then it reports no offense. 3. Given `case n when Prism::DefNode ... when Prism::CallNode ...`, when the cop runs, then it reports no offense. 4. Given an `is_a?(Circle)` / `is_a?(Square)` chain with both types undefined in the lint run, when the cop runs, then it reports an offense. 5. Given `case error when OwnedErrorA ... when OwnedErrorB ...`, when the cop runs, then it reports an offense. 6. Given the cop run over `plugins/`, when the run ends, then it reports no offenses. 7. Given `case error when OwnedErrorA ... when OwnedErrorB ...` where both types are registered for autoload (Module#autoload, as Zeitwerk does) from files inside the project root and not yet loaded, when the cop runs, then it reports an offense (same result as when they are loaded). 8. Given the AC5 switch inside a singleton method (`def self.label(error)`), when the cop runs, then it reports an offense (the real site `implementation/initiation.rb` `open_error_label` is a `def self.` method). ## Owner sketch ```ruby class SimulatedPolymorphism::OwnedType def initialize(source, project_root) = ... def owned? = ... end class SimulatedPolymorphism::CaseDispatch # gains the `case R when Const` form end ``` ## Origin - Trigger: the #1325 zero-false-positive audit of the Sdlc cops (docs/plans/class-length-family/sdlc-cop-audit.md) - Improvised this session: none - Chain: false positive ← matcher accepts any literal type list ← DESIGN (plugins/os-sdlc/lib/os_sdlc/cops/simulated_polymorphism.rb) - Root candidate: none found; searched SimulatedPolymorphism, polymorph, is_a?, type dispatch - Where: plugins/os-sdlc/lib/os_sdlc/cops/simulated_polymorphism.rb - Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl VERDICT: CREATE
Author
Owner

Body rewritten 2026-10-27 from the false-positive process; analysis and lint lesson merged to main (docs/lint-lessons/analyses/, plugins/os-sdlc/lint-lessons/).

Body rewritten 2026-10-27 from the false-positive process; analysis and lint lesson merged to main (docs/lint-lessons/analyses/, plugins/os-sdlc/lint-lessons/).
Author
Owner

Work started on branch ticket-1384 (os-sdlc implement pipeline).

Work started on branch ticket-1384 (os-sdlc implement pipeline).
Author
Owner

Known limit, not filed (skeptic DROP: no observed failure): OwnedType counts an unresolved constant as owned, so a switch over two types from a gem the rubocop process does not load would fire. If such a false positive appears, file a ticket then.

Known limit, not filed (skeptic DROP: no observed failure): OwnedType counts an unresolved constant as owned, so a switch over two types from a gem the rubocop process does not load would fire. If such a false positive appears, file a ticket then.
Author
Owner

Lens challenge (implementation 203): owned_type.rb:9 reads Gem.path in a constant inside library code (P2 break; the cop is the injection point, as unlayered_constant.rb does). Skeptic DROP as a separate ticket (depth-1): fix by passing the roots from the cop into OwnedType.among. Not done in this run because the run was complete; listed as a suggested follow-up for Jared.

Lens challenge (implementation 203): owned_type.rb:9 reads Gem.path in a constant inside library code (P2 break; the cop is the injection point, as unlayered_constant.rb does). Skeptic DROP as a separate ticket (depth-1): fix by passing the roots from the cop into OwnedType.among. Not done in this run because the run was complete; listed as a suggested follow-up for Jared.
Author
Owner

Resolution

Done: SimulatedPolymorphism now reports only when at least one type is owned (unresolved, autoload path or source file outside gem/Ruby library roots, not C-defined core). Reads case R when ConstA/ConstB and def self. methods. Message names only owned types. initiation.rb open_error_label replaced by each error class answering label. Lesson YAML updated. AC7/AC8 (autoload-pending constants, def self.) added mid-run after a probe.

Evidence: Implementation 203 merge-ready; commit 1ba3ee0f. os-sdlc suite 2850 runs 0 failures (31.6s). Cop over plugins/: 0 offenses before and after (2754 files). Probe: main's initiation.rb open_error_label now fires; Hash/Array and Prism:: switches silent; OsSdlc:: autoload-pending types fire.

Follow-ups: none filed: (1) Gem.path read in OwnedType constant (P2 break) - skeptic DROP, fix-in-place suggested, see comment; (2) unloaded gem constants count as owned - skeptic DROP, known limit, see comment

## Resolution **Done:** SimulatedPolymorphism now reports only when at least one type is owned (unresolved, autoload path or source file outside gem/Ruby library roots, not C-defined core). Reads case R when ConstA/ConstB and def self. methods. Message names only owned types. initiation.rb open_error_label replaced by each error class answering label. Lesson YAML updated. AC7/AC8 (autoload-pending constants, def self.) added mid-run after a probe. **Evidence:** Implementation 203 merge-ready; commit 1ba3ee0f. os-sdlc suite 2850 runs 0 failures (31.6s). Cop over plugins/: 0 offenses before and after (2754 files). Probe: main's initiation.rb open_error_label now fires; Hash/Array and Prism:: switches silent; OsSdlc:: autoload-pending types fire. **Follow-ups:** none filed: (1) Gem.path read in OwnedType constant (P2 break) - skeptic DROP, fix-in-place suggested, see comment; (2) unloaded gem constants count as owned - skeptic DROP, known limit, see comment
jared closed this issue 2026-10-27 22:42:03 +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#1384
No description provided.