Conversation
|
| Branch | decomplex |
| Testbed | ubuntu-latest |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | leak-build-ms | Measure (units) x 1e3 | leak-count | Measure (units) | leak-run-ms | Measure (units) |
|---|---|---|---|---|---|---|
| benchmarks/concurrent/01_socket_throughput/bench | 📈 view plot | 6.63 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,370.04 units |
| benchmarks/concurrent/06_dynamic_spawn/bench | 📈 view plot | 5.40 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 3,784.26 units |
| benchmarks/concurrent/11_parallel_aggregation/bench | 📈 view plot | 5.19 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 7,209.46 units |
| benchmarks/concurrent/18_atomic_counter/bench | 📈 view plot | 5.19 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 50.69 units |
| benchmarks/inter-clear/04_concurrent_mvcc_fat_struct/bench | 📈 view plot | 5.31 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 304.09 units |
| benchmarks/sequential/03_alloc_throughput/bench | 📈 view plot | 5.25 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 11,536.05 units |
| benchmarks/sequential/13_soa_layout/bench | 📈 view plot | 5.32 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 758.16 units |
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #63 +/- ##
===========================================
- Coverage 92.71% 81.74% -10.98%
===========================================
Files 208 214 +6
Lines 52716 52943 +227
Branches 12381 12451 +70
===========================================
- Hits 48878 43280 -5598
- Misses 3838 9663 +5825
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
| Branch | decomplex |
| Testbed | ubuntu-latest |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | leak-build-ms | Measure (units) x 1e3 | leak-count | Measure (units) | leak-run-ms | Measure (units) |
|---|---|---|---|---|---|---|
| benchmarks/concurrent/03_atomic_contention/bench | 📈 view plot | 6.15 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 82.97 units |
| benchmarks/concurrent/08_pubsub/bench | 📈 view plot | 5.34 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 2,913.91 units |
| benchmarks/concurrent/13_rwlock_starvation/bench | 📈 view plot | 5.39 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,247.61 units |
| benchmarks/inter-clear/06_concurrent_mvcc_writer_pressure/bench | 📈 view plot | 5.40 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,834.29 units |
| benchmarks/sequential/05_string_builder/bench | 📈 view plot | 5.28 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 28,063.65 units |
| benchmarks/sequential/10_pool_vs_multiowned/bench | 📈 view plot | 5.21 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 748.92 units |
| benchmarks/server/01_tcp_kvstore/server | 📈 view plot | 5.38 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,002.61 units |
|
| Branch | decomplex |
| Testbed | ubuntu-latest |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | leak-build-ms | Measure (units) x 1e3 | leak-count | Measure (units) | leak-run-ms | Measure (units) |
|---|---|---|---|---|---|---|
| benchmarks/concurrent/02_concurrent_search/bench | 📈 view plot | 4.01 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 5.97 units |
| benchmarks/concurrent/07_stream_merge/bench | 📈 view plot | 4.02 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 31.05 units |
| benchmarks/concurrent/12_false_sharing/bench | 📈 view plot | 3.93 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,002.13 units |
| benchmarks/concurrent/19_atomic_ptr/bench | 📈 view plot | 3.95 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 91.51 units |
| benchmarks/inter-clear/05_concurrent_mvcc_pure_read/bench | 📈 view plot | 4.07 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 501.64 units |
| benchmarks/sequential/04_hashmap/bench | 📈 view plot | 4.02 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,689.84 units |
| benchmarks/sequential/09_frame_vs_heap/bench | 📈 view plot | 3.87 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,642.19 units |
| benchmarks/sequential/14_iterator/bench | 📈 view plot | 3.97 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 370.41 units |
|
| Branch | decomplex |
| Testbed | ubuntu-latest |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | leak-build-ms | Measure (units) x 1e3 | leak-count | Measure (units) | leak-run-ms | Measure (units) |
|---|---|---|---|---|---|---|
| benchmarks/concurrent/05_backpressure/bench | 📈 view plot | 5.47 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,595.74 units |
| benchmarks/concurrent/10_shard_vs_locked/bench | 📈 view plot | 5.26 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 60,004.57 units |
| benchmarks/concurrent/16_observables/bench | 📈 view plot | 5.18 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 96.18 units |
| benchmarks/inter-clear/03_concurrent_mvcc_vs_rwlock/bench | 📈 view plot | 5.97 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 314.37 units |
| benchmarks/sequential/07_pointer_chase/bench | 📈 view plot | 5.18 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 515.82 units |
| benchmarks/sequential/12_weak_ref_graph/bench | 📈 view plot | 5.20 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 258.84 units |
| benchmarks/server/03_pathological/server | 📈 view plot | 5.38 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,002.74 units |
|
| Branch | decomplex |
| Testbed | ubuntu-latest |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | leak-build-ms | Measure (units) x 1e3 | leak-count | Measure (units) | leak-run-ms | Measure (units) |
|---|---|---|---|---|---|---|
| benchmarks/concurrent/04_fanout_fanin/bench | 📈 view plot | 5.57 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 3,266.30 units |
| benchmarks/concurrent/09_kvstore/bench | 📈 view plot | 5.47 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 60,003.90 units |
| benchmarks/concurrent/14_nested_lock/bench | 📈 view plot | 5.43 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 388.92 units |
| benchmarks/inter-clear/02_concurrent_fsm_vs_stackful/bench_fsm | 📈 view plot | 5.55 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 154.99 units |
| benchmarks/inter-clear/02_concurrent_fsm_vs_stackful/bench_stackful | 📈 view plot | 5.29 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 198.60 units |
| benchmarks/sequential/11_pipeline_overhead/bench | 📈 view plot | 5.31 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 12,378.92 units |
| benchmarks/server/02_json_api/server | 📈 view plot | 5.42 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,002.42 units |
Net effect of the 107 nil-kill-prod commits (auto-inferred Sorbet type annotations + nil-kill tooling under gems/nil-kill) replayed as a single commit onto origin/master (22a1f0d). 143 files; git 3-way auto-merged 136, 7 resolved by hand. Conflict resolution (all were comment+sig collisions): kept master's updated method comment (authoritative for master's current logic) and took nil-kill-prod's refined sig (.void / concrete return types -- the value this branch adds). sorbet/config unions all three gem ignores (decomplex, fix-cache, nil-kill). Backup of pre-replay nil-kill-prod: branch nil-kill-prod-backup-20260515-205549 (pushed to origin). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Expanded collection workload (integration specs + fuzz matrix + full builds + module/ffi) cut param NoEvidence 148 -> 120 (13.8%); the "never executed by workload" bucket fell 324 -> 70. Remaining: 70 genuine no-test-coverage, 40 block/kwarg tracer blind-spot, 10 only-NilClass. Gemfile.lock: the squash-replay merged master's tty-screen / wisper / tty-reader deps; recorded after bundle install so bundle exec works. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reframes the section per the actual decomplexity goal: 1. Slot source widened past sig params: guard_collapse_rows iterates every (class,method,receiver) that type_normalizers guards, drawn from existing_sigs + unsigned_methods, not just declared-union sig params. 2. Ranking key is now N = the count of defensive `is_a?(Type)` normalizers on the slot (guards that collapse), not callsite volume / a synthetic score. callsite_count was the wrong axis. 3. Joins the two facts nil-kill already gathers: type_normalizers (N guards + their sites) x param_origins (dominant producer type + the K outlier producer sites) into one ranked, actionable row. Old score-ranked union_decomplexity_rows / union_members / canonical_member removed (superseded, not kept as dead code). Also fixes the type_normalizer collector: it cleared current_method on the FIRST nested `end` (any if/do/each), so every normalizer past the first block in a method was mis-attributed to method=nil -- defeating producer attribution. Now indentation-aware: only the `end` at the def's own column closes the method. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When a guard receiver is a local (not a param), attribute its producers without static points-to: - Collector tags each type_normalizer with the receiver's one-hop intra-method origin: `recv = annotate(x)` -> (call, annotate); `recv = @cached` / `@cached.is_a?(Type)` -> (ivar, @cached); param if no in-method assignment. - Report joins that origin to facts the tracer already gathers: runtime return classes per method name, runtime ivar/field classes. A singleton observed set => "always T, collapse, N guards die"; a multi-member set => "tighten that contract" with the members. This is the cheaper path: no points-to, no new static ivar-write index. It names the origin contract + its empirical type set ranked by guard count; it does NOT pinpoint the producing return statement (runtime is per-method aggregate) -- that residual stays deferred. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The `\Adef` match missed `private def x`, `private_class_method def self.x`, `module_function def ...` -- pervasive in this codebase (src/mir/promotion_plan.rb, escape_analysis.rb, ...). Every normalizer in such a method got method="" , which broke param/call producer attribution wholesale. Detect def with an optional visibility-modifier prefix and `self.`, capturing the bare name. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- An attr reader (node.type_info) is a zero-arg method; join its runtime return classes the same as a call (largest origin bucket). - Add a program-wide "highest-leverage origin contracts" rollup: guards summed by the contract feeding them, so the single highest-leverage type-to-add is explicit. Finding (data, not tooling): the is_a?(Type) guards are diffuse -- <=2 per receiver, each fed by an accessor whose own runtime return is itself T.untyped (the transitive wall). No single contract collapses many guards; Type-coercion is pervasively defensive vs T.untyped. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fix #1 of the review feedback (the one that made the ranking noise): the section keyed on the per-method local (`attr ti in lower_get_index`) so `.type_info`'s guards shattered into ~90 buckets of 2. Now resolve each receiver to its canonical contract (accessor `.type_info`, hash key `[:type]`, ivar, call) and SUM guards across every method that reads it. Ranking is now real: .type_info > .full_type > .return_type (matches ground-truth ordering), tens-to-hundreds not capped at 2. Also extend the origin tracer to hash-key origins (`recv = h[:type]`) -- one of the three contracts the feedback flagged as missing. Producer attribution still shows "unattributed (no runtime trace for this contract yet)" for accessor contracts -- that is feedback #2, gated on a runtime collect with accessor-return tracing; deferred until the in-flight autofix loop releases the tree. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The single-line `is_a?(Type)` + `Type.new(` scanner undercounted ~10x and mis-tracked methods. Replace with an AST walk over each def body (reusing @method_nodes for exact class/method/params): match every recv.is_a?(Type)/kind_of?(Type) CallNode (multi-line, !-wrapped, ternary, T.must forms all caught), resolve the receiver's one-hop origin on the AST (accessor / hash-key / ivar / call / param; a local resolved once through its in-method assignment node). src/ measure: 170 -> 350 guards captured, 0 blank-method (was ~half). .type_info 59/38 methods, .full_type 38/29, [:type] 31/22 -- correct ordering and real magnitude; aggregation in report.rb now meaningful. Also removes the spurious is_a?() call-origin noise. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Proven defect: Pprof::Profile#add_sample is `@samples << {...}`
(Array#<<, returns Array). propose_static_return_action's origin
heuristically guessed `String`, confidence strong, no blockers, and
high_confidence_static_return_origin? stamped it HIGH. It then failed
`srb tc` ("Expected String, got Array"). 17 of the loop's high-tier
rejects are this exact miscalibration -- a HIGH action that cannot
typecheck is a contradiction.
Gate HIGH on static guarantee:
1. origin confidence strong (unchanged);
2. NO blockers -- a blocker IS the static analysis reporting it
could not cleanly determine the return; the candidate is then a
guess, never HIGH;
3. a BARE static source (kind=static, not RBI/stdlib, code not a
self-evident literal/constructor) is a heuristic guess and
requires runtime corroboration (runtime_contradicts? already
rejected incompatible observed returns, so any observed return
agrees; none at all => unverifiable => REVIEW).
Literal/RBI/stdlib-backed static returns stay HIGH (provable). The
demoted ones become REVIEW, where the verified loop filters them --
a review rejection is by-design, not a calibration failure.
Scope: fixes the static-return-origin HIGH path (the proven, cited
defect). add_sig@high / void / noreturn are separate lower-rate
paths, not addressed here (avoiding over-broad risk mid-loop).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
88 sig blocks across 26 files (src/mir/*, src/tools/*, src/lsp/server) landed by the nil-kill verified loop. Each survived per-batch srb tc + dependent-spec verification with bisection; the full post-loop gate is srb tc clean and the entire prspec spec/ suite passes (the lone fmt_verifier_spec.rb:116 failure is pre-existing parallel-harness flakiness -- passes deterministically in isolation, and src/tools/formatter.rb is untouched by this batch). 86 candidate actions were rejected by verification and not applied. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Post-loop re-infer on the +88-sig tree, with the AST normalizer collector, canonical-contract Union Decomplexity aggregation, and the HIGH calibration gate all active. Calibration validated: fix_sig_return@high from static_return_origin 17 -> 1; add_sig@high -> 0; fix_sig_param@high -> 0. Remaining fix_sig_return@high (17) are the sound void/noreturn whole-program analyses, deliberately left HIGH. Next-cycle high-tier rejection from heuristic static origins projected ~0. Union Decomplexity now AST-accurate: .type_info 59 guards/38 methods, .full_type 38/29, .type 28/23 -- aggregated by canonical contract, 0 blank-method. Producer attribution still pending a runtime collect with accessor-return tracing (fix #2, deferred). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Union Decomplexity producer attribution (feedback #2) needed runtime types for accessor contracts (.type_info/.full_type/.type -- the top guard clusters). attr_reader-backed accessors have no traced `def`, so rt_returns was empty and every row read "unattributed". collect now captures the backing store: record_ivar_assignment (which already receives every instrumented ivar write) accumulates a per-(declaring class, ivar) runtime class set, dumped to ivars-*.jsonl, ingested as the `ivar_runtime` fact. The report joins an attr/ivar origin to the like-named ivar's runtime classes, aggregated globally by name (a `.type_info` accessor is one contract across ~38 classes; its producers are the union of every @type_info class set). attr origins try rt_returns first, then fall back to the ivar store. End-to-end spec added (attr contract -> @ivar runtime -> aggregated "N guards collapse | .type_info ... via @type_info assignments {Type, Symbol}"). 148 nil-kill specs green. Hash-key origins ([:type], 31 guards) still need a hash-write hook -- separate, narrower follow-up. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Picks up the collect-update join (f959f51). Even before a dedicated collect populates ivar_runtime, the attr->ivar-by-name fallback attributes accessor contracts from existing struct-field facts: .type_info etc. now show "via @<ivar> assignments (runtime) {...}" instead of "producers unattributed". A future full collect enriches this with the per-(class,ivar) ivar_runtime set. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause of the recurring garbage reports: running `infer` standalone reused runtime evidence collected against an OLDER src/ (post-squash + post-autofix line shifts). Stale method records fail to join the changed code -> 60% calls==0 -> NoEvidence ballooned to ~50%+. Partial evidence is useless; producing a report from it is worse than no report. Guards (default-on, single explicit override --allow-stale-runtime): - `infer`: abort if RUNTIME_DIR empty, or newest src/ mtime > newest runtime jsonl mtime (src changed after the last collect). - `report`: abort if evidence.json older than newest src/. Both print the exact full-collect command. This makes FULL, fresh evidence the structural default -- you can no longer silently infer or report on stale/partial runtime. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…moved) Re-collected the full expanded workload on the current src/ (post +88-autofix, post-squash), then infer+report through the new freshness guards (all passed -- runtime newer than src/). NoEvidence corrected: Param 33.4%->13.7%, Returns 56.8%->32.6%, Arrays 75.9%->53.7% (Struct/ivar 72.3% unchanged -- genuine no-static-no-runtime floor). ivar_runtime now populated, so accessor producer attribution in Union Decomplexity is live. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Measured against fresh evidence: 161 of 204 Heterogeneous param slots (79%) have every observed class in ONE namespace -- 134 AST::*, 23 MIR::*, 4 FsmOps::*. They are not "untyped"; they are one node-union each. New "### Node-Union Alias Candidates" Hygiene section lists, per namespace alias (AstNode/MirNode/...), the count and EVERY param location (sorted grab-bags-first by distinct node-type count), so a single T.type_alias per namespace can type them all at once. Pure report analysis (reuses the Heterogeneous classifier); no proposer/codegen yet. 149 nil-kill specs green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Investigation of the "89 NoEvidence returns": only ~7 were truly
evidence-free. classify_return_untyped_cause had the same
runtime-evidence-discard bug already fixed for params:
1. Executed methods observed returning only nil fell through to
NoEvidence. They are determinable (void/NilClass/T.nilable) ->
Refused/Pending per the legend's "void/unused".
2. The `unless callees.empty?` transitive-wall branch returned
NoEvidence WITHOUT consulting the runtime `returns` set, so methods
observed returning {Array,Hash,nil} or 60 AST node types were
labeled NoEvidence. Now it only short-circuits to NoEvidence when
there is no runtime non-nil evidence; otherwise it falls through to
the runtime Heterogeneous / WeakEvidence verdict.
Returns NoEvidence 89 -> 15 (the residual 15 are the genuine floor:
never-executed + void-bang methods with no traced return).
Refused/Pending 83, WeakEvidence 73, Heterogeneous 65.
Verified params do NOT have this bug (NoEvidence params bucket only as
calls==0 / block-kwarg-untraced / only-nil; the a4165b4 honest
classifier already consults runtime). No param change. 4 new
return-classifier specs; suite green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause of 654 (72%) struct/ivar NoEvidence (not coverage, not a classifier discard like returns): record_struct_field stored the raw caller path, which under source instrumentation is the instrumented copy. record_call maps via abs_path; record_struct_field did not. The separate `infer` process has no NIL_KILL_INSTRUMENTED_ROOT, so its struct ingest `target_path?(obs["path"])` rejected EVERY struct row -> struct_field_runtime stayed 0 (verified: scratch collect produced 130 struct rows, all with instrumented paths infer drops). Fix 1 (tracer): record_struct_field maps the path via abs_path, same as record_call -> infer ingests it (verified: path now src, target_path? true). Fix 2 (classifier): classify_struct_ivar_untyped! never consulted struct_field_runtime/ivar_runtime -- it bucketed every untyped field PropagationGap-or-NoEvidence. Now honest (same as returns/params): single observed type or only-nil -> Refused/Pending; > MAX_UNION -> Heterogeneous; >1 -> WeakEvidence; action-resolvable -> PropagationGap; genuinely none -> NoEvidence. Effect lands after a fresh full collect (struct_field_runtime is empty in current evidence until re-collected). 149 specs green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fresh full collect with the abs_path fix: struct_field_runtime 0 -> 27,458 entries. Struct/class fields & ivars NoEvidence 654 (72.3%) -> 205 (22.7%); Refused/Pending 243 -> 604, PropagationGap 7 -> 52, Heterogeneous 0 -> 37. The 72% "no evidence" was the instrumented-path ingest bug, not test coverage -- now real. Collections NoEvidence ~unchanged (350) -- that is the separate sig-line vs mutation-site join-key bug, still outstanding. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
classify_collection_untyped! joined collection_runtime by [path, sig/decl line, name], but runtime records the OBSERVATION / mutation-site line, so nearly every join missed -> false NoEvidence. Join on owner identity instead: param name within the method's line-range, method name for returns, class-qualified "Class.field" for struct fields. Collections NoEvidence 350 -> 302 (52% -> 45%); Heterogeneous 146 -> 189, Refused/Pending 107 -> 129 (~48 reclassified + others corrected). The line-key bug is fixed. The residual 302 is NOT a join bug: collection_runtime only captures collections seen through the mutation/iteration hooks, so read-only params, build-and-return values, and tlet collections genuinely have no element evidence -- a deeper collection-coverage limit, separate and documented. 149 specs green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ence The residual collection NoEvidence (302) was collections the mutation/iteration hooks never see: read-only params, build-and-return values, tlet collections. But the tracer ALREADY captures element classes/shapes at the call/return boundary (param_elem/return_elem/*_kv/*_shapes) and at struct construction (struct_field_runtime.elem_classes) -- the classifier just ignored them. Fold all of those into the per-slot element/shape evidence alongside the owner-identity collection_runtime join. No collect change (data already present). Collections NoEvidence 350 -> 302 (join fix) -> 186 (27.8%); Heterogeneous 189 -> 246, WeakEvidence 44 -> 79, Refused/Pending 129 -> 131. Residual 186 are collections with genuinely zero element observation on any path (only-empty / never crossing an instrumented boundary). 150 specs green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New "### Untyped Evidence Gaps" Hygiene section turns the 526 residual
NoEvidence into a triage list grouped by root cause, each with file
locations:
- 284 never run -> dead code / missing test (72 param, 7 return,
205 struct field never constructed)
- 186 collection no-elements -> only-empty / off any instrumented path
- 38 arg untraced -> block/kwarg/splat (tracer types positional only)
- 10 only-nil -> likely unused/optional-dead
- 8 discarded-return -> should likely be .void
Counts reconcile exactly with the cause table's NoEvidence column.
Refactor: collection slot+evidence building extracted to
collection_evidence_slots (single source of truth) so the classifier
and this breakout cannot drift. 149 specs green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A#dead's return is unused program-wide -> classify_return_untyped_cause returns Refused/Pending (void), correctly NOT a NoEvidence gap. The production code was right; the spec over-asserted. Assert only the param is a never_run gap. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
What NotImplemented was: return-expression AST forms analyze_return_origin didn't model (the blocker "unknown return expression X"). In practice 28 returns: 25 were `@x = v` / `x = v` (assignment-as-return), 3 yield. Fix: return_sources_for now recurses into a write node's RHS (Instance/Local/Class/Global/Constant WriteNode) -- `@x = v` types exactly like `return v`. That resolves the 25 via their RHS evidence (static + the runtime/ivar fallback already in place). The residual 3 are `return yield` -- block-return typing, which is the observational wall, NOT single-file implementation debt. So the "NotImplemented" cause is no longer meaningful: removed from UNTYPED_CAUSES, the legend, the classifier branch, the NOT_IMPLEMENTED_RETURN_NODES list, and the actionable/inherent summary. Slots that were NotImplemented now classify by their real evidence (typed via RHS, or honest NoEvidence/Heterogeneous for yield). Cause table is now 5 columns. Effect needs a re-infer (return_origin regenerated by the new recursion); no re-collect. 148 specs green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
#1 Loader gap: install_instrumented_require_hook only overrode require_relative. Any src/ file reached via plain `require` (bare name via $LOAD_PATH, or a path) ran the REAL un-wrapped source -> no record, in every process. Now also override Kernel#require: resolve the name via $LOAD_PATH and, if it maps to a target src file with an instrumented copy, load the copy. Pristine loaders captured before prepend so transitively required files are still redirected (no global reentrancy flag, which would re-open the gap one level down). #2 Return-in-block: methods with a `return` inside an iterator/`proc` block were punted to the (fragile) TracePoint fallback. Such a return is a non-local METHOD return; rewriting it to `throw __nil_kill_return_tag` reproduces it exactly and records the value. collect_return_edits now recurses into BlockNode; only LambdaNode / `lambda {}` scopes (where `return` is lambda-local) stay punted (contains_return_inside_lambda? replaces contains_return_inside_block?). #3 Coverage join: a process that ran REAL src for a file that WAS instrumented had its src-space line numbers mis-translated through the instrumented line-map, fabricating "covered" lines. coverage_src_lines now applies the map ONLY when the raw Coverage key is under the instrumented root; real-src coverage is identity. Adds end-to-end specs (#1 plain-require redirect, #2 block-return recorded, #2 lambda-return still punted) and a unit spec (#3 line-map gating). Full nil-kill suite green (150 + 14 runtime_trace). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The #1 require-redirect loaded the instrumented copy by its instrumented path, so __FILE__/__dir__ pointed into the parallel instrumented tree. Every __FILE__-relative resource read broke -- e.g. transpiler.rb:121 `File.read(File.dirname(__FILE__)/../../zig/ runtime/runtime-footer.zig)` raised Errno::ENOENT, failing every ZigTranspiler.transpile under instrumentation (a latent bug the require_relative redirect also had; widening to `require` exposed it across the spec workload). Fix: load_instrumented reads the instrumented SOURCE and compiles it under the REAL path via RubyVM::InstructionSequence, so __FILE__ / __dir__ resolve against real src while the executed code is the instrumented transform. require idempotency preserved via $LOADED_FEATURES + an instrumented_loaded set; on any load-strategy error it falls back to the real file (workload stays sound). Coverage join (#3) now keys off instrumented_loaded (Coverage attributes lines to the real compile path) instead of an instrumented-root path prefix. Adds a regression spec proving __FILE__-relative resource reads work through a redirected instrumented copy. Full nil-kill suite green (150 + 15 runtime_trace). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
With the require-hook (#1, load instrumented under real path), return-in-block rewrite (#2), and coverage-join hardening (#3) all in effect, collect_ran_untraced 13 -> 0. No method's body provably executes during collect without producing a record anymore: the require loader gap is closed (every reached src file runs the wrapped transform), block/proc returns are captured, and the coverage join no longer fabricates "body-covered" from real-src line numbers. Residual reclassified honestly: untraced_covered 44 (workload-input gap -- file exercised by the aggregate suite, this method's body not hit in THIS collect), arg_untraced 52, only_nil 11, struct_unobserved 23, collection_no_elements 162. Total NoEvidence 326 -> 292. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The bucket detected methods whose body provably ran during collect yet
produced no record ("real tracer/trace-plan defect"). Every cause was
fixed: def-line-vs-body classifier, the require loader gap (load
instrumented under real path), return-in-block capture, coverage-join
hardening, and __FILE__ resolution. The bucket has been 0 since; the
category, the collect-run-coverage split, and the now-dead
collect_ran? / collect_coverage_index helpers are removed.
never_run_reason is now a pure SimpleCov split (untraced_covered /
unseen / never_run). Deleted the two specs that exercised the removed
collect_ran mechanism; kept the SimpleCov-split spec. Report gap table
loses the column; totals unchanged (292).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Evolves triage from "group dark arms by method" to "classify each
dark arm by which testing modality can reach it". Four buckets:
fuzz_axis valid program, unseen shape (case-on-AST, &&/||,
live if/while body) -> one fuzz axis covers a
family + a mutant
negative_spec the arm raises/diagnoses -> invalid-program only;
fuzz cannot reach it by construction
ffi_integration extern/require/module boundary -> needs a real
external artifact a fuzzer can't synthesize
accept_defensive narrow inert residue (synthetic else, empty, nil)
-> annotate + accept; human-confirmed, never
auto-accepts a reachable arm
Classification is AST-structural, NOT a regex over the arm line
(the rejected fake-value grep): the SimpleCov parent tuple gives the
decision kind, and the arm's (line,col) span is matched to an AST
node whose subtree is inspected for raise/FFI. Two PER-PROJECT
LEXICON constants (FFI boundary methods, diagnostic message names)
are the only project-specific knobs -- the algorithm generalizes,
swap the lexicon per codebase.
Result over the 3 lowering files: fuzz_axis 590, accept_defensive
296, ffi_integration 53, negative_spec 16. This is the work plan:
not one fuzz test, not tons of unit tests, not an integration suite
-- overwhelmingly fuzz axes, a bounded FFI .cht set, a tiny
negative-spec set, a human-confirmed accept residue.
Lives entirely in the coverage tool; decomplex untouched and stays
static/zero-runtime (boundary preserved).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 6 proposed fuzz_axis matrices, built and run. Result:
BUGS (3 real, all OPEN — deliberately not fixed):
catch_allocator_matrix surfaces B1: `r = maybe("") OR fbv` (frame
fallback, heap success) -> Invalid free (invariant #9).
catch_reassign_matrix surfaces B2 (leak: reassign-through-OR on
success) and B3 (segfault: struct field fallback reads itself
mid-cleanup -> UAF).
All three are the catch/OR-rescue allocator-identity family — the
exact P0 cluster branch_gap_triage flagged (infer_catch_value_
allocator 12/12 dark). The modality plan predicted it; the targeted
matrices confirmed real memory-safety bugs there.
Documented in docs/agents/fuzz-matrix-surfaced-bugs.md; the failing
:pass cells are the live tickets.
CLEAN: capability_wrap_matrix (3/3, +3 in_dev), match_matrix (6/6),
indexed_assignment_matrix (20/20), binary_op_matrix (21/21) — after
fixing two template-correctness bugs of mine (off-by-one list index;
inverted string lt/gte oracle). These were my noise, not CLEAR bugs.
COVERAGE: 68 cells moved mir_lowering branch coverage 673 -> 671 (2
arms). Verified real (COVERAGE=1 fuzz run writes a transpile-tests
resultset entry with mir_lowering data; branch_gap_triage merges
it). This reproduces the "92 programs -> 50 arms" result more
starkly: feature-level fuzzing finds bugs well but is NOT a
branch-closure lever — the dark arms need exact triggering
type_info, and/or the fuzz_axis bucket is over-assigned vs
reachability. Full analysis in the forensic doc.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Feasibility verified: the @target==:bc branches in mir_lowering fire during MIRLowering#lower_program (Ruby), before the bytecode VM. The incomplete _bc_runner is irrelevant -- we never execute, never require BcEmitter to succeed; a program that hits `raise Unimplemented` in a :bc arm still covered that arm. Per-file rescue, zero new programs. Result: mir_lowering dark arms 671 -> 656 (15 closed) by re-lowering the existing corpus with target: :bc. Cost comparison vs the 6 hand-written matrices: 15 arms / 0 new programs vs 2 arms / 68 new programs (~250x more cost-efficient). But still only 15/671 -- which is the decisive evidence, from a second direction, that the remaining ~581 fuzz_axis-bucketed arms are NOT closable by program generation in any backend mode. They are internal-IR-state / defensive guards: the fuzz_axis bucket is over-assigned and mir_lowering branch closure is a re-triage problem, not a test-generation problem. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New `bc-lower-coverage` job mirroring the transpile-tests / tools-fuzz coverage pattern: COVERAGE=1, run tools/bc_lower_coverage.rb, collate, upload to Codecov with flags `ruby,bc-lower`. Pure Ruby -- no Zig, no clear build (the @target==:bc arms are covered during MIRLowering, before the bytecode VM; the incomplete _bc_runner is never executed), so the job is minimal and fast. fail_ci_if_error: false, matching the other coverage jobs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rebuild both from SAMPLED axes to EXHAUSTIVE enumeration of the
dispatch's own when-labels, with surface syntax confirmed from
lexer/transpile-tests (not guessed):
binary_op: every comparison op incl. LTE/GT (was missing), POW
int+float (** confirmed), MOD, concat, OR. Symbol-path EXCLUDED
-- CLEAR has no surface symbol literal, so those {EQ,NEQ} arms
are not source-reachable (accept, not fuzz). 21 -> 30 cells, all
clean.
capability_wrap: one cell per ft.sync x ownership label
{locked, write_locked, always_mutable, versioned, atomic-ptr,
multiowned, shared:locked} -- all forms confirmed from
transpile-tests; zero in_dev. 6 pass.
Surfaces B4: @indirect:atomic + WITH EXCLUSIVE (both the compiler's
own directed forms) -> invalid Zig `no field 'ctrl' in AtomicPtr`.
The atomicPtrCreate dark arm of compose_capability_wrap is broken.
OPEN, not fixed.
Coverage delta from the provably-complete enumeration: mir_lowering
656 -> 653 (3 arms). Fourth independent confirmation that branch
coverage is not closable by test generation; documented in the
forensic. Fuzz's value here is bug-finding (4 bugs on dark arms),
not coverage.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…riven The project's primary goal made concrete: not "this decision is duplicated N times" (scatter) but "THIS contract is the SOURCE of N defensive type/nil decisions -- fix the contract once, the cluster dies." Attributes every is_a?/kind_of?/instance_of?/nil?/respond_to?/ safe-nav guard to the canonical root contract of its subject, resolving proximate locals through INTRA-procedural assignment (reuses the derived-state def-use idea + semantic-alias-style canonicalization). Cross-procedure pressure stays nil-kill's by the recorded boundary -- not re-implemented (decomplex stays CFG-free). Ranks contracts by decisions x methods; unresolved ~local bucket sorts last (that residue needs cross-proc = nil-kill). New tier-1 report section. Self-tested: decision_pressure_test (5), full suite 44/124/0. Verified on src/ (93 files): top contract `.type_info` drives 274 defensive decisions across 94 methods; the type-contract family (.type_info 274, .value 110, .full_type 33, .type 28, .return_type 27, [:type] 29) dominates the head of the ranking -- exactly the "one loose contract -> hundreds of conditionals" the user predicted. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Promotes tools/branch_gap_triage from a one-off probe to a
first-class gem (named `prick` -- it pricks holes in your codebase).
A flat "673/2732 uncovered" is unactionable; prick categorizes every
dark branch arm and overlays fix-churn so the actionable slice
surfaces.
OWNS the gap-categorization analysis (AST-structural per-arm
classifier, dead/live decision split, categorical rollup). CONSUMES
the sibling fix-cache gem for churn (require_relative, not
re-derived) + an optional nil-kill verdict for type_norm. Boundary
held: it aggregates, it does not re-implement.
Categories: type_norm (confirm w/ nil-kill -> removable), dead
(delete, complexity down), defensive (accept), ffi, diagnostic
(negative spec), genuine (the real gap). New signal: genuine x
fix-churn = "bugs highly likely HERE".
Validated on src/mir/{mir_lowering,control_flow,escape_analysis}:
935 dark arms -> diagnostic 305, genuine 273, type_norm 229, dead
68, ffi 46, defensive 14. Bugs-likely #1 mir_lowering (187 genuine x
churn 1.0); top sites hoist_alloc / owned_value_temp_needs_cleanup?
-- the exact methods that produced B1-B4. The synthesis points at
real bugs.
Honest v0 caveats (documented in design.md): diagnostic over-greedy
(subtree-wide raise), type_norm under-counted (no intra-proc
local->accessor resolution yet). Shape + bug-likely join are sound;
percentages are candidates to tighten. Self-tested 6/30/0 incl. a
real stdlib-Coverage resultset integration + temp-git churn overlay.
sorbet: ignore gems/prick/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Addresses three valid critiques: 1. Repo-relative + linked paths. Was absolute (/home/yahn/cheat/...); now [src/x.rb:226](src/x.rb#L226). 2. Report leads with the actionable artifact. Dropped the unhelpful per-file %-table; the headline is now "Top True Gaps (N) -- test these, ranked by fix-churn": every genuine reachable arm, linked, sorted by the file's fix-cache churn score. Compact category summary follows as context. 3. General gem, no baked-in repo lexicon/jargon. Category action text is now testing-strategy-neutral (no .cht / fuzz / nil-kill). The FFI/external-boundary lexicon ships EMPTY in the gem and is caller-supplied via --ffi (CLEAR's set lives in exe/prick, not the library). DIAGNOSTIC_MIDS is general Ruby. The engine (categorize uncovered branches, rank genuine by consumed fix-cache churn) is general to any Ruby project. classifier: ffi_boundary injected (kwarg, default []); doc comment de-jargoned + rename-mangled history ref removed. rollup: emits top_gaps (genuine arms ranked by churn) instead of file-level bug_likely. README/design.md rewritten generic + caveats kept. Tests updated for new signatures/shape; 6/30/0. report.md regenerated (Top True Gaps headline, linked paths). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
report.md lives at gems/prick/report.md, so `src/x.rb` resolved to the nonexistent gems/prick/src/x.rb. Report now computes the href relative to the OUTPUT file's directory (link_base = dirname of --output; defaults to repo root for stdout). Link is now ../../src/mir/mir_lowering.rb#L226 from gems/prick/report.md; display text stays the readable repo-relative path. Verified target resolves; 6/30/0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gems/prick -> gems/slopcop: directory, lib/, exe/, gemspec, module Prick -> SlopCop, require paths, CLI name, sorbet ignore entry, and README/design branding. Tests unchanged (6 runs / 30 assertions / 0 failures); CLI smoke-tested. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
other_type is sig-typed Type and nil-kill confirms the runtime producer is always Type, so both `other_type.is_a?(Type) &&` guards are provably dead. Removing them is behavior-preserving (nil-kill Union Decomplexity: "always Type: collapse, all 2 die"). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ti is sig-typed Type and nil-kill confirms the runtime producer is always Type, so `ti = Type.new(ti) if ti && !ti.is_a?(Type)` and `ti = nil unless ti.is_a?(Type)` are dead. Removing them is behavior-preserving (nil-kill: "always Type: collapse, all 2 die"). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
No CLEAR transpiler bugs encountered. Documents that only nil-kill "always Type" verdicts are safe standalone guard collapses (#55,#56 done); the other 18 tracked contracts are legitimately nilable or unattributed -- their is_a?(Type) checks are correct discriminators, so they need the producer-side propagation typing program, not blind guard deletion. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ull_type= 62 sites set `.full_type = :Sym`, which full_type= silently launders via Type.new -- the source of the nilable/non-Type pollution that forces is_a?(Type) re-guards across 38 reader methods. Pass Type.new at the producer instead (runtime-identical; the setter already did exactly this). Step 1 of the source-tightening program for #45. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pipeline_rewriter producer conversion is safe (uniform Locatable receivers, landed f29524a). The same transform on annotator.rb et al. regressed 1799 specs: heterogeneous full_type receivers, some of which genuinely store/read a raw Symbol. The source fix needs per-receiver typing, not a blanket caller rewrite. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
nil-kill-attributed Locatable#full_type= producer site. Wrap the Symbol RHS in Type.new (runtime-identical to the setter's existing launder). Validates the per-site approach for the 22-site producer worklist nil-kill enumerates for this contract. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 10 nil-kill-attributed Locatable#full_type= producer sites (303,307,368,445,503,611,645,911 + the 1623/1686 case exprs) wrapped in Type.new -- runtime-identical to the setter's launder. Scoped strictly to nil-kill's worklist (other :Void/:Bool sites untouched). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 11 nil-kill-attributed Locatable#full_type= producer sites in annotator.rb wrapped in Type.new (3838 case fixed per-branch, no double-wrap). Surfaced real slop: visit_Slice declared `returns(Symbol)` but is a side-effecting annotator whose return is unused -- only "satisfied" by the pre-launder Symbol. Corrected to `.void`, matching its sibling visitors (visit_HashLit/_YieldExpr). Completes nil-kill's 22-site producer worklist for this contract. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
With producers passing Type (prior commits) the Locatable#full_type= setter guarantees the .type_info contract is strictly nil|Type, so `x.type_info.is_a?(Type)` is a redundant nil-check. Collapsed to nil-safe access (&. / truthiness / drop the dead Type.new branch) across function_analysis(5), escape_analysis(3), generic_analysis(3), mir_checker(1), mir_lowering(2). The 3 remaining sites (annotator.rb:2793 final_type; 6522/6618 classify_og_kind param) are different contracts, intentionally untouched. Completes #45. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
.full_type and .type_info return the same @type_object, so the producer work in #45 already guarantees this contract is strictly nil|Type. All 14 .full_type is_a?(Type) reader guards collapsed to nil-safe access (&. / truthiness; dead Type.new branches dropped, incl. the 5014 block guarded by an outer non-nil check). Gates green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Locals sourced from `x.type_info rescue nil` are provably nil|Type post-#45, so the `Type.new(ti) if ti && !ti.is_a?(Type)` coercions are dead and `if ti.is_a?(Type)` is a redundant nil-check. Collapsed: escape_analysis per_fn_scan!(238), e2_loop_carry_names!(decl_ti, outer_ti), e3_mark_carry_expr!(904,910); control_flow _collect_share_moves; promotion_plan stamp_field_pre_cleanups!. #52's 327/374 are .symbol.type (heterogeneous, = #47), left alone. Gates green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Type#wrapped_type is structurally nil|Type (type.rb:1030), and both promotion_plan sites are guarded by `next unless inner_ti`, so `inner_ti = Type.new(inner_ti) unless inner_ti.is_a?(Type)` is dead. Removed. (annotator.rb:1325 nil-kill grouped here is actually a heterogeneous hash value b[:unwrapped_type], not wrapped_type -- left alone.) Gates green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
11 items landed (seam-backed nil|Type contracts -- safe behavior-preserving collapses, all gate-verified). Remaining 9 are a major blocker: genuinely heterogeneous contracts (.type holds FunctionSignature/String, .return_type holds Hash/Proc, final_type is Symbol|Type by design) where is_a?(Type) is a real discriminator -- collapsing is not behavior-preserving and needs a deep per-contract retype, not a deslop commit. Documented, not faked. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
| Branch | decomplex |
| Testbed | ubuntu-latest |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | leak-build-ms | Measure (units) x 1e3 | leak-count | Measure (units) | leak-run-ms | Measure (units) |
|---|---|---|---|---|---|---|
| benchmarks/concurrent/01_socket_throughput/bench | 📈 view plot | 5.49 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 1,558.84 units |
| benchmarks/concurrent/06_dynamic_spawn/bench | 📈 view plot | 5.23 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 3,689.04 units |
| benchmarks/concurrent/11_parallel_aggregation/bench | 📈 view plot | 5.19 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 7,221.62 units |
| benchmarks/concurrent/18_atomic_counter/bench | 📈 view plot | 5.21 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 45.75 units |
| benchmarks/inter-clear/04_concurrent_mvcc_fat_struct/bench | 📈 view plot | 5.46 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 332.10 units |
| benchmarks/sequential/03_alloc_throughput/bench | 📈 view plot | 5.33 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 11,550.72 units |
| benchmarks/sequential/13_soa_layout/bench | 📈 view plot | 5.40 units x 1e3 | 📈 view plot | 0.00 units | 📈 view plot | 757.18 units |
User's model validated (target nil|Type|FunctionSignature; String/ Symbol slop; sigs -> T.any(Type,FunctionSignature)). Carrier disambiguated: Literal#type is a separate token-kind field, no blast radius. Real obstacle is semantic, not mechanical: the 11 is_a?(Type) sites double as a resolved-vs-unresolved gate, so normalizing the seam changes which decls get processed. Documented as the spec for a focused reviewed PR; not auto-run (gates-green != provably-correct for semantic change). #48-#51,#53,#57,#59 same. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Evolves triage from "group dark arms by method" to "classify each dark arm by which testing modality can reach it". Four buckets:
fuzz_axis valid program, unseen shape (case-on-AST, &&/||,
live if/while body) -> one fuzz axis covers a
family + a mutant
negative_spec the arm raises/diagnoses -> invalid-program only;
fuzz cannot reach it by construction
ffi_integration extern/require/module boundary -> needs a real
external artifact a fuzzer can't synthesize
accept_defensive narrow inert residue (synthetic else, empty, nil)
-> annotate + accept; human-confirmed, never
auto-accepts a reachable arm
Classification is AST-structural, NOT a regex over the arm line (the rejected fake-value grep): the SimpleCov parent tuple gives the decision kind, and the arm's (line,col) span is matched to an AST node whose subtree is inspected for raise/FFI. Two PER-PROJECT LEXICON constants (FFI boundary methods, diagnostic message names) are the only project-specific knobs -- the algorithm generalizes, swap the lexicon per codebase.
Result over the 3 lowering files: fuzz_axis 590, accept_defensive 296, ffi_integration 53, negative_spec 16. This is the work plan: not one fuzz test, not tons of unit tests, not an integration suite -- overwhelmingly fuzz axes, a bounded FFI .cht set, a tiny negative-spec set, a human-confirmed accept residue.
Lives entirely in the coverage tool; decomplex untouched and stays static/zero-runtime (boundary preserved).