os-sdlc lint rule: format validation raising in initialize #94
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#94
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?
Context
Sandi Metz blind critique:
initializedoes regexp-format validation of a parameter andraises inline — primitive obsession, not a self-validating value object.
Problem
plugins/os-sdlc/lib/os_sdlc/workspace.rb:16-21:Detection
initializemethod body and its parameter list.initialize's parameter names..to_s(e.g.@ticket_number = ticket_number.to_s) — count as parameter-derived too.if/unlessnode directly in the body.&&/||) amatch?/=~send whosereceiver/argument is a parameter or parameter-derived var (one further
.to_sallowed), AND that node's branch body contains a
raise.nil?/is_a?/respond_to?guards raising a plainArgumentError—presence/type, not format validation.
return if <valid>then an unconditionalraisedoesn't match step 4 (no branch wraps it) — letsa value object's own validating
initialize(the recommended correction) pass."initialize validates ticket_number against a format regexp (TICKET_NUMBER) and raises inline -- this is primitive obsession: a plain String is re-validated by hand. Extract a value object (e.g. TicketNumber) that validates its own format in its own initialize and raises there, then call TicketNumber.new(ticket_number).to_s here."Correction
Workspace#initializehas noif/unlessnode with amatch?/=~condition and araisein its branch — it clears. The rule also inspects
TicketNumber#initialize(everyinitializeis in scope); that passes too: its guard isreturn if <valid>then anunconditional
raise, not nested in a branch, so step 4 never matches (guard-clauseexemption, step 6).
Pass/fail examples
Workspace#initialize—@ticket_number=ticket_number.to_s(step 2), then
unless @ticket_number.match?(TICKET_NUMBER)(parameter-derived) wrapsraise HarnessErrorinline (step 4: raise inside branch).TicketNumber#initialize(guard-clause shaped, nowrapped raise);
Workspace#initializeonly callsTicketNumber.new(...).raise ArgumentError, "project required" if project.nil?—presence guard, no
match?/=~.Provenance
Sandi Metz blind critique of workspace.rb, cc-os mine-lints session 2026-07-22 (organic
finding — not a council/run finding).
Implementation plan
Custom RuboCop cop under
plugins/os-sdlc/lib/os_sdlc/cops/, registered inplugins/os-sdlc/.rubocop.yml; implemented by a subagent from this ticket perreferences/implement-cop.md, with review before merge.Implemented as Sdlc/FormatValidationRaisingInInitialize in commit
e0f6139on branch stepper/1-workspace. Unit tests assert the failure message verbatim; the cop fires on the original workspace.rb snippet; full lib/ scan run report-only. Full os-sdlc suite: 88 runs, 162 assertions, 0 failures. Awaiting human review/merge of the branch.Covered: Sdlc/Structural/FormatValidationRaisingInInitialize shipped and enabled in .rubocop.yml with tests. ADR-0061 finalizes the os-sdlc lint program; closing per commit
8e88bbe.