os-sdlc cop Sdlc/Structural/DomainOperandInEdge: an edge compares with our literal, a limit it computes, or a second-source set (A19 R2) #1510

Closed
opened 2026-11-02 21:49:06 +00:00 by jared · 2 comments
Owner

Problem

An edge object (an adapter or an entry) compares a value with an operand of our rules: a literal of our vocabulary ('holdout', a cadence EVERY), a limit that this class computes (== longest), or a set read from a second source (judged.include?(id)). The comparison is a domain rule. Because it is inside the adapter, its test needs the resource. The literal at tracker_lookup.rb:69 (host == "github.com") is different: it is the resource's own code, so it is translation.

Evidence: docs/system-design-format/proof/2026-11-02-smell-analysis-decision-in-adapter.md (rule R2, section 7, cop O) and docs/system-design-format/proof/2026-11-02-detector-a19/facts/rule-v3-notes.md (clauses O1, O2, O3). On 147 labeled sites, the v3 definition of O fires on 7 TRUE sites and 0 FALSE sites.

No cop finds this shape. Sdlc/Structural/EffectInModel is disabled in this repo because no LedgerPath is set (#1507).

This cop is the second of three. It needs the shared objects from the P ticket (Sdlc/Structural/PrecedenceInEdge: EdgeScope, EffectReach). Build P first.

Design

/home/jared/dev/cc-os/docs/takedown/1510-20261102-175443/final-design.json

Approach

Add the RecordingCop Sdlc/Structural/DomainOperandInEdge, which uses the shared EdgeScope for role and judges each comparison by where its operands come from: a caller-given parameter against our literal, a block value against a local that holds a second source, or a class-level numeric limit.

Required work:

  1. Write plugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge.rb
  2. Write plugins/os-sdlc/lint-lessons/sdlc-structural-domain-operand-in-edge.yml
  3. Write plugins/os-sdlc/.rubocop.yml
  4. Write plugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge/limit.rb
  5. Write plugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge/cadence.rb
  6. Write plugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge/verdict.rb
  7. Write test plugins/os-sdlc/tests/cops/domain_operand_in_edge_test.rb

Orchestrator work: Whole-tree run: run the cop alone with the os-sdlc config over every Ruby file under plugins/, including bin/ scripts and the os-me and os-backlog plugins; put a table in the PR with one row for each hit (site, clause, TRUE or FALSE, and the reason by the swap test); the run must have zero false hits; for each false hit, either make the value rule tighter and add the hit as a must-pass fixture, or stop and file a ticket with /os-backlog:capture. Ruling 2026-11-02: the design accepts a narrower rule than the original ticket (O1 tests parameters only; Cadence takes numeric class constants only; no O3 caller-collection test). List each R2 TRUE site from docs/system-design-format/proof/2026-11-02-detector-a19/facts/rule-v3-notes.md that the real-tree run does not report, with the fact that blocks it.

Acceptance criteria

  • unless split == "holdout", split is a param, adapter: fires
  • return X if kind == :forgejo: pass (jump branch)
  • name == :require ? a : b: pass (ternary)
  • case param when :a in an adapter: fires
  • status.exitstatus == 0 on a read value: pass (not param-rooted)
  • hits.select { \: h\| h[:l] == longest }, longest a local | fires
  • judged.include?(row[..]) with judged a local: fires
  • row.ruled_by?(closed) with closed a local: fires
  • known.add?(r.id) or lineage.error?(d): pass
  • (total % EVERY).zero? with EVERY = 5 in the class: fires
  • Same code in a class with no effect call: pass (no role)
  • Entry, param-rooted argv compare: pass (entry input)
  • Entry with a local compare in a block: fires

Owner sketch

Responsibility Owner
Is this node inside an edge (adapter or entry)? EdgeScope#role (existing, shared with P)
Report with the exact message DomainOperandInEdge (RecordingCop)
Param-origin value compared with our literal as a verdict (if/unless/case) DomainOperandInEdge::Verdict
A block value compared with a local held from a second source DomainOperandInEdge::Limit
A class numeric constant used as the limit of a comparison DomainOperandInEdge::Cadence
Lesson text lint-lessons/sdlc-structural-domain-operand-in-edge.yml (shares why, decision opening and correction with P)
Default config plugins/os-sdlc/.rubocop.yml

plugins/os-sdlc/tests/cops/domain_operand_in_edge_test.rb

# frozen_string_literal: true

require_relative "../test_helper"
require_relative "../support/cop_helpers"

class DomainOperandInEdgeTest < Minitest::Test
  include CopHelpers

  PLUGIN_ROOT = File.expand_path("../..", __dir__)
  COP_NAME = "Sdlc/Structural/DomainOperandInEdge"
  COP_FILE = File.join(PLUGIN_ROOT, "lib", "os_sdlc", "cops", "structural", "domain_operand_in_edge.rb")
  LESSON_FILE = File.join(PLUGIN_ROOT, "lint-lessons", "sdlc-structural-domain-operand-in-edge.yml")
  PRECEDENCE_LESSON_FILE = File.join(PLUGIN_ROOT, "lint-lessons", "sdlc-structural-precedence-in-edge.yml")
  ENTRY_PATH = "bin/tool"
  MESSAGE = "An edge compares a value with an operand of our rules: our literal, a limit computed here, " \
            "or a set from a second source. Return the values it read; a decision object or the value " \
            "that owns the data compares."

  def test_cop_is_registered_from_its_file_as_a_recording_cop
    cop = RuboCop::Cop::Sdlc::Structural::DomainOperandInEdge

    assert_equal RuboCop::Cop::Sdlc::RecordingCop, cop.superclass
    assert_equal COP_FILE, Object.const_source_location(cop.name).first
    assert_includes RuboCop::Cop::Registry.global.map(&:cop_name), COP_NAME
  end

  def test_default_config_enables_the_cop_with_no_exclude_key
    entry = YAML.load_file(File.join(PLUGIN_ROOT, ".rubocop.yml"), aliases: true).fetch(COP_NAME)

    assert_same true, entry["Enabled"]
    refute_includes entry.keys, "Exclude"
  end

  def test_lesson_key_names_the_cop
    assert_equal COP_NAME, lesson["key"]
  end

  def test_lesson_shares_the_why_with_the_precedence_lesson
    assert_equal precedence_lesson["why"], lesson["why"]
  end

  def test_lesson_shares_the_correction_with_the_precedence_lesson
    assert_equal precedence_lesson["correction"], lesson["correction"]
  end

  def test_lesson_shares_the_first_decision_sentences_with_the_precedence_lesson
    assert_equal sentences(precedence_lesson["decision"]).first(2), sentences(lesson["decision"]).first(2)
  end

  def test_lesson_example_before_fires_and_after_passes
    example = YAML.load_file(LESSON_FILE).fetch("example")

    assert_fires example.fetch("before")
    assert_passes example.fetch("after")
  end

  def test_fire_1_literal_refusal_on_a_given_split
    assert_fires adapter(<<~'RUBY')
      def read(split)
        labels = parse
        refuse(labels, split) unless split == "holdout"
        labels.select { |label| label.split == split }
      end
      def refuse(labels, split) = (raise Error, "held-out row in #{split}" if labels.any?)
    RUBY
  end

  def test_fire_2_cadence_constant
    assert_fires adapter(<<~'RUBY')
      EVERY = 5
      TEXT = "Tip"
      def count(id)
        total = sighted(id) + 1
        File.write(path(id), total.to_s)
        TEXT if (total % EVERY).zero?
      end
    RUBY
  end

  def test_fire_3_case_on_a_given_classification
    assert_fires adapter(<<~'RUBY')
      def verdict(classification, output)
        case classification
        when :red then Result.new(status: 0, lines: output.lines)
        when :unexpected_pass then failure("survived")
        else failure("broken")
        end
      end
      def failure(line) = Result.new(status: 1, lines: [line])
    RUBY
  end

  def test_fire_4_computed_longest
    assert_fires adapter(<<~'RUBY')
      def longest_files(hits)
        longest = hits.map { |hit| hit[:length] }.max
        hits.select { |hit| hit[:length] == longest }.map { |hit| hit[:file] }
      end
    RUBY
  end

  def test_fire_5_rows_ruled_by_closed_subjects
    assert_fires adapter(<<~'RUBY')
      def unclosed(name)
        closed = keep_subjects(name)
        candidates(name).reject { |row| row.ruled_by?(closed) }
      end
    RUBY
  end

  def test_fire_6_judged_set_from_second_source
    assert_fires adapter(<<~'RUBY')
      def unread
        judged = rows.filter_map { |row| row["session_id"] if row["verdict"] }
        asked_rows.reject { |row| judged.include?(row["session_id"]) }
      end
    RUBY
  end

  def test_fire_7_mtime_against_seen_cursor
    assert_fires adapter(<<~'RUBY')
      def unread(paths)
        present = paths.select { |path| File.exist?(path) }
        seen = migrated(present)
        present.select { |path| File.mtime(path).to_f > seen.fetch(session_id(path), 0) }
      end
    RUBY
  end

  def test_pass_1_factory_return_on_kind
    assert_passes adapter(<<~'RUBY')
      def repo_issues(kind, repo)
        return ForgejoIssues.new(repo) if kind == :forgejo
        GhIssues.new(repo)
      end
    RUBY
  end

  def test_pass_2_entry_tier_from_a_called_object
    assert_passes <<~'RUBY', path: ENTRY_PATH
      issues.each do |issue|
        tier = Wakeup.tier(issue["labels"])
        next if tier == :skip
        if tier == :notify
          File.open(LOG, "a") { |f| f.puts(issue["title"]) }
        end
      end
    RUBY
  end

  def test_pass_3_ternary_selection
    assert_passes adapter(<<~'RUBY')
      def target(name, arg)
        base = name == :require ? test_dir : File.dirname(@file)
        File.join(base, arg)
      end
    RUBY
  end

  def test_pass_4_exit_status_of_a_read_value
    assert_passes adapter(<<~'RUBY')
      def run(cmd)
        _o, status = Open3.capture2(cmd)
        refuse("failed") unless status.exitstatus == 0
      end
      def refuse(line) = raise(Error, line)
    RUBY
  end

  def test_pass_5_header_read_from_the_file
    assert_passes adapter(<<~'RUBY')
      def problems(path)
        actual = File.open(path, &:gets).to_s.chomp.split("\t")
        return [] if actual == COLUMNS
        [{ "problem" => "header differs" }]
      end
    RUBY
  end

  def test_pass_6_file_name_against_a_string_constant
    assert_passes adapter(<<~'RUBY')
      SCORE_FILE = "score"
      def names(glob) = Dir.glob(glob).map { |f| File.basename(f, ".tsv") }.reject { |name| name == SCORE_FILE }
    RUBY
  end

  def test_pass_7_next_unless_status_missing
    assert_passes adapter(<<~'RUBY')
      def each_resolved(items)
        items.each do |item|
          resolution = resolve(item)
          next resolution unless resolution.status == :missing
          remember(item)
        end
      end
    RUBY
  end

  def test_pass_8_literal_list_membership
    assert_passes adapter(<<~'RUBY')
      def kinds(info)
        return none unless info && %i[forgejo github].include?(info[:kind])
        info[:kind]
      end
    RUBY
  end

  def test_pass_9_heading_from_an_instance_variable
    assert_passes adapter(<<~'RUBY')
      def in_history?
        in_history = false
        File.foreach(@file) { |line| in_history = line.chomp == @heading if line.start_with?("## ") }
        in_history
      end
    RUBY
  end

  def test_pass_10_row_constant_against_a_given_constant
    assert_passes adapter("def join_row_for(constant) = join_rows.find { |row| row.constant == constant }")
  end

  def test_pass_11_signals_from_a_lesson_source
    assert_passes adapter(<<~'RUBY')
      def typed
        signals.select { |s| s.source == Lessons.source("params", "initialize") && s.kind == "type" }
      end
    RUBY
  end

  def test_pass_12_entry_rescue_re_raise
    assert_passes <<~'RUBY', path: ENTRY_PATH
      begin
        run
      rescue StandardError => e
        raise unless e.message.include?("thor")
      end
    RUBY
  end

  def test_pass_13_path_text_predicates
    assert_passes adapter(<<~'RUBY')
      def kinds(path, scripts, opts, params)
        [path.end_with?("_test.rb"), scripts.reject { |p| p.include?("/hooks/") },
         opts.reject { |o| o.end_with?("bundler/setup") }, params.select { |k, _| k.end_with?("Path") }]
      end
    RUBY
  end

  def test_pass_14_stamp_size_literal
    assert_passes adapter(<<~'RUBY')
      def stamp
        stamp = @options.slice(:row_id, :tier)
        raise Error, "--row-id needs --tier" if stamp[:row_id] && stamp.size < 3
        stamp
      end
    RUBY
  end

  def test_pass_15_set_add_dedup
    assert_passes adapter(<<~'RUBY')
      def append(incoming)
        known = known_ids
        fresh = incoming.select { |r| known.add?(r.id) }
        File.open(@path, "a") { |f| fresh.each { |r| f.puts(r.id) } }
      end
    RUBY
  end

  def test_pass_16_prefix_from_the_block
    assert_passes adapter(<<~'RUBY')
      def kept(files) = @exclude.reject { |prefix| files.any? { |p| p.start_with?(prefix) } }
      def excluded?(path) = @exclude.any? { |prefix| path.start_with?(prefix) }
    RUBY
  end

  def test_pass_17_block_value_includes_a_given_path
    assert_passes adapter("def count(path) = targets.count { |required| required.include?(path) }")
  end

  def test_pass_18_object_answers_the_question
    assert_passes adapter(<<~'RUBY')
      def errors(classes)
        lineage = Lineage.new
        classes.select { |d| lineage.error?(d) }
      end
      def sent(names)
        index = Index.new
        names.select { |n| index.sent?(n) }
      end
    RUBY
  end

  def test_pass_19_tally_fetch_default
    assert_passes adapter(<<~'RUBY')
      def surplus(left, right)
        known = right.tally
        left.tally.flat_map { |e, n| [e] * [n - known.fetch(e, 0), 0].max }
      end
    RUBY
  end

  def test_pass_20_fetch_with_default
    assert_passes adapter("def ids(rulings) = rulings.flat_map { |cells| governed.fetch(cells[\"id\"], []) }")
  end

  def test_pass_21_skipped_segments
    assert_passes adapter(<<~'RUBY')
      def files(dir) = raw(dir).reject { |p| excluded?(p) }
      def excluded?(p) = p.split("/").any? { |s| SKIPPED.include?(s) }
    RUBY
  end

  def test_pass_22_interpolated_prefix
    assert_passes adapter(<<~'RUBY')
      def plugin_entries = entries.select { |e| under_plugin?(e) }
      def under_plugin?(e) = e.start_with?("plugins/#{@plugin}/")
    RUBY
  end

  def test_pass_23_pure_class_without_effect_call
    assert_passes <<~'RUBY'
      class Labels
        def read(split)
          labels = parse
          refuse(labels, split) unless split == "holdout"
          labels.select { |label| label.split == split }
        end
        def refuse(labels, split) = (raise Error, "held-out row in #{split}" if labels.any?)
      end
    RUBY
  end

  def test_pass_24_entry_input_ternary
    assert_passes <<~'RUBY', path: ENTRY_PATH
      def call(argv) = argv.first == "--help" ? usage : run(argv)
    RUBY
  end

  private

  def sentences(text) = text.split(/(?<=\.) /)

  def lesson = YAML.load_file(LESSON_FILE)

  def precedence_lesson = YAML.load_file(PRECEDENCE_LESSON_FILE)

  def adapter(body)
    "class Tool\n  def raw = File.read(@path)\n\n#{"#{body.chomp}\n".gsub(/^/, '  ')}end\n"
  end

  def assert_fires(source, path: CopHelpers::SOURCE_PATH)
    assert_equal [MESSAGE], offenses_for(RuboCop::Cop::Sdlc::Structural::DomainOperandInEdge, source, path: path)
  end

  def assert_passes(source, path: CopHelpers::SOURCE_PATH)
    assert_empty offenses_for(RuboCop::Cop::Sdlc::Structural::DomainOperandInEdge, source, path: path)
  end
end
## Problem An edge object (an adapter or an entry) compares a value with an operand of our rules: a literal of our vocabulary (`'holdout'`, a cadence `EVERY`), a limit that this class computes (`== longest`), or a set read from a second source (`judged.include?(id)`). The comparison is a domain rule. Because it is inside the adapter, its test needs the resource. The literal at `tracker_lookup.rb:69` (`host == "github.com"`) is different: it is the resource's own code, so it is translation. Evidence: `docs/system-design-format/proof/2026-11-02-smell-analysis-decision-in-adapter.md` (rule R2, section 7, cop O) and `docs/system-design-format/proof/2026-11-02-detector-a19/facts/rule-v3-notes.md` (clauses O1, O2, O3). On 147 labeled sites, the v3 definition of O fires on 7 TRUE sites and 0 FALSE sites. No cop finds this shape. `Sdlc/Structural/EffectInModel` is disabled in this repo because no `LedgerPath` is set (#1507). This cop is the second of three. It needs the shared objects from the P ticket (`Sdlc/Structural/PrecedenceInEdge`: `EdgeScope`, `EffectReach`). Build P first. ## Design /home/jared/dev/cc-os/docs/takedown/1510-20261102-175443/final-design.json ## Approach Add the RecordingCop `Sdlc/Structural/DomainOperandInEdge`, which uses the shared EdgeScope for role and judges each comparison by where its operands come from: a caller-given parameter against our literal, a block value against a local that holds a second source, or a class-level numeric limit. Required work: 1. Write `plugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge.rb` 2. Write `plugins/os-sdlc/lint-lessons/sdlc-structural-domain-operand-in-edge.yml` 3. Write `plugins/os-sdlc/.rubocop.yml` 4. Write `plugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge/limit.rb` 5. Write `plugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge/cadence.rb` 6. Write `plugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge/verdict.rb` 7. Write test `plugins/os-sdlc/tests/cops/domain_operand_in_edge_test.rb` Orchestrator work: Whole-tree run: run the cop alone with the os-sdlc config over every Ruby file under `plugins/`, including `bin/` scripts and the os-me and os-backlog plugins; put a table in the PR with one row for each hit (site, clause, TRUE or FALSE, and the reason by the swap test); the run must have zero false hits; for each false hit, either make the value rule tighter and add the hit as a must-pass fixture, or stop and file a ticket with `/os-backlog:capture`. Ruling 2026-11-02: the design accepts a narrower rule than the original ticket (O1 tests parameters only; `Cadence` takes numeric class constants only; no O3 caller-collection test). List each R2 TRUE site from `docs/system-design-format/proof/2026-11-02-detector-a19/facts/rule-v3-notes.md` that the real-tree run does not report, with the fact that blocks it. ## Acceptance criteria - `unless split == "holdout"`, `split` is a param, adapter: fires - `return X if kind == :forgejo`: pass (jump branch) - `name == :require ? a : b`: pass (ternary) - `case param when :a` in an adapter: fires - `status.exitstatus == 0` on a read value: pass (not param-rooted) - `hits.select { \: h\| h[:l] == longest }`, `longest` a local | fires - `judged.include?(row[..])` with `judged` a local: fires - `row.ruled_by?(closed)` with `closed` a local: fires - `known.add?(r.id)` or `lineage.error?(d)`: pass - `(total % EVERY).zero?` with `EVERY = 5` in the class: fires - Same code in a class with no effect call: pass (no role) - Entry, param-rooted `argv` compare: pass (entry input) - Entry with a local compare in a block: fires ## Owner sketch | Responsibility | Owner | |---|---| | Is this node inside an edge (adapter or entry)? | `EdgeScope#role` (existing, shared with P) | | Report with the exact message | `DomainOperandInEdge` (RecordingCop) | | Param-origin value compared with our literal as a verdict (`if`/`unless`/`case`) | `DomainOperandInEdge::Verdict` | | A block value compared with a local held from a second source | `DomainOperandInEdge::Limit` | | A class numeric constant used as the limit of a comparison | `DomainOperandInEdge::Cadence` | | Lesson text | `lint-lessons/sdlc-structural-domain-operand-in-edge.yml` (shares why, decision opening and correction with P) | | Default config | `plugins/os-sdlc/.rubocop.yml` | `plugins/os-sdlc/tests/cops/domain_operand_in_edge_test.rb` ```ruby # frozen_string_literal: true require_relative "../test_helper" require_relative "../support/cop_helpers" class DomainOperandInEdgeTest < Minitest::Test include CopHelpers PLUGIN_ROOT = File.expand_path("../..", __dir__) COP_NAME = "Sdlc/Structural/DomainOperandInEdge" COP_FILE = File.join(PLUGIN_ROOT, "lib", "os_sdlc", "cops", "structural", "domain_operand_in_edge.rb") LESSON_FILE = File.join(PLUGIN_ROOT, "lint-lessons", "sdlc-structural-domain-operand-in-edge.yml") PRECEDENCE_LESSON_FILE = File.join(PLUGIN_ROOT, "lint-lessons", "sdlc-structural-precedence-in-edge.yml") ENTRY_PATH = "bin/tool" MESSAGE = "An edge compares a value with an operand of our rules: our literal, a limit computed here, " \ "or a set from a second source. Return the values it read; a decision object or the value " \ "that owns the data compares." def test_cop_is_registered_from_its_file_as_a_recording_cop cop = RuboCop::Cop::Sdlc::Structural::DomainOperandInEdge assert_equal RuboCop::Cop::Sdlc::RecordingCop, cop.superclass assert_equal COP_FILE, Object.const_source_location(cop.name).first assert_includes RuboCop::Cop::Registry.global.map(&:cop_name), COP_NAME end def test_default_config_enables_the_cop_with_no_exclude_key entry = YAML.load_file(File.join(PLUGIN_ROOT, ".rubocop.yml"), aliases: true).fetch(COP_NAME) assert_same true, entry["Enabled"] refute_includes entry.keys, "Exclude" end def test_lesson_key_names_the_cop assert_equal COP_NAME, lesson["key"] end def test_lesson_shares_the_why_with_the_precedence_lesson assert_equal precedence_lesson["why"], lesson["why"] end def test_lesson_shares_the_correction_with_the_precedence_lesson assert_equal precedence_lesson["correction"], lesson["correction"] end def test_lesson_shares_the_first_decision_sentences_with_the_precedence_lesson assert_equal sentences(precedence_lesson["decision"]).first(2), sentences(lesson["decision"]).first(2) end def test_lesson_example_before_fires_and_after_passes example = YAML.load_file(LESSON_FILE).fetch("example") assert_fires example.fetch("before") assert_passes example.fetch("after") end def test_fire_1_literal_refusal_on_a_given_split assert_fires adapter(<<~'RUBY') def read(split) labels = parse refuse(labels, split) unless split == "holdout" labels.select { |label| label.split == split } end def refuse(labels, split) = (raise Error, "held-out row in #{split}" if labels.any?) RUBY end def test_fire_2_cadence_constant assert_fires adapter(<<~'RUBY') EVERY = 5 TEXT = "Tip" def count(id) total = sighted(id) + 1 File.write(path(id), total.to_s) TEXT if (total % EVERY).zero? end RUBY end def test_fire_3_case_on_a_given_classification assert_fires adapter(<<~'RUBY') def verdict(classification, output) case classification when :red then Result.new(status: 0, lines: output.lines) when :unexpected_pass then failure("survived") else failure("broken") end end def failure(line) = Result.new(status: 1, lines: [line]) RUBY end def test_fire_4_computed_longest assert_fires adapter(<<~'RUBY') def longest_files(hits) longest = hits.map { |hit| hit[:length] }.max hits.select { |hit| hit[:length] == longest }.map { |hit| hit[:file] } end RUBY end def test_fire_5_rows_ruled_by_closed_subjects assert_fires adapter(<<~'RUBY') def unclosed(name) closed = keep_subjects(name) candidates(name).reject { |row| row.ruled_by?(closed) } end RUBY end def test_fire_6_judged_set_from_second_source assert_fires adapter(<<~'RUBY') def unread judged = rows.filter_map { |row| row["session_id"] if row["verdict"] } asked_rows.reject { |row| judged.include?(row["session_id"]) } end RUBY end def test_fire_7_mtime_against_seen_cursor assert_fires adapter(<<~'RUBY') def unread(paths) present = paths.select { |path| File.exist?(path) } seen = migrated(present) present.select { |path| File.mtime(path).to_f > seen.fetch(session_id(path), 0) } end RUBY end def test_pass_1_factory_return_on_kind assert_passes adapter(<<~'RUBY') def repo_issues(kind, repo) return ForgejoIssues.new(repo) if kind == :forgejo GhIssues.new(repo) end RUBY end def test_pass_2_entry_tier_from_a_called_object assert_passes <<~'RUBY', path: ENTRY_PATH issues.each do |issue| tier = Wakeup.tier(issue["labels"]) next if tier == :skip if tier == :notify File.open(LOG, "a") { |f| f.puts(issue["title"]) } end end RUBY end def test_pass_3_ternary_selection assert_passes adapter(<<~'RUBY') def target(name, arg) base = name == :require ? test_dir : File.dirname(@file) File.join(base, arg) end RUBY end def test_pass_4_exit_status_of_a_read_value assert_passes adapter(<<~'RUBY') def run(cmd) _o, status = Open3.capture2(cmd) refuse("failed") unless status.exitstatus == 0 end def refuse(line) = raise(Error, line) RUBY end def test_pass_5_header_read_from_the_file assert_passes adapter(<<~'RUBY') def problems(path) actual = File.open(path, &:gets).to_s.chomp.split("\t") return [] if actual == COLUMNS [{ "problem" => "header differs" }] end RUBY end def test_pass_6_file_name_against_a_string_constant assert_passes adapter(<<~'RUBY') SCORE_FILE = "score" def names(glob) = Dir.glob(glob).map { |f| File.basename(f, ".tsv") }.reject { |name| name == SCORE_FILE } RUBY end def test_pass_7_next_unless_status_missing assert_passes adapter(<<~'RUBY') def each_resolved(items) items.each do |item| resolution = resolve(item) next resolution unless resolution.status == :missing remember(item) end end RUBY end def test_pass_8_literal_list_membership assert_passes adapter(<<~'RUBY') def kinds(info) return none unless info && %i[forgejo github].include?(info[:kind]) info[:kind] end RUBY end def test_pass_9_heading_from_an_instance_variable assert_passes adapter(<<~'RUBY') def in_history? in_history = false File.foreach(@file) { |line| in_history = line.chomp == @heading if line.start_with?("## ") } in_history end RUBY end def test_pass_10_row_constant_against_a_given_constant assert_passes adapter("def join_row_for(constant) = join_rows.find { |row| row.constant == constant }") end def test_pass_11_signals_from_a_lesson_source assert_passes adapter(<<~'RUBY') def typed signals.select { |s| s.source == Lessons.source("params", "initialize") && s.kind == "type" } end RUBY end def test_pass_12_entry_rescue_re_raise assert_passes <<~'RUBY', path: ENTRY_PATH begin run rescue StandardError => e raise unless e.message.include?("thor") end RUBY end def test_pass_13_path_text_predicates assert_passes adapter(<<~'RUBY') def kinds(path, scripts, opts, params) [path.end_with?("_test.rb"), scripts.reject { |p| p.include?("/hooks/") }, opts.reject { |o| o.end_with?("bundler/setup") }, params.select { |k, _| k.end_with?("Path") }] end RUBY end def test_pass_14_stamp_size_literal assert_passes adapter(<<~'RUBY') def stamp stamp = @options.slice(:row_id, :tier) raise Error, "--row-id needs --tier" if stamp[:row_id] && stamp.size < 3 stamp end RUBY end def test_pass_15_set_add_dedup assert_passes adapter(<<~'RUBY') def append(incoming) known = known_ids fresh = incoming.select { |r| known.add?(r.id) } File.open(@path, "a") { |f| fresh.each { |r| f.puts(r.id) } } end RUBY end def test_pass_16_prefix_from_the_block assert_passes adapter(<<~'RUBY') def kept(files) = @exclude.reject { |prefix| files.any? { |p| p.start_with?(prefix) } } def excluded?(path) = @exclude.any? { |prefix| path.start_with?(prefix) } RUBY end def test_pass_17_block_value_includes_a_given_path assert_passes adapter("def count(path) = targets.count { |required| required.include?(path) }") end def test_pass_18_object_answers_the_question assert_passes adapter(<<~'RUBY') def errors(classes) lineage = Lineage.new classes.select { |d| lineage.error?(d) } end def sent(names) index = Index.new names.select { |n| index.sent?(n) } end RUBY end def test_pass_19_tally_fetch_default assert_passes adapter(<<~'RUBY') def surplus(left, right) known = right.tally left.tally.flat_map { |e, n| [e] * [n - known.fetch(e, 0), 0].max } end RUBY end def test_pass_20_fetch_with_default assert_passes adapter("def ids(rulings) = rulings.flat_map { |cells| governed.fetch(cells[\"id\"], []) }") end def test_pass_21_skipped_segments assert_passes adapter(<<~'RUBY') def files(dir) = raw(dir).reject { |p| excluded?(p) } def excluded?(p) = p.split("/").any? { |s| SKIPPED.include?(s) } RUBY end def test_pass_22_interpolated_prefix assert_passes adapter(<<~'RUBY') def plugin_entries = entries.select { |e| under_plugin?(e) } def under_plugin?(e) = e.start_with?("plugins/#{@plugin}/") RUBY end def test_pass_23_pure_class_without_effect_call assert_passes <<~'RUBY' class Labels def read(split) labels = parse refuse(labels, split) unless split == "holdout" labels.select { |label| label.split == split } end def refuse(labels, split) = (raise Error, "held-out row in #{split}" if labels.any?) end RUBY end def test_pass_24_entry_input_ternary assert_passes <<~'RUBY', path: ENTRY_PATH def call(argv) = argv.first == "--help" ? usage : run(argv) RUBY end private def sentences(text) = text.split(/(?<=\.) /) def lesson = YAML.load_file(LESSON_FILE) def precedence_lesson = YAML.load_file(PRECEDENCE_LESSON_FILE) def adapter(body) "class Tool\n def raw = File.read(@path)\n\n#{"#{body.chomp}\n".gsub(/^/, ' ')}end\n" end def assert_fires(source, path: CopHelpers::SOURCE_PATH) assert_equal [MESSAGE], offenses_for(RuboCop::Cop::Sdlc::Structural::DomainOperandInEdge, source, path: path) end def assert_passes(source, path: CopHelpers::SOURCE_PATH) assert_empty offenses_for(RuboCop::Cop::Sdlc::Structural::DomainOperandInEdge, source, path: path) end end ```
Author
Owner

Work started on branch ticket-1510 (worktree .claude/worktrees/ticket-1510) through /os-sdlc:implement with map takedown-implement. Ruling 2026-11-02: option B (narrower design accepted; the whole-tree run lists the missed R2 TRUE sites).

Work started on branch ticket-1510 (worktree .claude/worktrees/ticket-1510) through /os-sdlc:implement with map takedown-implement. Ruling 2026-11-02: option B (narrower design accepted; the whole-tree run lists the missed R2 TRUE sites).
Author
Owner

Resolution

Done: Cop Sdlc/Structural/DomainOperandInEdge (A19 R2) with lesson; two test-file hits ruled TRUE and fixed onto values; lesson sharpened so tests and scripts that read a resource count as edges and the escape clause means parsing only; seven hits labeled F150-F156.

Evidence: main 4aa56700 (fast-forward of 4d16d7f9 + 4aa56700); suite 3243 runs 0 failures; rubocop 0 non-cop offenses; whole-tree cop hits 13.

Follow-ups: #1521 (ready-for-agent, unblocked by this merge); #1511 (unblocked); remaining 13 TRUE/unlabeled cop sites are the existing detector-a19 sweep work, no new ticket.

## Resolution **Done:** Cop Sdlc/Structural/DomainOperandInEdge (A19 R2) with lesson; two test-file hits ruled TRUE and fixed onto values; lesson sharpened so tests and scripts that read a resource count as edges and the escape clause means parsing only; seven hits labeled F150-F156. **Evidence:** main 4aa56700 (fast-forward of 4d16d7f9 + 4aa56700); suite 3243 runs 0 failures; rubocop 0 non-cop offenses; whole-tree cop hits 13. **Follow-ups:** #1521 (ready-for-agent, unblocked by this merge); #1511 (unblocked); remaining 13 TRUE/unlabeled cop sites are the existing detector-a19 sweep work, no new ticket.
jared closed this issue 2026-11-03 13:06:44 +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#1510
No description provided.