Scope bin/evidence sender matches by receiver class #997

Closed
opened 2026-10-12 13:03:30 +00:00 by jared · 3 comments
Owner

Problem

The takedown bin/evidence matches CLI#run by bare name. final-evidence.txt then lists unrelated worktree and os-sdlc senders, which hides the real callers.

Design

/home/jared/dev/cc-os/.claude/worktrees/prism-architect/docs/takedown/997-20261012-102656/final-design.json

Approach

Fingerprint stops claiming unverified same-name calls as senders of an instance method: only LSP-confirmed references are sent by, and unconfirmed bare-name matches become one capped UNCONFIRMED candidate line.

Required work:

  1. Write plugins/os-sdlc/lib/os_sdlc/command/fingerprint.rb
  2. Write plugins/os-sdlc/lib/os_sdlc/presenter/fingerprint/sender_row.rb
  3. Write plugins/os-sdlc/lib/os_sdlc/model/report/row_entries.rb
  4. Write plugins/os-sdlc/tests/command/fingerprint_test.rb
  5. Write plugins/os-sdlc/tests/evidence_bin_test.rb
  6. Write test plugins/os-sdlc/tests/command/fingerprint_candidates_test.rb

Acceptance criteria

  • Instance method, bare-name sites, LSP confirms some: EVIDENCE: C#m sent by [lsp] p:l. for the confirmed ones. One capped UNCONFIRMED: line for the rest, if any remain.
  • Instance method, bare-name sites, LSP empty or unavailable: No sent by. One capped UNCONFIRMED: C#m — N same-name call sites, receiver type unverified, at … line.
  • Instance method, zero bare-name sites: Unchanged: escalation and LSP sweep, UNKNOWN or [lsp] lines.
  • Singleton method: Unchanged: Owner.name match, sent by.
  • NEVER_SWEPT names: Unchanged.

Owner sketch

Responsibility Owner
Decide whether an instance-method call site is a confirmed sender or a candidate OsSdlc::Fingerprint
Type-aware references from the definition site Probe::Lsp (unchanged)
Syntactic candidate discovery ($RECV.name) Probe.sender (unchanged)
Format UNCONFIRMED: line, capped at SITE_LIMIT Fingerprint::SenderRow
Emit sender, lsp and candidate entries Report::RowEntries

plugins/os-sdlc/tests/command/fingerprint_candidates_test.rb

# frozen_string_literal: true

require_relative "../test_helper"

class OsSdlc::FingerprintCandidatesTest < Minitest::Test
  include OsSdlcTestHelper

  UNIT = OsSdlc::Resolver::Unit.new(klass: "Cli", path: "lib/cli.rb")
  Location = Struct.new(:path, :line, :column)

  class Runner
    def initialize(refs)
      @refs = refs
    end

    def symbols(name:, root:)
      _ = [name, root]
      []
    end

    def references(path:, line:, column:, root:)
      _ = [path, line, column, root]
      @refs
    end
  end

  def test_lsp_confirms_only_real_reference_and_unrelated_call_is_candidate
    row = run_row(Runner.new([Location.new("lib/real.rb", 1, 1)]), "worker.rb" => "Pool.new.run\n",
                  "real.rb" => "cli.run\n")
    assert_empty row.senders
    assert_equal ["lib/real.rb"], row.lsp_senders.map(&:path)
    assert_equal ["worker.rb"], row.candidates.map { |site| File.basename(site.path) }
    refute(row.lsp_evidence_lines.any? { |line| line.include?("worker.rb") })
  end

  def test_without_lsp_every_bare_name_match_is_a_candidate_not_a_sender
    row = run_row(EmptyLspRunner.new, "worker.rb" => "Pool.new.run\n", "other.rb" => "Job.run\n")
    assert_empty row.evidence_lines
    assert_empty row.lsp_evidence_lines
    assert_equal 2, row.candidates.size
    lines = OsSdlc::Report::RowEntries.new(row: row).call.map(&:line)
    assert_equal 1, lines.size
    assert_match(/\AUNCONFIRMED: Cli#run — 2 same-name call sites, receiver type unverified, at /, lines.first)
    refute(lines.any? { |line| line.start_with?("EVIDENCE:") })
  end

  def test_candidate_line_is_capped_at_three_sites
    files = (1..5).to_h { |n| ["caller_#{n}.rb", "Pool#{n}.new.run\n"] }
    row = run_row(EmptyLspRunner.new, files)
    assert_equal 5, row.candidates.size
    assert_match(/\AUNCONFIRMED: Cli#run — 5 same-name call sites, receiver type unverified, at [^,]+, [^,]+, [^,]+, …\.\z/,
                 row.candidate_line)
  end

  private

  def run_row(runner, callers)
    with_tmpdir do |dir|
      FileUtils.mkdir_p(File.join(dir, "lib"))
      File.write(File.join(dir, "lib", "cli.rb"), "class Cli\n  def run\n  end\nend\n")
      callers.each { |name, body| File.write(File.join(dir, "lib", name), body) }
      OsSdlc::Fingerprint.new(UNIT, dir, lsp_runner: runner).sender_rows.find { |r| r.method_name == "run" }
    end
  end
end
## Problem The takedown `bin/evidence` matches `CLI#run` by bare name. `final-evidence.txt` then lists unrelated worktree and os-sdlc senders, which hides the real callers. ## Design /home/jared/dev/cc-os/.claude/worktrees/prism-architect/docs/takedown/997-20261012-102656/final-design.json ## Approach Fingerprint stops claiming unverified same-name calls as senders of an instance method: only LSP-confirmed references are `sent by`, and unconfirmed bare-name matches become one capped UNCONFIRMED candidate line. Required work: 1. Write `plugins/os-sdlc/lib/os_sdlc/command/fingerprint.rb` 2. Write `plugins/os-sdlc/lib/os_sdlc/presenter/fingerprint/sender_row.rb` 3. Write `plugins/os-sdlc/lib/os_sdlc/model/report/row_entries.rb` 4. Write `plugins/os-sdlc/tests/command/fingerprint_test.rb` 5. Write `plugins/os-sdlc/tests/evidence_bin_test.rb` 6. Write test `plugins/os-sdlc/tests/command/fingerprint_candidates_test.rb` ## Acceptance criteria - Instance method, bare-name sites, LSP confirms some: `EVIDENCE: C#m sent by [lsp] p:l.` for the confirmed ones. One capped `UNCONFIRMED:` line for the rest, if any remain. - Instance method, bare-name sites, LSP empty or unavailable: No `sent by`. One capped `UNCONFIRMED: C#m — N same-name call sites, receiver type unverified, at …` line. - Instance method, zero bare-name sites: Unchanged: escalation and LSP sweep, `UNKNOWN` or `[lsp]` lines. - Singleton method: Unchanged: `Owner.name` match, `sent by`. - `NEVER_SWEPT` names: Unchanged. ## Owner sketch | Responsibility | Owner | |---|---| | Decide whether an instance-method call site is a confirmed sender or a candidate | `OsSdlc::Fingerprint` | | Type-aware references from the definition site | `Probe::Lsp` (unchanged) | | Syntactic candidate discovery (`$RECV.name`) | `Probe.sender` (unchanged) | | Format `UNCONFIRMED:` line, capped at `SITE_LIMIT` | `Fingerprint::SenderRow` | | Emit sender, lsp and candidate entries | `Report::RowEntries` | `plugins/os-sdlc/tests/command/fingerprint_candidates_test.rb` ```ruby # frozen_string_literal: true require_relative "../test_helper" class OsSdlc::FingerprintCandidatesTest < Minitest::Test include OsSdlcTestHelper UNIT = OsSdlc::Resolver::Unit.new(klass: "Cli", path: "lib/cli.rb") Location = Struct.new(:path, :line, :column) class Runner def initialize(refs) @refs = refs end def symbols(name:, root:) _ = [name, root] [] end def references(path:, line:, column:, root:) _ = [path, line, column, root] @refs end end def test_lsp_confirms_only_real_reference_and_unrelated_call_is_candidate row = run_row(Runner.new([Location.new("lib/real.rb", 1, 1)]), "worker.rb" => "Pool.new.run\n", "real.rb" => "cli.run\n") assert_empty row.senders assert_equal ["lib/real.rb"], row.lsp_senders.map(&:path) assert_equal ["worker.rb"], row.candidates.map { |site| File.basename(site.path) } refute(row.lsp_evidence_lines.any? { |line| line.include?("worker.rb") }) end def test_without_lsp_every_bare_name_match_is_a_candidate_not_a_sender row = run_row(EmptyLspRunner.new, "worker.rb" => "Pool.new.run\n", "other.rb" => "Job.run\n") assert_empty row.evidence_lines assert_empty row.lsp_evidence_lines assert_equal 2, row.candidates.size lines = OsSdlc::Report::RowEntries.new(row: row).call.map(&:line) assert_equal 1, lines.size assert_match(/\AUNCONFIRMED: Cli#run — 2 same-name call sites, receiver type unverified, at /, lines.first) refute(lines.any? { |line| line.start_with?("EVIDENCE:") }) end def test_candidate_line_is_capped_at_three_sites files = (1..5).to_h { |n| ["caller_#{n}.rb", "Pool#{n}.new.run\n"] } row = run_row(EmptyLspRunner.new, files) assert_equal 5, row.candidates.size assert_match(/\AUNCONFIRMED: Cli#run — 5 same-name call sites, receiver type unverified, at [^,]+, [^,]+, [^,]+, …\.\z/, row.candidate_line) end private def run_row(runner, callers) with_tmpdir do |dir| FileUtils.mkdir_p(File.join(dir, "lib")) File.write(File.join(dir, "lib", "cli.rb"), "class Cli\n def run\n end\nend\n") callers.each { |name, body| File.write(File.join(dir, "lib", name), body) } OsSdlc::Fingerprint.new(UNIT, dir, lsp_runner: runner).sender_rows.find { |r| r.method_name == "run" } end end end ```
Author
Owner

Ticket-skeptic verdict (real run; the VERDICT line in the body was written by the filing agent, not the skeptic): CREATE. EvidenceCheck passes only file paths to bin/evidence collect, so the bare-name match must be fixed in bin/evidence.
Session: 3f094e92-202f-41fc-8d9c-b591750d9bc1
Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-prism-architect/3f094e92-202f-41fc-8d9c-b591750d9bc1.jsonl

Ticket-skeptic verdict (real run; the VERDICT line in the body was written by the filing agent, not the skeptic): CREATE. EvidenceCheck passes only file paths to bin/evidence collect, so the bare-name match must be fixed in bin/evidence. Session: 3f094e92-202f-41fc-8d9c-b591750d9bc1 Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-prism-architect/3f094e92-202f-41fc-8d9c-b591750d9bc1.jsonl
Author
Owner

Implementation started on branch prism-architect (takedown-implement map, target os-sdlc), design docs/takedown/997-20261012-102656/final-design.json.

Implementation started on branch prism-architect (takedown-implement map, target os-sdlc), design docs/takedown/997-20261012-102656/final-design.json.
Author
Owner

Resolution

Done: bin/evidence reports a method's sender as 'EVIDENCE: ... sent by' only when the LSP confirms the reference; unconfirmed bare-name matches become one capped 'UNCONFIRMED:' candidate line. change-auditor, responsibility-auditor, blast-radius and code-probe now treat UNCONFIRMED as a candidate and forbid 'no consumer' while one exists.

Evidence: Implementation 41 complete after two orchestrator reroutes (escalation 18: map defect, existing tests in code_files; escalation 19: report_test.rb pinned the old output and was not in the design). Commits 5e03ed64 (production), ea934691 (test hand-fixes), plus the agent/skill doc commit. os-sdlc suite 1543 runs, 0 failures. Design docs/takedown/997-20261012-102656.

Follow-ups: #1003 (route changed existing tests away from the programmer at place-code). Known limit from the design: a down LSP shows real callers as UNCONFIRMED; no follow-up filed because the line is visible and the readers now handle it.

## Resolution **Done:** bin/evidence reports a method's sender as 'EVIDENCE: ... sent by' only when the LSP confirms the reference; unconfirmed bare-name matches become one capped 'UNCONFIRMED:' candidate line. change-auditor, responsibility-auditor, blast-radius and code-probe now treat UNCONFIRMED as a candidate and forbid 'no consumer' while one exists. **Evidence:** Implementation 41 complete after two orchestrator reroutes (escalation 18: map defect, existing tests in code_files; escalation 19: report_test.rb pinned the old output and was not in the design). Commits 5e03ed64 (production), ea934691 (test hand-fixes), plus the agent/skill doc commit. os-sdlc suite 1543 runs, 0 failures. Design docs/takedown/997-20261012-102656. **Follow-ups:** #1003 (route changed existing tests away from the programmer at place-code). Known limit from the design: a down LSP shows real callers as UNCONFIRMED; no follow-up filed because the line is visible and the readers now handle it.
jared closed this issue 2026-10-12 14:59: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#997
No description provided.