ADR-0075: Span-correlator god-file split (intra-package extraction)¶
- Status: Accepted
- Date: 2026-05-23
- Driver: 2026-05-23 architectural review §1.3 (god-file smell:
correlator.goat 754 LoC, above the 500-LoC budget enforced by ADR-0074 /tools/ci/check-file-size.sh) - Related: ADR-0034 (SPAN.Art 3-way fusion correlator), ADR-0035 (Pillar 1 stateful lifecycle, Pillar 2 deny_ineffective)
Context¶
pkg/span-correlator/internal/correlator/correlator.go had grown to
754 LoC across three accreted layers:
- Engine plumbing (v3.7) —
Config,Stats,Engine,New, ingest paths,Pending*introspection. - Snapshot drain + sweep (v3.7 + v3.8) —
pendingFlow,ingest(),drainEager(),canDrain(),isEmpty(),Sweep(),isPastDeadline(),hasAnySignal(). - Verdict decision table (v3.8 ADR-0034 + v3.9 ADR-0035) —
emitVerdict(),decideVerdict(), 4 per-mode decision functions,bumpStats(). - Classification predicates (v3.7, stable) —
isDeny,isPlaintextClassification,isTLS.
The mix meant:
* Every new verdict mode touched the same 750-line file (high merge
risk).
* New predicates (e.g. isQUIC, isDoH planned for v4.x) had no
natural home and had to be added to the same flat list.
* git blame on any verdict change pulled in plumbing context noise.
The 2026-05-23 architectural review (§1.3) flagged this as the most
egregious god-file in the codebase. ADR-0074 introduced a 500-LoC
budget enforced by CI (tools/ci/check-file-size.sh), and the file
was placed in tools/ci/.file-size-baseline pending a refactor —
this ADR.
Decision¶
Apply a pure intra-package extraction — same package
(correlator), no sub-packages, no public API change. Split
correlator.go along the four-layer seam:
| File | LoC after | Responsibility |
|---|---|---|
correlator.go |
318 | Engine + Config + Stats + ingest entry points + introspection |
drain.go |
186 | pendingFlow, snapshot ingest helper, drainEager, Sweep |
verdict.go |
255 | emitVerdict + decideVerdict + 4 per-mode decision functions + bumpStats |
classify.go |
57 | isDeny, isPlaintextClassification, isTLS |
lifecycle.go (491 LoC, stateful mode) is not split in this
refactor — already under the 500-LoC budget and internally cohesive.
Why pure extraction (not sub-packages)¶
Three reasons:
- No API surface change — moving types like
pendingFlowinto a sub-package would require either exporting them or providing a constructor, both of which leak implementation details into the package API. - Lock discipline —
Engine.muis shared by ingest + drain + sweep; splitting those across packages would either force the mutex to be exported or require a re-entrant locking pattern. Neither trade-off is worth it. - Verdict / classify cohesion —
decideVerdictcallsisDenyandisTLS6 times. Cross-package calls would add no value here.
If a future need arises (e.g. plug-in mode for third-party verdict
deciders, or PR-W4-X factoring out the entire snapshot path), a
follow-up ADR can promote drain.go + verdict.go to sub-packages.
Process¶
- Baseline:
go test -race -count=1 ./...green (5 packages, all suites pass). - Three new files written with the exact same code, just relocated.
- Original sections deleted from
correlator.go. go build ./...+go vet ./...+ full test suite re-run — all green, zero behavior change.tools/ci/.file-size-baselineupdated to removepkg/span-correlator/internal/correlator/correlator.go(file is now under budget);types.go(554 LoC) entry kept until a future refactor.
Consequences¶
Positive¶
correlator.godrops from 754 → 318 LoC (well under the 500 budget).- Verdict decisions are now in a dedicated file — every future mode edit has a narrow blast radius.
classify.gois the obvious home forisQUIC,isDoH, etc.- No public API change — every caller and every test pass unmodified.
Negative¶
- 4 files instead of 1 for the snapshot engine. Mitigated by clear per-file purpose statements at top.
See also¶
pkg/span-correlator/internal/correlator/correlator.go— Engine doc string still points at ADR-0034 + ADR-0035.pkg/span-correlator/internal/correlator/drain.go— new file.pkg/span-correlator/internal/correlator/verdict.go— new file.pkg/span-correlator/internal/correlator/classify.go— new file.- ADR-0034 — original 3-way correlator design.
- ADR-0035 — stateful lifecycle (Pillar 1) + deny_ineffective (Pillar 2).
- ADR-0074 — file-size linter gate; this refactor clears the correlator entry from the baseline.