Skip to content

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.go at 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:

  1. Engine plumbing (v3.7) — Config, Stats, Engine, New, ingest paths, Pending* introspection.
  2. Snapshot drain + sweep (v3.7 + v3.8) — pendingFlow, ingest(), drainEager(), canDrain(), isEmpty(), Sweep(), isPastDeadline(), hasAnySignal().
  3. Verdict decision table (v3.8 ADR-0034 + v3.9 ADR-0035) — emitVerdict(), decideVerdict(), 4 per-mode decision functions, bumpStats().
  4. 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:

  1. No API surface change — moving types like pendingFlow into a sub-package would require either exporting them or providing a constructor, both of which leak implementation details into the package API.
  2. Lock disciplineEngine.mu is 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.
  3. Verdict / classify cohesiondecideVerdict calls isDeny and isTLS 6 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

  1. Baseline: go test -race -count=1 ./... green (5 packages, all suites pass).
  2. Three new files written with the exact same code, just relocated.
  3. Original sections deleted from correlator.go.
  4. go build ./... + go vet ./... + full test suite re-run — all green, zero behavior change.
  5. tools/ci/.file-size-baseline updated to remove pkg/span-correlator/internal/correlator/correlator.go (file is now under budget); types.go (554 LoC) entry kept until a future refactor.

Consequences

Positive

  • correlator.go drops 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.go is the obvious home for isQUIC, 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.