Sdlc/Structural/RescueSite: record is_a?, ===, and when constants only when the name ends in Error or Exception #1376
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#1376
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/RescueSiteis a recording cop. Its record claims that a constant is an error family that a site handles. For theis_a?,===, andwhenforms it records any constant, so most of those records name a class that is not an error.ErrorLineage::RescueIndexreads the records, andbin/lint-sweepshows each record as an offense.Found by the #1325 zero-false-positive audit (docs/plans/class-length-family/sdlc-cop-audit.md). Shapes measured on main
3ccd7a22, 2026-10-26, over everyplugins/**/*.rbfile (446 records):rescue C(any constant)plugins/os/lib/handoff/cli.rb:24rescue Error => eis_a?/===/whenwith a constant whose last name segment ends inErrororExceptionplugins/os-sdlc/lib/os_sdlc/project.rb:14raise if e.is_a?(OsSdlc::Project::TargetSelectionError)is_a?/===/whenwith any other constantplugins/os-aidd-lint/lib/aidd_lint/hook_input.rb:12data = {} unless data.is_a?(Hash); alsowhen Arrayatplugins/os-adr/lib/adr/record/template.rb:21. Most common:is_a?(Hash)42,is_a?(String)15, Prism node classesIn the #1317 sweep of os-backlog, os, os-adr, os-aidd-lint, and os-me (163 RescueSite offenses in 93 files), 33 are this false-positive shape and 130 are
rescuesites. Example:value.is_a?(String)is recorded as an error family.Approach
Fix by shape in the cop matcher. Never use a config Exclude list or a test-path exclusion.
Rule: for the
is_a?,===, andwhenforms, the cop records a constant only when the last segment of its name ends inErrororException. Therescueform does not change: a rescued constant is always an exception class or module.Measured effect (scratch simulation on main
3ccd7a22): records overplugins/**/*.rbgo from 446 to 330; the 116 removed records are all non-errors; the 9 kept non-rescue records are all errors.plugins/os-sdlc/bin/detector error-lineage --target os-sdlcprints the same bytes before and after. Two rejected rules changed 8 rows of that output: record a non-rescue constant only when the same file rescues it, or drop the non-rescue forms.A non-rescue check on an error whose name does not end in
ErrororException(for exampleGate::Failed) is no longer recorded. This miss is accepted, as in ruling R2 (docs/plans/class-length-family/HANDOFF.md): the cop gives up recall to keep every record true. No such site exists inplugins/today.Required work:
plugins/os-sdlc/lib/os_sdlc/cops/structural/rescue_site.rb, record anis_a?argument, a===receiver, or awhencondition only when the last segment of its constant name ends inErrororException. Gate the rule on the form, not inside the sharedsitepath, so that therescueform stays unchanged. Add must-pass fixtures (value.is_a?(String),Hash === value,case vwithwhen Array) and must-fire fixtures (e.is_a?(Map::InvalidError),Psych::Exception === e) toplugins/os-sdlc/tests/cops/rescue_site_test.rb.check: present
is_a?(String)inplugins/os-sdlc/tests/cops/rescue_site_test.rbtest_a_when_on_a_local_variable_names_its_constants(plugins/os-sdlc/tests/cops/rescue_site_test.rb:48-51): its fixturewhen Failed, Loud::Errorrecords onlyLoud::Errorunder the rule. Rename the fixture constantFailedto anError-suffixed name so that the test keeps twowhenrecords.check: absent
when Failed, Loud::Errorinplugins/os-sdlc/tests/cops/rescue_site_test.rbplugins/os-sdlc/tests/detector/error_lineage_test.rbthat check the errorRootwithis_a?,===, andwhen(:13,:28). RenameRoottoRootErrorin every fixture and assertion of that file, so that the family counts stay 3, 3, 1 and 1.check: absent
is_a?(Root)inplugins/os-sdlc/tests/detector/error_lineage_test.rbplugins/os-sdlc/lint-lessons/sdlc-structural-rescue-site.yml, state insignalthat anis_a?,===, orwhensite counts only when the constant name ends in Error or Exception.check: present
ends in Error or Exceptioninplugins/os-sdlc/lint-lessons/sdlc-structural-rescue-site.ymldocs/implementation-status/os-sdlc.md.check: present
#1376indocs/implementation-status/os-sdlc.mdAcceptance criteria
value.is_a?(String),Hash === value, orcase vwithwhen Array, when the cop runs, then it records nothing.e.is_a?(Map::InvalidError),Psych::Exception === e, orcase errorwithwhen Gate::FailedError, when the cop runs, then it records the constant with its form.rescue Gate::Failed => e, when the cop runs, then it recordsGate::Failedwith formrescue, as today.plugins/os-sdlc/bin/detector error-lineage --target os-sdlc, when it runs after the change, then its stdout is byte-identical to its stdout on the base commit.plugins/os-sdlc/bin/lint-sweep plugins/<p>for os-backlog, os, os-adr, os-aidd-lint, and os-me, when it runs, then everySdlc/Structural/RescueSiteoffense is therescueform or names a constant whose last segment ends inErrororException(163 offenses on3ccd7a22become 130).Owner sketch
RuboCop::Cop::Sdlc::Structural::RescueSite(existing) owns the decision: it already routes all four forms throughsite. It asks for the constant's name parts (RescueSite::ConstantParts, existing) and, for a non-rescue form, reports only when the last part names an error. No new object.In scope / out of scope
is_a?,===, andwhenforms, its tests, the two pinning tests above, and the lesson signal.plugins/): recordingrescue *CONSTwith a literal constant array,kind_of?andinstance_of?arguments, andcase @ivarsubjects.rescue_site_test.rb:57-60(an unresolved splat is not a site) stays. The onlycase @ivarinplugins/(plugins/os-sdlc/lib/os_sdlc/constant_path.rb:27) checks Prism node classes, so recording it would add a false positive.ErrorLineage::RescueIndexand the other detector code; their output does not change.Blocking edges
None. One implement run.
Origin
VERDICT: CREATE
Sdlc/Structural/RescueSite: record constant-array splats, ivar case subjects, kind_of?; skip non-errorsto Sdlc/Structural/RescueSite: record is_a?, ===, and when constants only when the name ends in Error or ExceptionWork started on branch ticket-1376 via /os-sdlc:implement.
Resolution
Done: RescueSite records is_a?/===/when constants only when the last name segment ends in Error or Exception; rescue sites unchanged. Tests, lesson signal and History row updated.
Evidence: Implementation 170, commit
50c917b8on ticket-1376; all 8 pipeline gates passed, including full suite (70 s) and contract-auditor.Follow-ups: none: the sdlc-cop-audit.md RescueSite row and the #1317 count note (163 -> 130) are carried in handoff 2026-10-26-1527 for the #1317 sweep step.