Fix MutableCollectionLeakViaAttrReader: skip private readers, report every attr, decide by reader return #1010
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#1010
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
The cop
Sdlc/Structural/MutableCollectionLeakViaAttrReaderhas four defects. Jared ruled on all four on 2026-10-15 (ruling record: docs/research/lint-lessons/sdlc-structural-mutable-collection-leak-via-attr-reader.md, section 10). Probes ran on HEAD (aa6e5c3a) withbundle exec rubocop --cache false --only Sdlc/Structural/MutableCollectionLeakViaAttrReader --config .rubocop.yml; probe files are in the scratchpad of session 270dfee2.p1_private_section.rb(privatesection, thenattr_reader :items;@items = []and@items << x) gives 1 offense. Probep1b_private_inline.rb(private attr_reader :items) gives 0 offenses, becauseAttrDeclarationScan#attr_reader_callsonly selects top-levelattr_readersends and theprivate(...)wrapper hides the inner send. So the code sees the section form only.p2_two_attrs.rb(attr_reader :calls, :seen, with@calls << xand@seen << x) gives 1 offense, forcalls.checkcallsadd_offense(node, ...)on the class node for every attr. RuboCop'sBase#add_offensereturns early whencurrent_offense_locations.add?(range)is false (rubocop/cop/base.rb:206), so the second offense at the same location is dropped.<<raiseFrozenError.IvarMutationScan#mutable_literal_assigned?requires@x = [],{},Array.new, orHash.new. Probep4_callog.rb:attr_reader :logwith@log = CallLog.newand@log.add(x)gives 0 offenses (must fire). The same file with@log << xgives 0 offenses (must fire). SoCallLog.newevades the cop by shape.On HEAD the cop fires on two test classes in
plugins/os-sdlc/eval/ticket-takedown/test/run_support.rb:ContentTests(line 80,seen) andFakeGrader(line 241,requests). Jared ruled these are true positives. Tests stay in scope, with noExclude.Design
/home/jared/dev/cc-os/.claude/worktrees/prism-architect/docs/takedown/1010-20261016-074221/final-design.json
Approach
Decide the leak from what the public reader returns: walk the class body tracking visibility, report one offense per public attr symbol, treat any collection-like initializer (literal or Const.new) as mutable, and resolve file-local classes' mutating methods; the message prescribes a copy.
Required work:
plugins/os-sdlc/lib/os_sdlc/cops/mutable_collection_leak_via_attr_reader.rbplugins/os-sdlc/lib/os_sdlc/cops/mutable_collection_leak_via_attr_reader/body_statements.rbplugins/os-sdlc/lib/os_sdlc/cops/mutable_collection_leak_via_attr_reader/collection_initializer.rbplugins/os-sdlc/lib/os_sdlc/cops/mutable_collection_leak_via_attr_reader/class_mutators.rbplugins/os-sdlc/eval/ticket-takedown/test/run_support.rbplugins/os-sdlc/lib/os_sdlc/cops/mutable_collection_leak_via_attr_reader/ivar_mutation_scan.rbplugins/os-sdlc/lib/os_sdlc/cops/mutable_collection_leak_via_attr_reader/mutable_literal.rbplugins/os-sdlc/tests/cops/mutable_collection_leak_reader_visibility_and_shape_test.rbAcceptance criteria
privatesection, then attr_reader,@items << x: noneprivate attr_reader/attr_accessor :items: noneattr_reader :calls, :seen, both shoveled: 2 offenses, one on each symbol@log = CallLog.new, CallLog#add calls push or@a << x,@log.add(x): offense atlog@log = CallLog.new,@log << x: offense atlogdef calls = @log.to_a, ordef items = @items.dup: none (no attr declaration)@flags = 0,@flags << 1: none@log = Remote.new, undefined,@log.add(x): noneOwner sketch
plugins/os-sdlc/tests/cops/mutable_collection_leak_reader_visibility_and_shape_test.rbRuling (Jared, 2026-10-15) on the open design question: option A. When the receiver's class is defined in another file, the cop stays silent on a non-listed method such as
add. The 4a rule forbids false positives, not missed offenses. When #1008 lands a cross-file owner index that is safe with the RuboCop result cache, a follow-up reuses that index for this cop. Do not build a separate index here.Implementation started: takedown-implement map on branch prism-architect (persistent worktree), design from docs/takedown/1010-20261016-074221/final-design.json.
jared referenced this issue2026-10-16 12:04:48 +00:00
Resolution
Done: Takedown run 1010-20261016-074221 produced the final design; takedown-implement implementation 47 placed it and passed behavior-verifier, suite-check, contract-auditor (one refactor round). MutableCollectionLeakViaAttrReader now handles reader visibility and collection shape per the design.
Evidence: Commits
56686026(Runner, refactor paths),b51ef274(placed design files the Runner commit omitted),913661b7(P5 lens signal) on prism-architect; suite gate green twice (dispatches 421, 424).Follow-ups: #1018 Runner commit omits design-placer paths (filed, skeptic CREATE). #1017 rejected polish round cost logged as $0 (filed). Grader marked design-1 cause_remains while the critic accepted it; reported to Jared in the session summary, no ticket.