# Cause tree: signals, checks, fixes and confirmation

Each cause has the signal that points to it, the checks that confirm or rule it out, the fix (always a draft for approval) and what new data must show afterwards. Query names refer to [bigquery-checks.md](bigquery-checks.md); run QC (export coverage) before using any of them as evidence. Facts were checked against Google's live docs on 2026-10-17; re-check before quoting a limit or menu path.

Proof that a pair came from UTMs is a `utm_` parameter in Landing page + query string, or in `page_location` in the export. Session manual source / medium alone is not proof: Google's export schema says the manual fields also hold values parsed from the referrer. Treat manual values in parentheses, such as `(referral)`, `(organic)` or `(direct)`, as referrer-derived, and check at run time how the property fills manual dimensions for untagged traffic.

## A. UTM values that miss channel rules

Signal:

- The Unassigned row breaks down into readable Session source / medium pairs, not `(not set)`.
- Landing page + query string for those sessions carries `utm_` parameters.
- Or a partial set: `utm_source` without `utm_medium` (or the reverse) arrives as `<source> / (not set)`. That is a tagging problem, not a collection one.

Checks:

- List the pairs inside Unassigned, sorted by key events (exploration, or query Q7 for the raw pairs that arrived on `session_start` and `page_view`, including pairs with only a medium).
- Test each pair against the default channel rules in [channel-rules.md](channel-rules.md). Test source and medium together: Email matches on source or medium; Paid Search needs a search source and a paid medium; Display matches medium only.
- Look for parameter-name typos that drop a field entirely, such as `utm_meduim` or `utm-medium`, and for a parameter repeated twice with different values. Compare the published URL with Landing page + query string to see which value arrived.
- Look for unresolved placeholders arriving literally: values such as `{campaign}`, `{{campaign.name}}` or `%%name%%`. A literal placeholder is a tagging failure in the ad or email platform, not a GA4 problem.
- Internal UTMs: Session manual source values that name your own site or sections (`homepage`, `nav`, `banner`, `internal`). In the export, look for events with `collected_traffic_source.manual_source` set whose `page_referrer` host is your own domain. UTMs on internal banners or navigation overwrite the campaign mid-visit and move key event credit.
- Paid media rarely reach Unassigned: any medium matching the paid pattern lands in Paid Other at worst. A paid campaign sitting in Paid Other or an organic channel is the same defect with a plausible label, so check those rows too.

Fix:

- Correct the link template or registry entry for future links, setting source, medium and campaign together (hand off to utm-convention-and-registry).
- Internal links: remove the UTMs and measure the placement with an event and a placement parameter. Never add an internal medium to the registry or a custom channel.
- For an agency or partner, send the evidence request below instead of asking them to "fix UTMs".
- Reclassify history only under the rules in [channel-rules.md](channel-rules.md#when-a-custom-channel-group-is-defensible).

Confirm:

- A controlled click through the corrected link lands in the expected channel in the processed report.
- The old pair stops appearing in new data after the template change date.

Evidence request for an agency or partner:

- The exact published links (not the intended ones), launch times, and the redirect or shortener path.
- The intended source and medium values from the registry.
- One controlled visit through the real link in an agreed consent state, with the time of the visit, so it can be found in DebugView or the export.

## B. Parameters lost before collection

Signal:

- The published link carries UTMs, `gclid` or `gad_` parameters, but Landing page + query string for the session does not.
- Session source / medium is `(direct) / (none)` or the shortener's domain as a referral.
- Google Ads dimensions are `(not set)` for paid Google traffic only.

Checks:

- Click the real link from its real distribution context (email client, ad preview, QR scan) in a clean browser. Record every hop and which parameters survive each one. A URL that is correct in a spreadsheet proves nothing about the email platform's click wrapper.
- Repeat with the consent banner accepted, refused and already saved. A banner that reloads or redirects the page can drop the query string.
- Google Ads: list every Google Ads account that sends traffic to the site, including agency-managed ones and other teams' accounts. Confirm each is linked to this property and stream with auto-tagging on, and that the link is older than 48 hours. Google's test appends `?gclid=TesTER-123` to the landing URL and checks that it survives unchanged, with case preserved. That checks transport only; a dummy click ID cannot attribute anything.
- Open the report's data quality indicator and look for "Campaign data accuracy is affected by missing URL parameters". It appears when GBRAID or `gad_` parameters are missing from URLs that carry `gclid`. View URLs lists the affected paths. Its absence does not prove every path is clean.
- `srsltid` sessions: Google says these can show `(not set)` without a Google referrer, for example when a modified search result URL is shared. Sample the affected landing URLs and referrers. Do not relabel them as organic search.
- When both auto-tagging and UTMs exist and the click ID cannot be used, GA4 derives all cross-channel source fields from the UTMs alone, so an incomplete UTM set produces `(not set)` fields.

Fix:

- Redirects and shorteners preserve the full query string, including `gclid`, `gbraid`, `wbraid`, `gad_*`, `_gl` and UTMs, with case unchanged.
- The consent flow updates consent on the same page rather than reloading with parameters stripped. If `ad_storage` is denied, `url_passthrough` can carry ad click parameters across pages; confirm the current behaviour in the consent docs before proposing it.
- Do not strip every query parameter from landing pages to "clean" URLs.

Confirm:

- The hop log for each fixed path shows parameters intact at the final URL.
- The test session's `collected_traffic_source` fields in the export carry the expected values.

## C. Google tag initialized late, `session_start` missing

Signal:

- Session source / medium `(not set)` on sessions that have other events.
- An unexpectedly large share of Direct after a GTM or site release.
- Query Q1 shows sessions with `has_session_start = false`, or a first event that is a custom event rather than `page_view` or `session_start`.

Checks:

- In GTM, the Google tag should use an Initialization trigger (Google's `(not set)` page names a non-Initialization trigger as a cause). Consent defaults must still be set before it, on Consent Initialization.
- In Tag Assistant, open a fresh session on the failing path and on a known good page. Record the order: consent default, Google tag, first `page_view`, first business event. Google warns that custom events fired before the config are batched with `session_start`, and that a late config can change user and session identity for the rest of the page.
- Check for more than one cookie prefix or a second Google tag configuration on some routes. Inconsistent prefixes create separate cookie silos and separate identities.
- If one route (a checkout step, an embedded form, a single-page-app route) fires an event before configuration, the fix is on that route, not on every tag.

Fix:

- Google tag on Initialization, consent default before it, one cookie prefix, one configuration per stream.

Confirm:

- Q1 for the days after release: share of sessions without `session_start` falls on the affected hostname and path.
- Tag Assistant order is correct for each consent state in the test matrix.

## D. Consent sequence errors, consented-away data and modelling

Separate three things that all look like missing source data:

- Sequence errors (defects). Google lists using `default` to change a visitor's choice (it should be `update`), which loses the initial `session_start` and `user_engagement`, and sending events with denied consent after the visitor granted it, which creates a separate session without `session_start`.
- Consented-away data (not a defect). In basic consent mode nothing is sent before consent. In advanced mode, tags send cookieless pings while consent is denied, without reading or writing analytics cookies, so those hits cannot join an earlier identity or source. Google's export page says cookieless pings are present in the BigQuery export, and its reporting surfaces comparison page says each session in that data has a different `user_pseudo_id`. Google does not say whether they carry `ga_session_id`; Q4a measures it.
- Modelled data (a reporting layer). Behavioural modelling needs advanced consent mode on all pages, at least 1,000 events per day with `analytics_storage` denied for at least 7 days, and at least 1,000 daily users with it granted on at least 7 of the previous 28 days; meeting these does not guarantee it. Modelled data shows under the Blended reporting identity and is not in the BigQuery export, so UI and export session counts differ by design. Google's modelling page also says that when there is not enough consented traffic to inform the model, events from users who decline are not reported, while the export still holds them. That is why QC compares the API with non-denied export events.

Checks:

- Query Q4a first: whether denied rows in this export carry a `user_pseudo_id` (Google says they do, a new one per session) and a session ID, plus the denied share of events per day.
- Query Q4b: sessions and sessions without `session_start` by the session's first consent state, with a flag for sessions that mixed states. Q4b builds sessions from events that have both a pseudo ID and a session ID, so it can only judge the `No` state when Q4a shows denied rows carry both.
  - Gap in `Yes` sessions: the signature of `default` called where `update` belongs. After acceptance every later event is granted, but the session's first `session_start` was lost. Check the CMP's consent call order in Tag Assistant.
  - Gap in `No` sessions, or in mixed sessions that end in `No`: denied events sent after a granted choice, or consented-away data. Test the sequence before calling it consented-away.
  - No gap in any state Q4b can see: consent is not the explanation for those states.
- If denied rows lack session IDs, decide the `No` half from Q4a's denied share per day against the baseline, then run the sequence test: grant consent, navigate to a second page, and confirm in Tag Assistant that no hits with denied `analytics_storage` follow the grant. Only then record denied traffic as consented-away.
- In Tag Assistant's consent view (Google's consent debugging page), check that the default is set on every page before any measurement command, that the choice triggers `update`, and that no second sender (another CMP, a hard-coded snippet) sends contradictory states.
- Test a choice followed by an immediate redirect. Google warns that reloading with a new state without an update loses data from the original page, and that an update fired as the page unloads may be cancelled.

Fix:

- Sequence errors: one consent sender, default first, `update` on every change, no reload-only flows. Hand off to consent-debugging.
- Consented-away data: no fix. Report it as a known gap.
- Never change the meaning of a visitor's choice to improve a report.

Confirm:

- Q4b after release: sessions without `session_start` fall in the state where the gap was; denied sessions are reported separately. Where Q4b cannot see the `No` state, Q4a's denied share returns to the baseline range and the sequence test passes.

## E. Server events without session context (Measurement Protocol, Data Manager API, server containers)

Signal:

- Key events (often `purchase` from a backend, billing system or webhook) in Unassigned or `(not set)`.
- In the Data API or MCP: the event has revenue but Page path or Landing page is `(not set)`.
- In the export: no `page_location`, no `ga_session_id`, and very few distinct `user_pseudo_id` values.

Checks:

- Inventory which API each backend uses. Since 7 May 2026 GA web and app streams also accept server events through the Data Manager API, alongside the Measurement Protocol. Field names differ, so check each sender against its own docs.
- Query Q3. Compare `COUNT(*)`, `COUNT(DISTINCT transaction_id)`, `COUNT(DISTINCT user_pseudo_id)`, the share with `ga_session_id`, and the distinct source and medium values. One value of `user_pseudo_id` across hundreds of orders, or literals such as `undefined`, `null`, `0` or an empty string, means the sender is not passing the browser's client ID.
- The non-null trap: source and medium on those rows can be populated with one constant value. A populated column is not attribution; one distinct value is the tell.
- Measurement Protocol, web: `client_id` must match the ID the Google tag generated on the site. For session attribution the event needs `session_id` in its parameters, the request must arrive no later than 24 hours after the session started, and an overridden `timestamp_micros` must fall between the session's start and end. Events meant to join client events should reach GA4 within 48 hours, and backdating stops at 72 hours.
- Data Manager API: the browser client ID goes in `events.client_id` (required for web events) and the session ID in `additional_event_parameters` as an entry with `parameter_name` `session_id`. Check both on a sample payload.
- Error reporting differs. The Measurement Protocol does not return HTTP error codes even for malformed events, so sender logs showing success prove transport only; use the validation server. The Data Manager API fails fast (a failed request processes no events) and reports per-event errors and warnings through Diagnostics.
- Server-side tagging: when a server container manages its own client ID, all measurements for the stream must flow through it. Some events direct to Google and some through the server fragments identity. Inventory routes by event name.
- Events generated or renamed by GA4 interface rules are not applied to Measurement Protocol events, so a server event must already carry its final name.

Fix (hand off to ga4-measurement-protocol, if installed):

- Capture `client_id` and `session_id` from the Google tag when the customer acts (checkout start, form submit), store them with the order, and send them with the server event.
- Outcomes that happen later than the session window (renewals, offline payments, sales-assisted deals) are new business events. Do not borrow an old session or backdate. Report them through a warehouse model or an offline conversion import instead.
- One sender per event. If the browser also sends `purchase`, decide which is canonical. GA4 deduplicates `purchase` events with the same `transaction_id` on web streams only, and deduplicates every purchase sent with an empty `transaction_id` together. Do not rely on deduplication for any other event.
- For a new or rebuilt sender, note that Google recommends the Data Manager API for new server-to-server integrations; the Measurement Protocol stays supported with no deprecation planned.

Confirm:

- Q3 after release: distinct `user_pseudo_id` close to distinct orders, most rows with a `ga_session_id` that also appears on a browser `session_start`, and several distinct source and medium values.
- Data Manager API senders: Diagnostics shows no errors for the sample period.
- Unassigned purchases fall while total purchases hold.

## F. Cross-domain handoffs and payment referrals

How it shows up: Google's GA4 and Universal Analytics comparison page says GA4 does not restart a session when new campaign parameters arrive. Treat a new referrer the same way unless test T5 shows otherwise: a return from a payment page inside the session timeout (30 minutes by default) creates no `session_start` and leaves session source alone. The leak shows in event-scoped key event credit and, through last-non-direct-click, in the user's later sessions. A return after the timeout starts a new session with the processor as its source.

Signal:

- A payment host, or a domain you own with a different registrable domain from the landing host, as a session source with medium `referral` (query Q5a).
- A payment host as the event-scoped Source / medium (`sourceMedium`) of a key event such as `purchase`.
- Return `page_view` events whose `page_referrer` or `collected_traffic_source.manual_source` is a payment host (query Q5b).
- `user_pseudo_id` changing across a domain boundary.

Not a signal: own-domain or same-site subdomain referrers on `session_start`. Google ignores referrers on the current page's domain or its subdomains, and visits that carry `_gl`. Those rows are sessions that restarted after the timeout or in a new tab.

Checks:

- Walk one journey from landing page to success page across each domain. Record `user_pseudo_id` (client ID) and session ID on both sides and whether the destination URL carries `_gl`. Google warns that redirects can remove `_gl` even when the previous page added it, and that JavaScript-driven navigation or `stopPropagation` can stop decoration.
- Payment return: identify the provider hosts that send visitors back (card authentication pages, wallet pages, regional gateways). Run Q5b for those hosts on every event, not only `session_start`.
- Card authentication (3-D Secure) returns can come from many bank-operated hosts, and the set changes as banks change providers. Count the distinct referrer hosts on the payment-return and confirmation paths with Q5b's unfiltered variant, per month.
- Iframe checkout or booking widget on another domain: record whether the iframe URL carries `_gl` and whether events from inside it share the parent page's `user_pseudo_id`. Google's cross-domain pages describe decorating links and forms; verify iframe behaviour at run time and hand manual decoration to cross-domain-and-spa-qa, if installed.
- In an exploration, break the key event down by event-scoped Source / medium and look for payment hosts.
- Subdomains that appear as referrers: check whether they share the same Google tag configuration and cookie domain. Google's cookie page says that with automatic cookie domain configuration (the default) activity is measured across subdomains without extra configuration.

Fix:

- Domains you own and can tag: configure cross-domain measurement (Admin, Data collection and modification, Data streams, the web stream, Configure tag settings, Configure your domains) and preserve `_gl` through redirects. Hand off to cross-domain-and-spa-qa, if installed.
- Third-party payment processors: add them as unwanted referrals (Admin, Data collection and modification, Data streams, the web stream, Configure tag settings, Show all, List unwanted referrals). GA4 then appends `ignore_referrer=true` to every matching event. The limit was 50 per stream on 2026-10-17.
- Too many hosts to list: if the monthly count above finds more than 10 new payment or bank hosts, or the stream's list already holds 40 or more entries, draft `ignore_referrer` on the payment-return and confirmation pages only, scoped by URL, instead of adding every host. Google's unwanted referrals page describes setting the parameter manually in the Google tag configuration and says most sites will not need to. It warns against setting it on every page, and says any value, including false, makes GA4 ignore the referrer. So the draft must omit the parameter on every other page, never set it to false.
- Never use unwanted referrals in place of cross-domain measurement or a shared cookie domain when `user_pseudo_id` changes across the boundary: the referral label goes away but the identity break stays. For subdomains that share the cookie domain, Google's cross-domain page lists adding them to the exclusion list as an option, once you have confirmed identity carries over.

Confirm:

- A human-run test purchase through each payment method (see the test matrix, and Q8 for the recorded client ID) keeps one `user_pseudo_id`; the return `page_view` carries `ignore_referrer` (check `event_params` in the export, or the request in Tag Assistant if the export does not show it); and the purchase's event-scoped Source / medium is the original campaign, not the processor.
- Q5b after release: return events from payment hosts without `ignore_referrer` fall to zero.
- Q5a after release: payment hosts as session sources fall. Existing users may keep a processor as their last non-direct source until they return through another channel.

## G. Events outside a session

Signal:

- Event counts broken down by session dimensions show a `(not set)` row.
- Query Q2 finds events without `ga_session_id`.

Checks:

- Group sessionless events by `event_name`, `stream_id`, `platform`, consent state and whether `page_location` exists (Q2). Typical senders: Measurement Protocol without `session_id`, and server containers or backend jobs that build their own events.
- Sessionless `No` rows: Google documents that cookieless pings are in the export with a different `user_pseudo_id` per session, not whether they carry a session ID. Read Q4a. If denied rows normally carry a session ID, sessionless `No` rows are a sender or sequence defect. If they normally lack one, the rows can be consented-away pings, but run the sequence test in cause D before recording them that way.
- Separate denied-after-granted consent events (cause D), which do have a session but one without `session_start`.

Fix:

- Add session context where the event belongs to a session (cause E). Where it does not, accept the gap and exclude those events from session-scoped reporting, documenting the exclusion.

Confirm:

- Q2 after release: sessionless events limited to the senders documented as sessionless.

## H. Landing page `(not set)`

Signal:

- Landing page is `(not set)` for a share of sessions. Google's documented cause: the session has no `page_view`.

Checks:

- Compute the share over web-stream sessions only. App streams and server senders have no web landing page, so they inflate the share without being a defect.
- Query Q6: group sessions without `page_view` by first event name, hostname and platform. Clusters to look for:
  - Sessions started by a server event or a consent-update event (causes D, E).
  - Single-page apps with `send_page_view` set to false. Enhanced measurement can still send history-based page views, so a manual implementation must own both sources or it double counts; if history-based page views are off, any route without a manual call has no `page_view`.
  - Sessions whose first event is an interaction after a long idle gap. The default session timeout is 30 minutes.
- Test a hard load, a client-side route change, browser back, and a return from authentication or payment.

Decision rule: report a cluster as a defect when it holds more than 1% of the affected key events; otherwise record it as expected behaviour with its count.

Fix:

- One owner for each `page_view` trigger. Never add a fabricated page view to a webhook or server event to fill the dimension.

Confirm:

- Q6 after release: sessions without `page_view` limited to the documented clusters.

## I. Campaign `(not set)` on Direct and other non-campaign rows

Signal:

- Session campaign shows `(not set)` or a placeholder in parentheses.

Checks:

- Cross-tab Session campaign by Session source / medium. On `(direct) / (none)`, organic and plain referral rows, there is no campaign to record; the value is expected. Check what your property shows for these rows rather than relying on a remembered placeholder.
- A defect exists only when the landing URL carried UTMs (a `utm_` parameter in Landing page + query string) and the campaign is `(not set)`: `utm_campaign` is missing or misspelled. Google says missing UTM parameters produce `(not set)` values.
- For Google Ads traffic, see cause B (link, auto-tagging, `gclid`).
- Event-scoped Campaign on non-key events is `(not set)` by design (step 2 of the procedure).

Fix and confirm: as cause A, on the link template.

## J. AI assistant referrers

Facts checked on 2026-10-17:

- Google's release notes announced the AI Assistant channel on 13 May 2026. When the referrer matches Google's list of AI assistants, GA4 sets medium `ai-assistant` and campaign `(ai-assistant)`, and the session lands in the AI Assistant channel. A medium of exactly `ai-assistant` also matches.
- Google's AI Overviews and AI Mode are not in this channel; they count as Organic Search.
- The help page does not publish the full assistant list. Assistants not on it stay in Referral.
- Since 11 June 2026 a Source Group field merges variant source values, with built-in groups for ChatGPT (OpenAI) and Perplexity, and is filled retroactively. The same release reclassified some Source Platform values, which Google Ads channel rules depend on.

Checks:

- Find the first date medium `ai-assistant` appears in the property and annotate that date on any trend that crosses it, with 13 May 2026 as the announcement date. Neither the release note nor the channel page says the change reached every property that day or applies to history.
- Break traffic down by Source Group to see one assistant's sessions across Referral, AI Assistant and Unassigned at once. Check at run time whether the Data API exposes it.
- Search Session source / medium for `ai-assistant` to see which assistants are recognized.
- List Referral sources that look like assistants; those are unrecognized.
- List Unassigned pairs with media such as `ai`, `chatgpt`, `llm` or `genai`: these are manual tags that match no rule.
- List Unassigned sessions whose Session manual source is an assistant domain and whose medium is `(not set)`, and check Landing page + query string for a lone `utm_source`. Some assistants may add their own UTM parameters to outbound links; verify the current behaviour of each assistant at run time rather than assuming it.
- Copied links from a chat arrive without a referrer and land in Direct. No GA4 setting recovers them.
- When comparing Source Platform or Google Ads channels across 11 June 2026, note the reclassification.

Fix:

- Do not invent manual AI media, and do not copy `ai-assistant` into manual tags: keep recognized assistant referrals separable from links you tag. For links you control, use the registry's values.
- A partner or affiliate that places links inside an assistant is the source, not the assistant: tag `<partner_name> / referral` (or `/ affiliate` when it is paid commission), with the assistant placement in `utm_content`. That is cause A with an AI context note, not cause J. Never retag someone else's referral to improve a report.
- For unrecognized assistants, a custom channel group with an AI channel above Referral, using the two-condition rule in [channel-rules.md](channel-rules.md#ai-assistant-channel-in-a-custom-group): medium exactly `ai-assistant`, or a listed assistant host with medium exactly `referral`. Never a source-only regex. Hand off to ai-traffic-tracking, if installed, for measurement beyond GA4.

Confirm:

- In the comparison view, the custom group's AI channel equals the default AI Assistant sessions plus the Referral sessions from the listed hosts, and Referral falls by that second number. Session counts in the UI are approximate, so a small difference is not a failure; check it with exact export counts.
- No session with a tagged medium other than `referral` or `ai-assistant` lands in the AI channel.

## K. Direct inflation

Signal:

- `(direct) / (none)` sessions rise, often with a falling engagement rate.
- No matching rise in typed-URL behaviour (brand search, returning users) to explain it.

Checks:

- Bots and data-centre traffic: split the new Direct sessions by City, Browser, Browser version, Operating system with version, Screen resolution and Device category, with Sessions, Engaged sessions and Engagement rate. Suggested threshold for flagging a cluster for review: one value holds 50% or more of the increase with an engagement rate under 5%. It is a practitioner heuristic, not a Google threshold, so it flags a cluster; it does not prove the traffic is non-human. Confirm with the export check below and the site's server or CDN logs before reporting it as contamination rather than a tag defect.
  - Example signature (fictional): new Direct sessions from one city, one screen resolution (`1280x720`), landing page `/`, engagement rate 2%, all on a desktop browser version two releases old.
- Export check, query Q9: web events whose `device.web_info.hostname` is empty or not one of the hostnames in `stack.md`, by day and city. Events from hosts you do not own, or page views with an empty hostname, point to your measurement ID being sent from outside your site.
- Referrer loss: list the landing pages of the new Direct sessions. Deep campaign destinations with no UTMs point to untagged email, PDF, QR or app links. Google's Direct page lists links without UTMs, redirects that strip UTMs (including HTTPS to HTTP), URL shorteners, offline documents and ad blockers. Walk your own redirects and link services for an HTTPS to HTTP hop or a `Referrer-Policy: no-referrer` header.
- Identity resets: compare the new-user share inside Direct by browser before and after the first-change date. A jump in one browser points to cookie lifetime or storage limits; check that browser's current cookie limits at run time rather than quoting remembered values.
- Paid click IDs lost: compare Google Ads clicks with GA4 sessions from Google Ads by device and browser. A gap in one browser points to lost `gclid` (cause B).
- Then the identity causes: C (late tag), D (consent sequence) and F (cross-domain), because a new identity has no earlier non-direct source to inherit within the lookback window.

Fix:

- Tag owned links (email, PDF, QR, app) through the registry. Repair redirects and referrer policies on your own link services.
- Identity resets by browser: hand off to server-side work (server-side-tracking-debug, if installed).
- Bot clusters: route blocking to the site owner. GA4 has two hostname data filter types, both drafts for the property owner: Exclude filters (release notes, 11 June 2026) drop events from listed hostnames, and Include filters (21 September 2026) keep only listed hostnames, also drop events with an empty hostname, and do not apply to Measurement Protocol events. An active data filter permanently drops the data it excludes, so start it in Testing where the filter type allows it, and verify the current behaviour at run time.

Confirm:

- After release, the Direct count falls back toward the baseline range without a matching fall in total key events.
- The bot cluster, if any, is recorded with its date range so trends can exclude it.
