Skip to content

fix(checks): stop ImportFanOutOutlier flagging shared utilities (CD-333) - #217

Merged
TAJD merged 2 commits into
mainfrom
fix/cd-333
Aug 11, 2026
Merged

fix(checks): stop ImportFanOutOutlier flagging shared utilities (CD-333)#217
TAJD merged 2 commits into
mainfrom
fix/cd-333

Conversation

@TAJD

@TAJD TAJD commented Aug 11, 2026

Copy link
Copy Markdown
Owner

The problem, measured across the estate

Design.ImportFanOutOutlier flags a file whose fan-in or fan-out is more than three standard deviations above the project mean. Any successfully shared module is a fan-in outlier by construction, so the fan-in half flags leaf utilities for being reused.

Ran the current check against all five local repos that use cofferdam:

Repo Fan-in findings
personal-blog BaseHead.astro, Footer.astro, Header.astro, game-logic.ts
bestefforttools ui/button.tsx, config.ts, lib/utils.ts, types/hyrox.ts, types/workouts.ts
poker-puzzle Button.tsx, use-puzzle-state.ts, lib/api.ts, splitmix64.ts, wasm-bridge.ts
projektor error-adapter.ts, access.ts, errors.ts, test/helpers.ts, board-utils.ts, Base.astro, api-client.ts
start-line strategyContext.ts, world.ts

23 findings — a button, a config module, a PRNG, an API client, a test-helpers module, two type modules, a layout partial. Not one genuine over-centralised god object. The fan-out findings, by contrast, look right everywhere.

Why not the fix the ticket suggested

CD-333 proposed requiring fan-out to be above the mean. I measured that and it does not work: these means sit around 1.7 to 2.2, so clearing the bar means importing three things. On personal-blog it suppresses one of four known false positives.

Using the check's own stated definition instead — "high fan-in and high fan-out, pulling in half the project" — the fan-in branch now requires fan-out to be an outlier too, above the same three-sigma bar the fan-out branch already uses. Both thresholds were already computed; nothing new is calculated.

Result

Repo fan-in before fan-in after fan-out before fan-out after
personal-blog 4 0 2 2
bestefforttools 5 0 7 7
poker-puzzle 4 0 4 4
projektor 7 0 5 5
start-line 2 0 3 3

Every fan-in false positive gone; fan-out findings identical, same files and same messages.

The honest consequence

No file in any of the five repos appears in both lists, so this silences the fan-in branch across the entire estate. That is deliberate and the docs say so plainly — fan-in findings are now rare by design, and the branch exists only to catch a hub that is also pulled in from everywhere.

To stop that becoming a silent deletion of half the check, a synthetic god module with both metrics as outliers is pinned by a unit test. A pre-existing test encoding the old behaviour failed as predicted and was updated in place rather than left red.

The alternative — retiring the fan-in half outright — is recorded on the ticket. It is simpler and arguably more honest about a signal that has never once been right in practice, but discarding the capability on five repos' evidence is a bigger step than tightening it, and remains easy to do later.

Verification

cargo build --workspace, cargo test --workspace (1430 passed), cargo clippy --workspace --all-targets -D warnings, cargo fmt --check, gen-docs --check via pre-commit, plus the five-repo run above.

🤖 Generated with Claude Code

https://claude.ai/code/session_019afugU2bTYCtXn6Vy9dn1v

Any successfully shared module is a fan-in outlier by construction, so
the fan-in branch flagged leaf utilities for being reused. Measured
across five local repos: 23 fan-in findings, every one a false positive
— a button, a config module, a utils module, two type modules, a seeded
PRNG, an API client, an error adapter, test helpers, a layout partial —
and not one genuine god object.

The check's stated target is a module with high fan-in AND high fan-out.
The fan-in branch now requires fan-out to be an outlier too, rather than
merely above the mean: these means sit near 1.7 to 2.2, so an
above-the-mean bar means importing three things and suppresses barely a
quarter of the false positives.

Fan-in findings become rare by design, and the docs say so. A synthetic
god module — both metrics outliers — is pinned by a test so the branch
is not silently dead. The fan-out branch, which the field report found
accurate, is untouched and its findings are unchanged on all five repos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019afugU2bTYCtXn6Vy9dn1v
@github-actions

Copy link
Copy Markdown

Criterion benchmark comparison (PR head vs base branch)

�[1m�[92m   Compiling�[0m cofferdam-checks v0.4.2 (/home/runner/work/cofferdam/cofferdam/crates/cofferdam-checks)
�[1m�[92m   Compiling�[0m cofferdam-engine v0.4.2 (/home/runner/work/cofferdam/cofferdam/crates/cofferdam-engine)
�[1m�[92m    Finished�[0m `bench` profile [optimized] target(s) in 50.41s
�[1m�[92m     Running�[0m benches/engine_bench.rs (target/release/deps/engine_bench-e0ce420cbfc0fb1d)
Gnuplot not found, using plotters backend
Benchmarking full_run_no_cache
Benchmarking full_run_no_cache: Warming up for 3.0000 s
Benchmarking full_run_no_cache: Collecting 20 samples in estimated 7.2079 s (420 iterations)
Benchmarking full_run_no_cache: Analyzing
full_run_no_cache       time:   [16.689 ms 16.784 ms 16.873 ms]
                        change: [+0.1162% +0.7570% +1.5140%] (p = 0.05 < 0.05)
                        Change within noise threshold.

Benchmarking single_file_edit_incremental
Benchmarking single_file_edit_incremental: Warming up for 3.0000 s

Warning: Unable to complete 20 samples in 5.0s. You may wish to increase target time to 9.7s, enable flat sampling, or reduce sample count to 10.
Benchmarking single_file_edit_incremental: Collecting 20 samples in estimated 9.7254 s (210 iterations)
Benchmarking single_file_edit_incremental: Analyzing
single_file_edit_incremental
                        time:   [7.3761 ms 7.4239 ms 7.4757 ms]
                        change: [-2.0194% -1.0525% +0.1338%] (p = 0.07 > 0.05)
                        No change in performance detected.
Found 1 outliers among 20 measurements (5.00%)
  1 (5.00%) high mild


@github-actions

Copy link
Copy Markdown

Criterion benchmark comparison (PR head vs base branch)

�[1m�[92m   Compiling�[0m cofferdam-checks v0.4.2 (/home/runner/work/cofferdam/cofferdam/crates/cofferdam-checks)
�[1m�[92m   Compiling�[0m cofferdam-engine v0.4.2 (/home/runner/work/cofferdam/cofferdam/crates/cofferdam-engine)
�[1m�[92m    Finished�[0m `bench` profile [optimized] target(s) in 50.10s
�[1m�[92m     Running�[0m benches/engine_bench.rs (target/release/deps/engine_bench-e0ce420cbfc0fb1d)
Gnuplot not found, using plotters backend
Benchmarking full_run_no_cache
Benchmarking full_run_no_cache: Warming up for 3.0000 s
Benchmarking full_run_no_cache: Collecting 20 samples in estimated 6.9972 s (420 iterations)
Benchmarking full_run_no_cache: Analyzing
full_run_no_cache       time:   [16.323 ms 16.652 ms 17.248 ms]
                        change: [-0.0511% +2.8813% +5.1043%] (p = 0.02 < 0.05)
                        Change within noise threshold.
Found 3 outliers among 20 measurements (15.00%)
  1 (5.00%) low mild
  1 (5.00%) high mild
  1 (5.00%) high severe

Benchmarking single_file_edit_incremental
Benchmarking single_file_edit_incremental: Warming up for 3.0000 s

Warning: Unable to complete 20 samples in 5.0s. You may wish to increase target time to 9.8s, enable flat sampling, or reduce sample count to 10.
Benchmarking single_file_edit_incremental: Collecting 20 samples in estimated 9.8005 s (210 iterations)
Benchmarking single_file_edit_incremental: Analyzing
single_file_edit_incremental
                        time:   [6.8469 ms 6.9006 ms 6.9688 ms]
                        change: [+5.9342% +7.4794% +8.9826%] (p = 0.00 < 0.05)
                        Performance has regressed.


@TAJD
TAJD merged commit 00b3e92 into main Aug 11, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant