Skip to content

feat(analytics): make the collector timeout compatibility path observable, without widening CollectorFailure.kind #6289

Description

@koala73

Problem

#6086's third suggested direction — "make the untimed path observable at all, so a future regression here is visible rather than presenting as unexplained queue-overflow" — is the one item PR #6088 did not address. The PR is right not to have solved it the obvious way, but it substituted nothing, so the compatibility path is still invisible in both directions.

withTimeout / withManualTimeout return TimeoutBoundInit { init, cleanup }. No field anywhere records which branch produced a given request's signal.

On failure: the diagnostics object in recordCollectorOutcome already carries failureKind, prismaCode, constraint, botFiltered, and the cohort rates, and rides into Sentry extra. It has no field for the timeout mechanism. An operator (or an agent running sentry-triage against the analytics-collector / write-failed fingerprint) pulling up a timeout issue cannot tell whether it came from native AbortSignal.timeout / AbortSignal.any or from withManualTimeout.

On success it is worse: CollectorOutcome.failure is null and nothing records that the compat branch fired at all. There is no way to know what fraction of live traffic runs in an AbortSignal.timeout-absent environment, whether that population is shrinking, or whether a browser regression widened it.

Why it matters more here than as a general nicety

The defect #6086 fixed was unobservable by construction — no log, no counter, no signal — and surfaced only as unexplained queue-overflow in Sentry, long after the fact. That is the specific failure mode this module has already been bitten by once.

It compounds with the open latch-side gap: if a fetch wrapper ever ignores the forwarded abort, the fix silently reproduces the original park with no differentiating signal at all. Sizing the compat population is also the only way to answer "is this compatibility path still worth carrying?" empirically rather than by guess.

The shape it must NOT take

Widening CollectorFailure.kind is the wrong fix and was correctly avoided by PR #6088. kind is consumed by ENVIRONMENT_NOISE_KINDS, isAlertWorthyCollectorFailure, both retry predicates, and the Sentry fingerprint. A new kind not audited against every one of those arms falls through to return true — "everything left is actionable" — and pages someone on a browser-capability gap.

Suggested direction

Follow the precedent this module already set for botFiltered, which is deliberately a marker, not a kind, with a doc comment explaining exactly why.

Thread a usedFallback: boolean (or timeoutMechanism: 'native' | 'manual') through TimeoutBoundInit and record it as:

  • a field on the diagnostics object -> Sentry extra, and optionally
  • a Sentry tag, so it is filterable

and explicitly not:

  • part of CollectorFailure.kind
  • an element of the fingerprint array (grouping and cardinality must not move)
  • an input to either retry predicate or isDurableMarkerResolved

That satisfies the original ask at zero classification risk.

tests/analytics-beacon-rejection.test.mts already asserts on the per-branch signals the transport hands to fetch, so there is a ready-made harness to assert the new field from.

Note

This is consistent with existing convention elsewhere in the repo — src/services/wm-session.ts, src/services/country-geometry.ts, and src/services/ollama-models.ts all fall back to a manual AbortController with zero telemetry. The argument for doing better here is that this is the one module where the silent-degradation failure mode has already caused an incident.

Found during adversarial review of PR #6088; carried over from #6086's open questions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Standard priority, backlog or low urgency workarea: infrastructureCables, pipelines, power grids, cyber threats

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions