os-sdlc cop Sdlc/Structural/DomainOperandInEdge: an edge compares with our literal, a limit it computes, or a second-source set (A19 R2) #1510
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#1510
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
An edge object (an adapter or an entry) compares a value with an operand of our rules: a literal of our vocabulary (
'holdout', a cadenceEVERY), a limit that this class computes (== longest), or a set read from a second source (judged.include?(id)). The comparison is a domain rule. Because it is inside the adapter, its test needs the resource. The literal attracker_lookup.rb:69(host == "github.com") is different: it is the resource's own code, so it is translation.Evidence:
docs/system-design-format/proof/2026-11-02-smell-analysis-decision-in-adapter.md(rule R2, section 7, cop O) anddocs/system-design-format/proof/2026-11-02-detector-a19/facts/rule-v3-notes.md(clauses O1, O2, O3). On 147 labeled sites, the v3 definition of O fires on 7 TRUE sites and 0 FALSE sites.No cop finds this shape.
Sdlc/Structural/EffectInModelis disabled in this repo because noLedgerPathis set (#1507).This cop is the second of three. It needs the shared objects from the P ticket (
Sdlc/Structural/PrecedenceInEdge:EdgeScope,EffectReach). Build P first.Design
/home/jared/dev/cc-os/docs/takedown/1510-20261102-175443/final-design.json
Approach
Add the RecordingCop
Sdlc/Structural/DomainOperandInEdge, which uses the shared EdgeScope for role and judges each comparison by where its operands come from: a caller-given parameter against our literal, a block value against a local that holds a second source, or a class-level numeric limit.Required work:
plugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge.rbplugins/os-sdlc/lint-lessons/sdlc-structural-domain-operand-in-edge.ymlplugins/os-sdlc/.rubocop.ymlplugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge/limit.rbplugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge/cadence.rbplugins/os-sdlc/lib/os_sdlc/cops/structural/domain_operand_in_edge/verdict.rbplugins/os-sdlc/tests/cops/domain_operand_in_edge_test.rbOrchestrator work: Whole-tree run: run the cop alone with the os-sdlc config over every Ruby file under
plugins/, includingbin/scripts and the os-me and os-backlog plugins; put a table in the PR with one row for each hit (site, clause, TRUE or FALSE, and the reason by the swap test); the run must have zero false hits; for each false hit, either make the value rule tighter and add the hit as a must-pass fixture, or stop and file a ticket with/os-backlog:capture. Ruling 2026-11-02: the design accepts a narrower rule than the original ticket (O1 tests parameters only;Cadencetakes numeric class constants only; no O3 caller-collection test). List each R2 TRUE site fromdocs/system-design-format/proof/2026-11-02-detector-a19/facts/rule-v3-notes.mdthat the real-tree run does not report, with the fact that blocks it.Acceptance criteria
unless split == "holdout",splitis a param, adapter: firesreturn X if kind == :forgejo: pass (jump branch)name == :require ? a : b: pass (ternary)case param when :ain an adapter: firesstatus.exitstatus == 0on a read value: pass (not param-rooted)hits.select { \: h\| h[:l] == longest },longesta local | firesjudged.include?(row[..])withjudgeda local: firesrow.ruled_by?(closed)withcloseda local: firesknown.add?(r.id)orlineage.error?(d): pass(total % EVERY).zero?withEVERY = 5in the class: firesargvcompare: pass (entry input)Owner sketch
EdgeScope#role(existing, shared with P)DomainOperandInEdge(RecordingCop)if/unless/case)DomainOperandInEdge::VerdictDomainOperandInEdge::LimitDomainOperandInEdge::Cadencelint-lessons/sdlc-structural-domain-operand-in-edge.yml(shares why, decision opening and correction with P)plugins/os-sdlc/.rubocop.ymlplugins/os-sdlc/tests/cops/domain_operand_in_edge_test.rbWork started on branch ticket-1510 (worktree .claude/worktrees/ticket-1510) through /os-sdlc:implement with map takedown-implement. Ruling 2026-11-02: option B (narrower design accepted; the whole-tree run lists the missed R2 TRUE sites).
Resolution
Done: Cop Sdlc/Structural/DomainOperandInEdge (A19 R2) with lesson; two test-file hits ruled TRUE and fixed onto values; lesson sharpened so tests and scripts that read a resource count as edges and the escape clause means parsing only; seven hits labeled F150-F156.
Evidence: main
4aa56700(fast-forward of4d16d7f9+4aa56700); suite 3243 runs 0 failures; rubocop 0 non-cop offenses; whole-tree cop hits 13.Follow-ups: #1521 (ready-for-agent, unblocked by this merge); #1511 (unblocked); remaining 13 TRUE/unlabeled cop sites are the existing detector-a19 sweep work, no new ticket.