Extract exits, result_shape and value_semantics to measure three rules #1551
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#1551
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
system-model opportunitiesprints three rules asnot measurable: P&F R2 needs factexits, CMD R2 needs factresult_shape, SPEC R2 needs factvalue_semantics. Each rule usesRule::Unmeasured, so it can never flag a line. A reader cannot tell "no misfit" from "not measured" for these rules. Run 3 records finding #14 as a miss with the cause "missing fact (result_shape)".Reproduce
n/a: this ticket adds facts and rules; no defect exists to reproduce.
Approach
Only
value_semanticshas a landscape entry (F16).exitsandresult_shapehave no F number. This ticket defines both.Fact to rule map.
exitsto P&F R2, "Flow runs one way, with no exit in the middle of the chain."ClassFacts#exits, a list ofExit(path, line), one entry for each process exit: a call ofexit,exit!orabortwith no receiver or with receiverKernelorProcess.ioholdsexitandabortas terminal entries, but anIoentry keeps no call name, soiocannot answer this.Rule::ForbiddenExit.new(roles: %i[Filter Pipe])flags each exit of a class that fits the role when the terminalioentries on its exit lines are removed.ForbiddenIouses the same removal. An exit inSinkis the end of the chain, so it is not a misfit.raiseis not an exit. pattern-matching.md section 2 treats a guard that raises as a decision. 65 of 797Filterclasses in os-sdlc sit in a file withraise, so countingraisegives false positives on guards.lib/in os, os-backlog, os-me or os-sdlc callsexitorabort. The run 1 hand count ("exits in CliOptions, CliTrackerRequirement, Commands") describes code that no longer exists;Backlog::ExitStatusis now raised instead.result_shapeto CMD R2, "Commands share one result shape."ClassFacts#result_shape, oneResultShape(shape, path, line)for a publiccall, or nil when the class has nocall.shapecomes from the last expression ofcall::nil,:string,:integer,:boolean,:array,:hash,:self, the last name segment ofConst.new, or:unknown. A self-send follows the sent method in the same class.:unknowncovers every other expression and a conditional whose branches differ.Rule::DivergentResult.new(roles: [:Command]). In one component, take theCommandclasses with a known shape. When at least 3 exist and one shape holds a strict majority, flag thecallline of each other class.:unknownis never a misfit. A component with no strict majority gives no site.Crc::RecordsandCrc::Sweep(Reportwhere the majority isResult), andEvidence::DirectUnitEntries(:arraywhere the majority isEntry). Run 3 finding #14 stays a miss, because half of the os-backlog commands have shape:unknown. The misses are acceptable.value_semanticsto SPEC R2, "A specification is a value object that combines with and, or, not."ClassFacts#state_writes, a list ofWrite(path, line), one entry for each ivar write outsideinitializethat is not||=. Derived reader:value_semantics = value? || state_writes.empty?.||=becausemutates_state?counts memoization.Inspector::ReferenceMatch,ForwardingClass::RequiredLibrariesandForwardingClass::MethodMatchmutate only through@x ||=, and these writes change no visible value.freezeand==. 45 of 61 classes that fitSpecificationhave none of these marks and no mutation. A rule on the marks would flag those 45 and rank them at threshold 1.Rule::BrokenValue.new(roles: [:Specification])flags eachstate_writesentry of a class withvalue_semanticsfalse. A scratch run found 1 site:RequireInsideCode::TopRunattop_run.rb:30.&&, so the combination half has no fact, and a rule on it would flag every specification. The sentence becomes "A specification is a value object."Effect on calibration. P&F, CMD and SPEC are strict, so these rules now add flags to convergence and violations to
Match#distance. This does not change a threshold. It moves the detector toward the calibration: run 2 counted R2 by hand, and run 3 could not count it. A scratch run with CMD R2 and SPEC R2 measured gave these changes over os-backlog, os-me and os-sdlc:top_run.rb:30SPEC R2.presentrows: one row leaves in os-sdlc, because a violation raises its distance.This ticket does not change the 0.05 threshold or
Match#present?. A later change can move a file from convergence 2 to convergence 3 through these rules. That is the intended behavior of a measured rule.Required work:
Exit = Data.defineinplugins/os-sdlc/lib/os_sdlc/system_model/class_facts/exit.rbResultShape = Data.defineinplugins/os-sdlc/lib/os_sdlc/system_model/class_facts/result_shape.rbinitialize— check: presentWrite = Data.defineinplugins/os-sdlc/lib/os_sdlc/system_model/class_facts/write.rbClassFacts— check: presentdef value_semanticsinplugins/os-sdlc/lib/os_sdlc/system_model/class_facts.rbForbiddenExit = Data.defineinplugins/os-sdlc/lib/os_sdlc/system_model/pattern/rule/forbidden_exit.rbDivergentResult = Data.defineinplugins/os-sdlc/lib/os_sdlc/system_model/pattern/rule/divergent_result.rbBrokenValue = Data.defineinplugins/os-sdlc/lib/os_sdlc/system_model/pattern/rule/broken_value.rbUnmeasuredinplugins/os-sdlc/lib/os_sdlc/system_model/catalog/pipes_and_filters.rbUnmeasuredinplugins/os-sdlc/lib/os_sdlc/system_model/catalog/command_router.rbUnmeasuredinplugins/os-sdlc/lib/os_sdlc/system_model/catalog/specification.rbexits,result_shapeandvalue_semanticsin section 2 of the method document — check: presentresult_shapeindocs/system-design-format/pattern-matching.mdOrchestrator work: add one History row to
docs/implementation-status/os-sdlc-system-model.md.Rejected: mark the three rules as measured with no fact — a zero would then mean nothing. Count
raiseas an exit — false positives on guards. Require==orfreezefor value semantics — 45 false positives at threshold 1. Move the rules out of convergence — no per-rule switch exists, and the hand calibration counted them.Acceptance criteria
opportunitiesruns, then thenot measurablesection has no rows, and the JSONunmeasuredlist is empty.exitin a method, whenClassFactsis built, thenexitsholds the path and line of that call.Process.exitorexit!, whenClassFactsis built, thenexitsholds that call.raise, whenClassFactsis built, thenexitsis empty.exitthat fitsFilterwhen the exit is removed, whenP&Fmatches the component, then R2 flags the exit line.Sink, whenP&Fmatches the component, then R2 flags no line.callwhose last expression isResult.new(...), whenClassFactsis built, thenresult_shapeholds the shapeResult.callwhose last expression is a self-send to a method that returns a string, whenClassFactsis built, thenresult_shapeholds the shape:string.callwhose last expression is a call on another object, whenClassFactsis built, thenresult_shapeholds the shape:unknown.Commandclasses of shapeResultand one of shape:array, whenCMDmatches the component, then R2 flags thecallline of the:arrayclass only.Commandclasses of three different shapes, whenCMDmatches the component, then R2 flags no line.Commandclasses of different shapes and one of shape:unknown, whenCMDmatches the component, then R2 flags no line.@x ||= 1in a method, whenClassFactsis built, thenstate_writesis empty andvalue_semanticsis true.@x = 1in a method other thaninitialize, whenClassFactsis built, thenstate_writesholds that line andvalue_semanticsis false.Data.definevalue, whenClassFactsis built, thenvalue_semanticsis true.Specificationand writes an ivar outsideinitialize, whenSPECmatches the component, then R2 flags the write line.opportunities --jsonoutput for os-backlog, os-me and os-sdlc before this ticket, when the command runs after this ticket, then the group A rows are the same.Owner sketch
Non-goals
Do not change the 0.05 threshold,
Match#present?,mutates_state?, orCatalog.thresholds. Do not deleteRule::Unmeasured; tests and later patterns use it. Do not add a memoization fact (F18). Do not widenroutes_to.Retired tests
plugins/os-sdlc/tests/system_model_class_scale_patterns_test.rb#test_specification_value_semantics_shows_as_not_measurable
plugins/os-sdlc/tests/system_model_pattern_match_hand_counts_test.rb#test_b7_cmd_r2_result_shape_shows_as_not_measurable
Origin
plugins/os-sdlc/bin/system-model opportunities plugins/os-backlogprintsnot measurablefor P&F R2, CMD R2 and SPEC R2.not measurable← each rule usesRule::Unmeasured← ClassFacts has no field for the rule. DESIGN:docs/system-design-format/pattern-matching.mdsection 2, "The fact schema limits the catalog".plugins/os-sdlc/lib/os_sdlc/system_model/class_facts.rb,plugins/os-sdlc/lib/os_sdlc/system_model/extractor/fact_scan.rbVERDICT: CREATE
Work started on branch ticket-1551.
Resolution
Done: Facts exits, result_shape, state_writes and reader value_semantics added; rules ForbiddenExit, DivergentResult, BrokenValue replace Unmeasured for P&F R2, CMD R2, SPEC R2.
Evidence: main
d9a42338; suite 3422 runs 0 failures; opportunities on os-backlog, os-me, os-sdlc: group A rows unchanged, group C +1 at top_run.rb:30, present -1, unmeasured empty. Hand-finished after escalation 105 (test pinned the field list); design audit 6 defects and 7 preferences fixed or answered.Follow-ups: none