Skip to content

Redmine 6.x regression sweep — baseline-drift over a release line

Date: 2026-05-21. Rigor: v0.1.9 (master). Target: redmine/redmine, 13 tags 6.0.06.1.2.

Second run of the rigor-regression-sweep procedure, after the Mastodon v4.5.x sweep (20260521-mastodon-v4.5-regression-sweep.md). Broadens the empirical corpus and — unlike Mastodon’s flat-zero result — produces a real baseline-breach event to dissect.

  • Blobless clone of redmine/redmine; 13 tags in release order: 6.0.0 … 6.0.9 (a 10-patch series) plus 6.1.0 6.1.1 6.1.2 — so the range spans both a patch-stabilisation window and the 6.0 → 6.1 minor-feature bump (per the SKILL’s Phase 1 guidance: prefer a feature-spanning range over a pure patch series).
  • Frozen config, identical to the Mastodon sweep for cross-project comparability: paths: [app, lib], exclude: [vendor, tmp], severity_profile: lenient, signature_paths: → the rigor-activesupport-core-ext sig/ bundle (absolute path).
  • Baseline at 6.0.0: 145 diagnostics / 59 buckets.
  • app + lib scope: 322 .rb files at 6.0.0, 331 at 6.1.2; 112 .rb files changed across the range.

Result — a flat 22, then a +6 breach at 6.1.2

Section titled “Result — a flat 22, then a +6 breach at 6.1.2”
Tagrawsilencedsurfaced
6.0.0 … 6.1.1144–145122–12322
6.1.214511728

Two things to explain: the constant 22, and the +6 at 6.1.2.

The constant 22 — parse errors, un-baselineable

Section titled “The constant 22 — parse errors, un-baselineable”

The 22 diagnostics surfaced at every tag — including the baseline tag 6.0.0 itself — are parse errors (unexpected '<', ignoring it, unterminated string meets end of file, …), and all 22 come from one file: lib/generators/redmine_plugin_model/templates/migration.rb. That file is a Rails generator template — a .rb-extension file that is actually an ERB template (<%= … %>), the survey-§8a pattern from 20260519-oss-library-survey.md.

They surface at the baseline tag because parse errors carry no rule, and Baseline.from_diagnostics only buckets diagnostics that have both a qualified_rule and a path — so a rule-less diagnostic is never recorded in the baseline and Baseline#filter always passes it through (unkeyable set). This is a methodology finding: the baseline mechanism has a blind spot for parse / internal-analyzer errors. The fix is config-side — exclude: the generator-template directory — and the rigor-regression-sweep and rigor-project-init SKILLs should say so.

The +6 at 6.1.2 — false positives from FP-pattern churn

Section titled “The +6 at 6.1.2 — false positives from FP-pattern churn”

6.1.1 → 6.1.2 surfaced 6 new call.undefined-method diagnostics, all in app/models/issue.rb:

issue.rb:1763 undefined method `user' for nil
issue.rb:1799 undefined method `user' for nil
issue.rb:2008 undefined method `user' for nil
issue.rb:2008 undefined method `notes' for nil
issue.rb:2009 undefined method `private_notes' for nil
issue.rb:2019 undefined method `notes_and_details_empty?' for nil

Every one is @current_journal.METHOD inside an if @current_journal (or @current_journal && …) guard:

if @current_journal
@copied_from.init_journal(@current_journal.user) # flagged
end

These are false positives. The code explicitly guards the instance variable; Rigor does not narrow an instance variable to non-nil through a truthy if @ivar guard, so @current_journal stays Journal | nil inside the consequent and every method call on it reports … for nil. This is the same family as the cluster-4 G2 ivar-narrowing gap (20260521-mastodon-cluster4-flow-folding-triage.md).

They surfaced at 6.1.2 — not earlier — because Redmine’s 6.1 development added more guarded-@current_journal call sites to issue.rb, pushing the (app/models/issue.rb, call.undefined-method) bucket over its 6.0.0 baseline count; WD4 ALL-or-NOTHING then surfaces the whole bucket.

  1. The sweep detected a real baseline-drift event — unlike Mastodon’s flat zero. The drift mechanism works: a project that adopted Rigor at 6.0.0 would have seen CI go from clean to “6 new diagnostics in issue.rb” at 6.1.2.
  2. But the breach is false positives, not bugs. The experiment shows baseline drift catches FP-pattern accumulation too: as normal development adds more code matching a known false-positive shape (guarded ivar access), the bucket breaches even though no real defect was introduced. This is an argument for the severity_profile: lenient default (the 6 are error only because call.undefined-method stays error under lenient; they would still surface) and for the ivar-narrowing engine fix below.
  3. Engine gap surfaced — ivar narrowing through a truthy guard. if @ivar; @ivar.method does not narrow @ivar to non-nil. A concrete, recurring false-positive source; queued (see below).
  4. Parse errors are a baseline blind spot. Rule-less diagnostics bypass the baseline and surface forever. Generator .rb templates are the common source; exclude: them.

rigor triage on 6.0.0 without the AS bundle (320 diagnostics) — to check the heuristic catalogue on a second project:

  • H1 activesupport-core-ext — 155 diagnostics, html_safe×94 wrap×25 deep_dup×5 …; action correctly names plugins: activation (the ADR-25 H1 fix landed this cycle). Correct.
  • H4 activerecord-relation-misinference — 4, names rigor-activerecord. Correct.
  • H5 systemic-file-cluster — pinpoints the 22-parse-error file lib/generators/redmine_plugin_model/templates/migration.rb exactly. Correct.
  • H6 genuine-bugs — 3 call.argument-type-mismatch. Plausible.

The heuristic catalogue holds up on a second, structurally different project.

  • Engine — ivar narrowing through if @ivar / @ivar && … truthy guards. Recurring false positive; same surface as cluster-4 G2 (control-flow-analysis.md § “mutation effects” / narrowing). Queued.
  • SKILLrigor-regression-sweep (and rigor-project-init) should note that generator .rb templates produce un-baselineable parse errors and belong in exclude:.

~/repo/ruby/rigor-survey/_redmine-sweep/ holds sweep.sh, tabulate.rb, rigor-no-as.yml, the frozen baseline.yml, and reports/<tag>.json.

© 2026 TypedDuck. Licensed under CC BY-SA 4.0.