lint rule: mutable collection leak via attr_reader #202
Labels
No labels
P0
P1
P2
P3
bug
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
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#202
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?
Migrated from jared/os-sdlc#27 (repo retired).
lint rule: mutable collection leak via attr_reader
Problem
General pattern (originally found in the retired eval-sandbox's
decision-dice.rb; the specific file no longer matters, the pattern
generalizes):
attr_reader :foo(orattr_accessor) exposes an ivar thatis a mutable Array/Hash literal, while some instance method mutates that
same ivar in place (
<<,push,[]=,merge!,concat,delete,etc.). Callers holding the returned reference can observe or corrupt
internal state they were never meant to write to -- a POODR
encapsulation violation.
Not covered by Sdlc/Structural/LifecycleIvarBehindAttrReader (checked): that
cop flags a different shape -- an attr_reader whose ivar is set to a real
value in one method and reset to literal
nilin another (temporal/lifecyclestate), unrelated to mutable-collection exposure.
Detection
attr_reader/attr_accessordeclarations naming ivar@foo.@footo a mutable literal ([],{},Array.new,Hash.new).(
<<,push,pop,shift,unshift,concat,[]=,merge!,delete,clear,sort!,reject!,select!,map!) on@foo.@foo.dup/@foo.freezeinstead of the raw reader.returning
@foo.dup(or freezing) instead of the raw ivar.Correction
Return
@foo.dup(or a frozen array) from the reader instead of the rawivar, or define an explicit reader method rather than
attr_reader.Pass/fail examples
attr_reader :historyalongside a method that does@history << xor similar in-place mutation.def history; @history.dup; end, or the ivar isnever mutated in place after assignment.
Provenance
Fable + Codex dual review, run 18 (ticket #18, decision-dice), 2026-07-21.
Reassessed 2026-08-01 during os-sdlc sandbox retirement triage: confirmed
distinct from LifecycleIvarBehindAttrReader, confirmed AST-checkable within
a single file (fits the plugin's existing per-file cop model), example
generalized away from the retired sandbox file.
Implementation plan
Implement as a new Sdlc/Structural cop following the existing cop pattern in
lib/os_sdlc/cops/ (e.g. Sdlc/Structural/MutableCollectionLeakViaAttrReader),
registered via a require line + config section in .rubocop.yml.
Implemented as Sdlc/Structural/MutableCollectionLeakViaAttrReader (commit
aafd6cd, branch worktree-sdlc-lint-cops). 6 tests green, rubocop clean.