Sdlc/Structural/ForwardingClass: skip role players and third-party adapters #1360
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
rule-proposal
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#1360
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Sdlc/Structural/ForwardingClassis a recording cop. It records a class that only builds a constant and forwards to it, and its message prescribes "give the constant a constructor that takes what this class takes, and call it where this class is called".OsSdlc::Detector::Holdersreads the records, andbin/lint-sweepshows each record as an offense. Both records inplugins/are classes where that fix is wrong.Found by the #1325 zero-false-positive audit (docs/plans/class-length-family/sdlc-cop-audit.md). Shapes measured on main
4a0fc41d, 2026-10-26, over everyplugins/**/*.rbfile (2442 files, 2 records):plugins/os-aidd-lint/tests/support/fake_invoker.rb:3def call(_prompt) = Result.new(output: @output, error: @error), injected asinvoker:atplugins/os-aidd-lint/tests/tier2/judge_test.rb:25plugins/os-vault/eval/fixture/project/lib/reportgen/charts.rb:7require 'chartkit'thenChartkit::SVG.render(series)MIDDLEMAN_SOURCE,DIRECT_NEW_SOURCEinplugins/os-sdlc/tests/cops/forwarding_class_test.rb;wrapper.rbinplugins/os-sdlc/tests/detector/holders_bin_test.rb:38The #1317 sweep (
plugins/os-sdlc/bin/lint-sweep plugins/<p>for os-backlog, os, os-adr, os-aidd-lint, os-me) shows 1 offense: the FakeInvoker record.Approach
Fix by shape in the cop matcher. Never use a config Exclude list or a test-path exclusion.
Rule 1 (role player): skip a class when any public def has a parameter that its body never reads. Rule 2 (library adapter): skip a class when its file calls
require(notrequire_relative) with a string whose first path segment, with_and-removed, equals the first segment of the forwarded constant without case (chartkitmatchesChartkit,svg_kitmatchesSVGKit,jsonmatchesJSON). Read the first segment after a leading::(cbase).Measured effect (scratch monkey-patch of both rules on main
4a0fc41d): records overplugins/**/*.rbgo from 2 to 0;lint-sweep plugins/os-aidd-lintgoes from 1 to 0. The fixturesMIDDLEMAN_SOURCEandDIRECT_NEW_SOURCE, a role player that reads its parameter, and the Charts shape withrequire_relative "chartkit"all still fire. os-sdlc has no record, sobin/detector holdersoutput does not change.Accepted misses, as in ruling R2 (docs/plans/class-length-family/HANDOFF.md): the cop gives up recall to keep every record true. A forwarder that ignores a parameter (
Lint/UnusedMethodArgumentstill flags that parameter), or that forwards to a project constant whose library name the file also requires through the load path (require "reportgen"thenReportgen::X), is no longer recorded. No such site exists inplugins/today.Required work:
plugins/os-sdlc/lib/os_sdlc/cops/structural/forwarding_class/class_match.rb, makeforwarding?false when any public def has a named parameter (positional, optional, keyword, rest, or block) that nolvarin its body reads. An anonymous*,**,&, or...does not count. Add a must-pass fixture withdef call(_prompt)that returnsResult.new(output: @output, error: @error)toplugins/os-sdlc/tests/cops/forwarding_class_test.rb.check: present
def call(_prompt)inplugins/os-sdlc/tests/cops/forwarding_class_test.rbplugins/os-sdlc/lib/os_sdlc/cops/structural/forwarding_class.rbor a newforwarding_class/helper, skip the class when the filerequires a library whose first path segment matches the first segment of the forwarded constant by rule 2. Add must-pass fixtures (require "chartkit", thenChartkit::SVG.render(series);require "svg_kit", thenSVGKit.render(series)) and a must-fire fixture (the same class withrequire_relative "chartkit") toplugins/os-sdlc/tests/cops/forwarding_class_test.rb.check: present
Chartkit::SVG.render(series)inplugins/os-sdlc/tests/cops/forwarding_class_test.rbplugins/os-sdlc/lint-lessons/sdlc-structural-forwarding-class.yml, state insignalthat the class reads every parameter of its public methods and that the target is not a constant of a required library.check: present
required libraryinplugins/os-sdlc/lint-lessons/sdlc-structural-forwarding-class.ymldocs/plans/class-length-family/sdlc-cop-audit.md, change the ForwardingClass row (:47) fromfailstopassesand give its current pathplugins/os-sdlc/lib/os_sdlc/cops/structural/forwarding_class.rb.check: present
cops/structural/forwarding_class.rbindocs/plans/class-length-family/sdlc-cop-audit.mddocs/implementation-status/os-sdlc.md, and runbin/refresh-pluginsafter the lesson edit.check: present
#1360indocs/implementation-status/os-sdlc.mdAcceptance criteria
initialize(output: nil, error: nil)storing both anddef call(_prompt)returningResult.new(output: @output, error: @error), when the cop runs, then it records nothing.def build(series:) = Chartkit::SVG.render(series), when the cop runs once on a file withrequire 'chartkit'and once on the same file withrequire_relative "chartkit", then it records nothing for the first and recordsChartkit::SVGfor the second (one contrast test: the kind of require decides).MIDDLEMAN_SOURCEandDIRECT_NEW_SOURCE, when the cop runs, then it recordsThingas today. The existing tests inplugins/os-sdlc/tests/cops/forwarding_class_test.rbandplugins/os-sdlc/tests/detector/holders_bin_test.rbprove this unchanged; do not add a new test for it, because a test of existing behavior cannot be red in the red-first round.rubocopwith onlySdlc/Structural/ForwardingClassover everyplugins/**/*.rbfile, when it runs, then it reports 0 offenses (2 on4a0fc41d), andplugins/os-sdlc/bin/lint-sweep plugins/os-aidd-lintreports no ForwardingClass offense.Owner sketch
RuboCop::Cop::Sdlc::Structural::ForwardingClass::ClassMatch(existing) owns rule 1: it already holds the public defs and decidesforwarding?.RuboCop::Cop::Sdlc::Structural::ForwardingClass(existing) owns rule 2: only the cop hasprocessed_source, so it reads the file'srequirecalls and compares them withClassMatch#constant. No new object is required; a smallforwarding_class/helper is acceptable if the cop grows past its size cops.In scope / out of scope
plugins/): a role player that reads all its parameters (needs cross-file knowledge of who injects it); a library constant loaded throughBundler.requireor Zeitwerk without arequirein the file; a project-wide constant index to tell project constants from library constants.OsSdlc::Detector::Holdersand other detector code; their output does not change.Blocking edges
None. One implement run.
Origin
VERDICT: CREATE
Work started on branch ticket-1360 via /os-sdlc:implement. Before the run, acceptance criterion 3 was folded into criterion 2 as one contrast test, and the preserved-behavior criterion now says the existing tests prove it (see #953 occurrence from #1370).
Resolution
Done: ForwardingClass skips a class whose public def ignores a named parameter (role player) or whose forwarded constant comes from a library the file requires; MethodMatch, RequireCall and RequiredLibraries own the decisions. Lesson, audit row and History row updated.
Evidence: Commit
356eb506on main. Implementation 173 escalated (programmer found test defect sub->gsub at forwarding_class_test.rb:158); hand-finished: full suite 2686 tests green, rubocop clean on 9 files, ForwardingClass over plugins/**/*.rb reports 0 (was 2), design audit pass.Follow-ups: none: optional audit findings accepted under ruling R2 (0 sites in plugins/): mlhs parameters not handled, a class mixing project and library forwards is skipped by its last constant, RequiredLibraries rescans per class.