Sdlc/Structural/InstanceVariableAssumption: treat always-run assignments and guarded reads as safe #1364

Closed
opened 2026-10-25 21:23:20 +00:00 by jared · 5 comments
Owner

Problem

Rewritten 2026-10-27 from docs/lint-lessons/analyses/sdlc-structural-instance-variable-assumption.md.

The cop calls an ivar assignment conditional when any if/case/and/or is above it. @x = m.match(t) or raise always assigns (false hit). An ivar assigned in every branch of if/else or case/else is also flagged. A while/until body is skippable but not flagged (miss). The message prescribes "or guard the read", but the cop flags the guard.

Reproduce

n/a: the 5 live hits in plugins/os/lib/handoff/document.rb and the probe table are in the analysis file, step 7.

Approach

Required work:

  1. In IvasgnConditionality, call an assignment conditional only when an ancestor holds it in a skippable slot: if then/else body, case when/else body, rhs of and/or, while/until body (block and modifier forms).
  2. In InitializeIvars, treat an ivar as unconditional when every branch of one if/else or case-with-else assigns it.
  3. Change MESSAGE: remove "or guard the read"; prescribe assigning on every path of initialize, or moving the optional part to its own object.
  4. Keep guarded reads (defined?(@x), @x&.) as offenses; ticket item 3 (skip guarded reads) is rejected.

Acceptance criteria

  1. Given @x = R.match(t) or raise and a read in another method, when the cop runs, then it reports no offense.
  2. Given if f; @x = 1; else; @x = 2; end, when the cop runs, then it reports no offense.
  3. Given t && @x = 1 or @x = 1 while c.next, when the cop runs, then it reports one offense per read.
  4. Given @x = {} if flag and a defined?(@x)-guarded reader, when the cop runs, then it reports an offense and the message does not contain "guard the read".
  5. Given plugins/ on main, when the cop runs, then it reports 0 offenses.

Owner sketch

IvasgnConditionality owns "is this slot skippable"; InitializeIvars owns "assigned on every path"; ReadSites is unchanged.

Origin

  • Trigger: #1325 zero-false-positive audit of the Sdlc cops
  • Improvised this session: none
  • Chain: false positive on valid code ← cop matcher tests a proxy shape, not the real condition ← DESIGN (cop source named in Where:)
  • Root candidate: none found; searched the cop name, its mechanism terms, and false positive
  • Where: plugins/os-sdlc/lib/os_sdlc/cops/instance_variable_assumption.rb (instance_variable_assumption/ivasgn_conditionality.rb:9-28, initialize_ivars.rb:13-37)
  • Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl

VERDICT: CREATE

## Problem Rewritten 2026-10-27 from docs/lint-lessons/analyses/sdlc-structural-instance-variable-assumption.md. The cop calls an ivar assignment conditional when any `if`/`case`/`and`/`or` is above it. `@x = m.match(t) or raise` always assigns (false hit). An ivar assigned in every branch of `if`/`else` or `case`/`else` is also flagged. A `while`/`until` body is skippable but not flagged (miss). The message prescribes "or guard the read", but the cop flags the guard. ## Reproduce n/a: the 5 live hits in plugins/os/lib/handoff/document.rb and the probe table are in the analysis file, step 7. ## Approach Required work: 1. In `IvasgnConditionality`, call an assignment conditional only when an ancestor holds it in a skippable slot: `if` then/else body, `case` when/else body, rhs of `and`/`or`, `while`/`until` body (block and modifier forms). 2. In `InitializeIvars`, treat an ivar as unconditional when every branch of one `if`/`else` or `case`-with-`else` assigns it. 3. Change `MESSAGE`: remove "or guard the read"; prescribe assigning on every path of initialize, or moving the optional part to its own object. 4. Keep guarded reads (`defined?(@x)`, `@x&.`) as offenses; ticket item 3 (skip guarded reads) is rejected. ## Acceptance criteria 1. Given `@x = R.match(t) or raise` and a read in another method, when the cop runs, then it reports no offense. 2. Given `if f; @x = 1; else; @x = 2; end`, when the cop runs, then it reports no offense. 3. Given `t && @x = 1` or `@x = 1 while c.next`, when the cop runs, then it reports one offense per read. 4. Given `@x = {} if flag` and a `defined?(@x)`-guarded reader, when the cop runs, then it reports an offense and the message does not contain "guard the read". 5. Given `plugins/` on main, when the cop runs, then it reports 0 offenses. ## Owner sketch `IvasgnConditionality` owns "is this slot skippable"; `InitializeIvars` owns "assigned on every path"; `ReadSites` is unchanged. ## Origin - Trigger: #1325 zero-false-positive audit of the Sdlc cops - Improvised this session: none - Chain: false positive on valid code ← cop matcher tests a proxy shape, not the real condition ← DESIGN (cop source named in Where:) - Root candidate: none found; searched the cop name, its mechanism terms, and false positive - Where: plugins/os-sdlc/lib/os_sdlc/cops/instance_variable_assumption.rb (instance_variable_assumption/ivasgn_conditionality.rb:9-28, initialize_ivars.rb:13-37) - Session: eb98cdcd-6783-46a0-b7df-cf18a8fe2a56 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-ticket-1325/eb98cdcd-6783-46a0-b7df-cf18a8fe2a56.jsonl VERDICT: CREATE
Author
Owner

Skeptic DROP (depth-1, 1 hit fixed by #1423): proposed cop for a character offset and a byte operation in one method. Revisit if a second site appears. The real bug is filed as #1423.

Skeptic DROP (depth-1, 1 hit fixed by #1423): proposed cop for a character offset and a byte operation in one method. Revisit if a second site appears. The real bug is filed as #1423.
Author
Owner

Body rewritten 2026-10-27 from the false-positive process; analysis and lint lesson merged to main (docs/lint-lessons/analyses/, plugins/os-sdlc/lint-lessons/).

Body rewritten 2026-10-27 from the false-positive process; analysis and lint lesson merged to main (docs/lint-lessons/analyses/, plugins/os-sdlc/lint-lessons/).
Author
Owner

Work started on branch ticket-1364 (os-sdlc implement).

Work started on branch ticket-1364 (os-sdlc implement).
jared closed this issue 2026-10-27 21:40:41 +00:00
Author
Owner

Resolution

Done: IvasgnConditionality flags an initialize assignment only in a skippable slot (if/case branch body, rhs of and/or, while/until body); InitializeIvars treats an ivar assigned in every branch of if/else or case/else (including elsif chains) as set on every path, via new BranchBodies; MESSAGE drops 'or guard the read' and prescribes assigning on every path or moving the optional part to its own object. Guarded reads stay offenses.

Evidence: main 76313394 (commit fab94ec4), pushed. Cop over plugins/: 5 offenses (plugins/os/lib/handoff/document.rb:23,31,37,37,39) -> 0, no new hits. 8 new tests cover AC 1-4; os-sdlc suite 2811 runs, 0 failures, 38 s. Lint clean. Lesson file already matched the new rule; unchanged. Hand-finished: implementation 199 went stale when another unit in the same session opened implementation 200.

Follow-ups: Pipeline defect: two implementations in one Claude session collide (capture pending skeptic verdict).

## Resolution **Done:** IvasgnConditionality flags an initialize assignment only in a skippable slot (if/case branch body, rhs of and/or, while/until body); InitializeIvars treats an ivar assigned in every branch of if/else or case/else (including elsif chains) as set on every path, via new BranchBodies; MESSAGE drops 'or guard the read' and prescribes assigning on every path or moving the optional part to its own object. Guarded reads stay offenses. **Evidence:** main 76313394 (commit fab94ec4), pushed. Cop over plugins/: 5 offenses (plugins/os/lib/handoff/document.rb:23,31,37,37,39) -> 0, no new hits. 8 new tests cover AC 1-4; os-sdlc suite 2811 runs, 0 failures, 38 s. Lint clean. Lesson file already matched the new rule; unchanged. Hand-finished: implementation 199 went stale when another unit in the same session opened implementation 200. **Follow-ups:** Pipeline defect: two implementations in one Claude session collide (capture pending skeptic verdict).
Author
Owner

Follow-up filed: #1447 (two implementations in one Claude session collide; this ticket was hand-finished because of it).

Follow-up filed: #1447 (two implementations in one Claude session collide; this ticket was hand-finished because of it).
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#1364
No description provided.