# Audit rules G01 to G26: evidence, thresholds, confirmation

Severity means the same in every rule:

- FAIL: wrong data now, data collected without permission, personal data in GA4, or permanent data loss in progress or one click away. Downgrade only by citing a dated exception from `decisions.md` (put it in the config's `accepted_exceptions`; the script then prints the FAIL as WARN with the citation).
- WARN: a plausible defect that needs a human decision, more evidence or a runtime check.
- PASS: the rule ran on real evidence, every primary input it lists was read, and nothing was wrong.
- NOT CHECKED: the evidence was not available (endpoint unreadable, intake row empty). Never guessed.
- N/A: the rule does not apply (for example, web rules on an app-only property).

Rule status is the worst sub-finding. Each sub-finding scores in one rule only, the most specific one.

Primary inputs: a rule with a "Primary" line may PASS only when every input on that line was read. Without one, a would-be PASS becomes NOT CHECKED with the reason "primary evidence missing"; a WARN or FAIL stays and lists the missing input as partial. A PASS sentence names only what was read.

Low-volume rule: percentage thresholds need at least 1,000 sessions (session and event shares), 100 key events (key event shares) or 100 purchases (purchase integrity) in the window. Below that, the script reports the counts and caps the sub-finding at WARN with "low volume". Sessions come from R04; without R04 they are summed from R03 and labelled approximate. A share computed from a report that returned fewer rows than its rowCount is capped at WARN the same way. All thresholds are config overrides (`thresholds` in the config) and the script prints every value it used. The 2%, 10%, 20%, 5%, 1.2, 80%, 85%, 102% and 90-day values are practitioner defaults, not Google limits.

Help centre pages are cited by answer number; the URLs are in `sources.yaml`.

## A. Property and streams

### G01 property-basics

- Evidence: `properties.get` timeZone, currencyCode, serviceLevel, propertyType, deleteTime, expireTime; `stack.md`; Google Ads `customer.time_zone` and `customer.currency_code`.
- FAIL: deleteTime or expireTime is set (the property is in the trash).
- WARN: time zone or currency differs from `stack.md`; differs from a linked Google Ads account (day boundaries and values will not reconcile); property type is a subproperty or roll-up.
- PASS: values match the documented ones.
- Why: the time zone sets every day boundary in reports, BigQuery `event_date` and comparisons with Google Ads.
- Agent adds: a time zone change applies forward only and leaves a flat spot or spike on the change day. Never propose it without the reporting owner.
- Change hint: P4 unless the trash flag is set (restore before anything else).

### G02 streams-and-tag-coverage

- Evidence: `dataStreams.list`; R03 hostName by streamId; R02; Tag coverage summary or its CSV.
- FAIL: one hostname sends 1% or more of its events to each of two or more web streams (double tagging), unless documented; a page on a key event path (checkout, confirmation, lead thank-you) shows Not tagged or No recent activity.
- WARN: a stream with no events in the window; a measurement ID not in `stack.md`; any other page Not tagged or No recent activity; more than one web stream for one registrable domain.
- PASS: each hostname feeds one stream and key paths are tagged.
- Why: two streams on one page double events and split users and sessions between streams. Tag coverage lists up to 10,000 pages with the statuses Tagged, Not tagged and No recent activity (no tag seen in 30 days), and can be downloaded (answer 12270036).
- Agent adds: a second stream is often a hard-coded snippet left in a theme after a rebuild. Ask gtm-container-audit and a page-source search to find it.
- Confirm: one fresh page load, network panel filtered on `collect`, count requests per measurement ID.
- Change hint: P2; remove the extra sender at the source, then archive nothing until the owner agrees.

### G03 enhanced-measurement

- Evidence: `dataStreams.getEnhancedMeasurementSettings` per web stream; tracking plan; R01.
- WARN: page changes on while the tracking plan sends route-change page_view manually (duplicate page views); form interactions on while tags send a confirmed lead event; site search on but its keys lack the site's search key, or view_search_results is zero while the site has search; a GTM tag sends the same event as an enhanced measurement option (gtm-container-audit finding).
- PASS: settings match the tracking plan.
- Why: page changes listen to browser history changes; form_submit fires on submit attempts, including failed, search and newsletter forms. Default site search keys are q, s, search, query and keyword.
- Agent adds: `send_page_view: false` stops the first page view only; it does not stop history-based page views.
- Confirm: one approved navigation in DebugView on staging shows one page_view per screen.
- Change hint: P4; turning an option off changes data from that moment only.

## B. Retention and identity

### G04 data-retention

- Evidence: `getDataRetentionSettings` eventDataRetention, userDataRetention, resetUserDataOnNewActivity; config `needs_history_months` (default 14); BigQuery results when BigQuery is the named history source.
- FAIL: event data retention shorter than the history needed (TWO_MONTHS fails on standard unless `decisions.md` records why). When BigQuery is the named source but cannot stand in, the FAIL says why: no BigQuery results, a default or table expiration shorter than the history needed, or a stale export.
- WARN: the same, when `decisions.md` names BigQuery as the long-range source and the BigQuery results show the daily export runs with the newest table within 3 days and no default or table expiration shorter than the history needed (needs_history_months x 30.4 days); user data retention TWO_MONTHS (check on the live page which reports it limits); reset on new activity not recorded as a decision.
- PASS: retention meets the documented need.
- Why: standard properties offer 2 or 14 months; 360 offers 2, 14, 26, 38 or 50 months. Retention limits explorations and funnels, not standard aggregated reports. An increase applies to data not yet deleted; a reduction deletes older data at the next monthly process after a 24-hour grace period. Demographic data keeps a separate two-month limit, and Google Ads data in GA4 covers a rolling 36 months (answer 7667196).
- Agent adds: state the earliest exploration date in the analyst caveats so nobody reads an empty month as zero.
- Change hint: P3; raising retention is reversible and urgent, because every month of delay is lost for good.

### G05 reporting-identity-and-user-id

- Evidence: `getReportingIdentitySettings`; `getGoogleSignalsSettings`; R12 signedInWithUserId; R13 probe metadata; intake `identity.modeling`; config `user_id_expected`, `user_id_min_share` and `modelled_data_expected`.
- Primary: the reporting identity settings, plus R12 when User-ID is expected. A 403 or 404 on the identity endpoint makes G05 NOT CHECKED.
- FAIL: User-ID is expected and no user is signed in with a User-ID in the window.
- WARN: identity Blended or Observed while a needed probe is thresholded and no Device based comparison is recorded; identity choice not recorded; User-ID share below the expected minimum; `decisions.md` expects modelled data while identity is not Blended, or while consent mode is advanced, identity is Blended and the data quality icon says estimated user data is unavailable or excluded.
- PASS: identity documented, User-ID present where expected, modelling as expected.
- Why: switching identity changes neither collection nor processing and can be reversed; Blended and Observed reports can be thresholded (answer 10976610). Since 15 June 2026 the Google signals setting only governs association with signed-in data for behavioural reporting; consent mode governs Google Ads cookies and identifiers (answer 17016975). Behavioural modelling needs consent mode on every page with tags loading before the banner, at least 1,000 events a day with analytics_storage denied for 7 days, and at least 1,000 daily users with analytics_storage granted on 7 of the previous 28 days; modelled data shows only under Blended and is never exported to BigQuery (answer 11161109). The data quality icon reads "Including estimated user data", "Excluding estimated user data" or "Estimated user data unavailable".
- Agent adds: do not prescribe switching off Google signals as a fix for thresholds; compare identities in a labelled test and restore the agreed setting.
- Change hint: P3; identity changes need Editor at account level.

## C. Traffic hygiene

### G06 internal-traffic

- Evidence: intake internal traffic rules (names and traffic_type values only) and data filters (type, operation, value, state, date); R11T testDataFilterName totals; R11 testDataFilterName by eventName.
- Primary: intake `tag.internal_traffic_rules` (`none` counts as read).
- FAIL: a traffic_type value set by a rule that no Active or Testing internal filter uses, or a filter value that differs from the values the rules set; an internal filter in Testing that matches 20% or more of events, or purchases at half the property's normal purchase rate or more (VPN, carrier or shared range). Do not activate. Shares come from R11T; from a truncated R11 alone they are approximate and capped at WARN.
- WARN: a filter in Testing for more than 30 days (nothing is excluded); a filter Inactive; no rule exists, R11T shows no events for the filter, and the filter is Active or `stack.md` lists offices or remote staff (confirm whether tags set traffic_type); no internal traffic definition while `stack.md` lists offices, agencies or call centres; IP-only rules while staff work remotely.
- PASS: rule and Active filter values match, with Testing evidence kept from before activation. With no rule, a Testing filter that matches events means tags set traffic_type; a Testing filter that matches nothing, with no offices or remote staff, raises nothing, not even the 30-day WARN.
- N/A: app-only property (internal traffic cannot be filtered for app users).
- Why: up to 10 data filters per property; Active filters are permanent and also keep data out of BigQuery; filters take 24 to 36 hours to apply (answers 13296761, 10104470).
- Agent adds: for remote staff, the traffic_type parameter can be set on events directly instead of matching IP ranges.
- Confirm: read a filter already in Testing with R11 after 24 to 36 hours, adding hostName as a dimension; only intended traffic matches. Putting a filter into Testing is a change list item for a property editor.
- Change hint: P1 for a broad Testing filter, P2 otherwise.

### G07 developer-traffic

- Evidence: intake data filters; R11; R02 daily volume; filter activation date; gtm-container-audit debug_mode findings.
- FAIL: the Developer traffic filter in Testing matches 5% or more of events; daily events fell 50% or more within 3 days after the filter's activation date.
- WARN: no Developer traffic filter, or Inactive (GTM Preview and DebugView traffic reaches reports and the export); a filter in Testing for more than 30 days.
- PASS: Active filter and no sign of debug_mode on production events.
- Why: the filter excludes events sent in debug mode while DebugView keeps working. Google's help warns that setting debug_mode to false does not turn debug mode off (answer 7201382), so a tag that sends `debug_mode: false` marks every production event as debug traffic.
- Agent adds: never advise activation until a person has removed the parameter from production tags (gtm-container-audit R20 finds it) and R11 shows a low match share again.
- Confirm: DebugView device list at a time nobody is testing; gtm-container-audit for debug_mode parameters; R11 after the fix.
- Change hint: P1; activation is permanent for the data processed while Active.

### G08 hostnames-and-hostname-filters

- Evidence: R03 hostName by eventCount and keyEvents; intake hostname filters; config `expected_hostnames`.
- Primary: R03 and config `expected_hostnames`.
- FAIL: an unexpected hostname with 1% or more of events or any key events (staging, localhost, a copied tag); an Active Include hostname filter that does not match every expected hostname.
- WARN: unexpected hostnames below 1% with no key events; empty or (not set) hostname on 5% or more of events; an Include filter in Testing (the script lists hosts it would drop).
- PASS: only expected hostnames, and filters cover them.
- Why: Include filters are applied as a union first, then Exclude filters. Hostname Include filters (release of 21 September 2026) do not apply to Measurement Protocol events and block web events with an empty hostname; Exclude hostname filters arrived on 11 June 2026 (answers 16608575, 9164320). Known bots are excluded automatically and that cannot be turned off.
- Agent adds: check match types. "contains example.com" also matches example.com.other.test; "ends with example.com" matches notexample.com.
- Change hint: P1 for an Active Include filter that drops a host (data loss in progress), P2 otherwise.

### G09 referrals

- Evidence: R05 session source and medium (referral); R06 event-scoped sourceMedium by keyEvents; intake unwanted referral list; config payment and sign-in hosts, owned registrable domains.
- FAIL: a payment or sign-in host (config list plus paypal.com, checkout.stripe.com, accounts.google.com, login.microsoftonline.com, appleid.apple.com) holds 1% or more of key event credit and is not an unwanted referral; an owned registrable domain is a referral source with 1% or more of sessions.
- WARN: either case below 1%; an owned, tagged domain listed in unwanted referrals; 40 or more of the 50 unwanted referrals per stream used; a listed provider still holding credit (check when it was added).
- PASS: no payment, sign-in or owned domain takes referral credit.
- Why: unwanted referrals add `ignore_referrer=true`; GA4 already ignores self-referrals from the same domain and subdomains and navigation that carries `_gl` (answer 10327750). A returning user keeps an earlier referral within the lookback window, so credit decays slowly after a fix.
- Agent adds: on route C, R06 is not exported, so the script uses session-scoped key events from R05; say so.
- Change hint: P3; add processors to unwanted referrals, owned domains to Configure your domains.

### G10 cross-domain-and-session-settings

- Evidence: intake Configure your domains, session timeout, engaged-session timer, cookie override; R07 outbound clicks by linkDomain; G09 evidence.
- Primary: intake `tag.domains` when the config lists two or more owned registrable domains.
- FAIL: an owned registrable domain in the journey that uses the same measurement ID is missing from Configure your domains, and it shows as a referral with 1% or more of sessions or receives 1% or more of click events as outbound clicks.
- WARN: missing with no evidence yet; session timeout not 30 minutes or engaged-session timer not 10 seconds without a decision; cookie override on without a decision; a manual linker in GTM overriding the Admin list.
- PASS: every owned domain listed, defaults or documented values.
- Why: links to configured domains stop producing outbound click events, and linker parameters expire after two minutes (answer 10071811, cross-domain developer guide). Timer changes move engagement rate and bounce rate.
- Confirm: a fresh click across each boundary inside two minutes; `_gl` on arrival and the same client ID on both sides.
- Change hint: P3.

## D. Key events and values

### G11 key-event-inventory

- Evidence: `keyEvents.list` eventName, custom, deletable, countingMethod, defaultValue, createTime; R01; Google Ads links; config intended key events.
- FAIL: session_start, first_visit, page_view, user_engagement or form_start marked as a key event, or form_submit marked while enhanced measurement sends it, and the key event is deletable.
- WARN: the same with `deletable` false; view_item, view_item_list, add_to_cart, add_to_wishlist, begin_checkout or view_search_results marked without a documented purpose; key events not in the tracking plan; more than 10 key events; 24 or more of 30 (standard) or 40 or more of 50 (360) used; an intended key event not marked; a key event with no events in the window.
- PASS: only documented business outcomes are key events.
- Why: Google marks purchase by default (plus app defaults). With a linked Google Ads account and ads personalization on, it also marks add_to_cart, add_to_wishlist, begin_checkout, session_start, view_item, view_item_list and view_search_results (answer 13128484). Marking applies from that moment on.
- Agent adds: the script flags auto-marked events created the day the Google Ads link was made. Check whether Google re-marks them after unmarking while personalization stays on (verify at run time). This rule owns the key event list. tracking-audit CNV-05 rates the same event WARN unless a Google Ads action bids on it, because it scores bidding and this rule scores the property.
- Confirm: GAQL shows no Google Ads action imports these events before anything is unmarked.
- Change hint: P2; tell the reporting owner the all-key-events total will fall.

### G12 key-event-counting-and-duplicates

- Evidence: key event countingMethod; R01 eventCount and sessions by eventName.
- FAIL: purchase or another transactional key event counted ONCE_PER_SESSION.
- WARN: another key event on ONCE_PER_SESSION without a decision; lead or sign-up events above 1.2 events per session in which they occur, or purchase above 1.1 (with at least 100 events).
- PASS: Once per event where outcomes can repeat, no duplicate pattern.
- Why: Google recommends Once per event and calls Once per session legacy; it was the default for key events created from Universal Analytics goals. Changes apply to future data only (answer 13366706).
- Agent adds: a duplicate trigger is fixed at the trigger, not hidden with the counting method; the duplicates still reach every other destination.
- Change hint: P2.

### G13 purchase-and-value-integrity

- Evidence: R08 transaction ID counts; R09 currencyCode by eventCount and eventValue; R17A purchase totals; R17B item totals; R17C items with item name (not set); key event defaultValue; R01 eventValue; config `needs_value` and `backend_orders` (aggregate order count and revenue for the window from the order system's owner, never order IDs).
- Primary: R08 or R09.
- FAIL: 1% or more of purchase events have no transaction_id; 1% or more of purchases carry no currency (revenue is not computed).
- WARN: 1% or more of transaction IDs appear on more than one purchase event; a default value on purchase (hides missing values); a key event that needs a value with zero event value and no default; fewer than 100 purchases; GA4 purchases below 85% or above 102% of `backend_orders` count; gross item revenue and gross purchase revenue differ by more than 2%; item name (not set) on 1% or more of items purchased.
- PASS: transaction ID and currency on at least 99% of purchases (the PASS line prints any remainder), values present, purchases within the band of the order count, items consistent.
- N/A: no purchase events and no value-bearing key events.
- Trust: without `backend_orders`, the Revenue and ecommerce trust label stops at "use with caveat"; matching the order system is the cheapest proof that purchase capture works.
- Why: transaction_id is required for purchase and lets GA4 drop duplicate web purchases; currency is required when value is sent; value is the item subtotal, excluding shipping and tax (events reference). Gross item revenue is price times quantity without tax or shipping, and the gross metrics on both sides keep refunds out of the comparison (Data API schema).
- Agent adds: this is a property-level band, so it can pass where a finer check fails: tracking-audit CNV-03 fails one test purchase without an ID, CNV-10 joins order IDs, and tracking-plan-audit D12 fails any ratio above 1.0 against the system of record. Treat an ID-level failure as the stronger evidence. On advanced consent mode, consent-denied purchases are left out of reports unless modelled, so a ratio under 85% can be consent; compare orders from regions without a consent requirement before calling capture broken. Above 102% points to duplicates, test orders or a second sender. Verify at run time that currencyCode returns (not set) for purchases sent without currency; otherwise use the BigQuery check in [data-checks.md](data-checks.md).
- Change hint: P2; hand order-level gaps to purchase-reconciliation.

### G14 event-rules-and-names

- Evidence: eventCreateRules and eventEditRules per web stream; R01 event names; config `tag_sent_events`.
- FAIL: a create rule produces a key event that tags already send (double count).
- WARN: a create rule that makes a key event from page_view on a URL condition (reloads and back-button visits count); an edit rule that renames events or sets value, currency, transaction_id or items; event names with capitals, spaces or case-only differences.
- PASS: no rules, or documented rules.
- N/A: app-only property.
- Agent adds: edit rules change data silently for every consumer, BigQuery included; list the consumers before proposing removal. Naming fixes go to event-naming-convention.
- Change hint: P2.

## E. Google Ads integration

### G15 google-ads-links

- Evidence: `googleAdsLinks.list` customerId, canManageClients, adsPersonalizationEnabled; R10 google / cpc sessions by sessionGoogleAdsCustomerId; config expected, retired and third-party accounts.
- FAIL: an expected account that uses GA4 key events or audiences has no link; a link to an account the config marks as owned by a third party.
- WARN: a link to a retired or unknown account; a manager account link (imports become available to every client account under it); 10% or more of google / cpc sessions without a customer ID.
- PASS: expected links only, Google Ads dimensions populated.
- N/A: no link, none expected, nothing imported.
- Why: up to 400 links per property; personalized advertising is on by default when linking; clicks from unlinked accounts appear as google / cpc but are not used for Google paid channel attribution, and unlinked accounts stop importing key events (answer 9379420).
- Change hint: P3; unlinking stops imports, so agree the date with the Google Ads owner.

### G16 ads-conversion-sharing

- Evidence: `getAttributionSettings` adsWebConversionDataExportScope; intake `conversions.NAME` rows from Advertising, Tools, Conversion management (source key event, counting, click-through and engaged-view windows, channels eligible for credit); Google Ads GAQL on conversion_action (type, category, status, primary_for_goal, counting_type, click_through_lookback_window_days) and customer.
- FAIL: an enabled GA4-imported action (any GOOGLE_ANALYTICS_4_ type) and an enabled native or uploaded action (WEBPAGE, WEBPAGE_CODELESS or UPLOAD_CLICKS) are both primary in PURCHASE, or in any category when the config's `ads_same_outcome_actions` puts them in one group (one outcome counted twice for bidding).
- WARN: the same pair in another category with no such group (lead forms can be different outcomes: confirm they are, otherwise one outcome counts twice); export scope NOT_SELECTED_YET while GA4 key events are imported; Universal Analytics actions still enabled; a secondary action in a custom goal (still biddable); a GA4 conversion whose counting differs from the key event's intended counting in the tracking plan; a lead-type action (SUBMIT_LEAD_FORM, SIGNUP, CONTACT, QUALIFIED_LEAD, REQUEST_QUOTE, BOOK_APPOINTMENT) counting MANY_PER_CLICK; a primary action whose click-through window is shorter than `sales_cycle_days` (the GA4 conversion window is rated only when Google Ads results are missing, because the two settings match).
- PASS: one primary action per outcome, export scope chosen, counting and windows fit the outcome.
- NOT CHECKED (partial): without Google Ads access, only the GA4-side export scope and conversion rows are checked.
- Confirm: the GAQL result, then each action's tag trigger or GA4 source event, before calling the finding confirmed.
- Why: in the Google Ads API, `primary_for_goal` false makes an action non-biddable except in custom goals; conversions created from GA4 default to secondary (verify at run time). Conversions built from key events keep counting method and windows in step between GA4 and Google Ads, and are not available on subproperties or roll-ups (answer 14710559). Google suggests counting One for leads and Every for sales (answer 3438531).
- Agent adds: the time zone and currency comparison with Google Ads scores in G01, not here.
- Change hint: P2; the Google Ads owner chooses which action stays primary and how leads count.

## F. BigQuery export

### G17 bigquery-link-config

- Evidence: `bigQueryLinks.list` project, datasetLocation, dailyExportEnabled, streamingExportEnabled, freshDailyExportEnabled, exportStreams, excludedEvents; config `bigquery_required`, `warehouse_uses_revenue`, `production_stream_ids`.
- FAIL: export required and no link, or daily and streaming both off; a production stream missing from exportStreams; purchase or any key event excluded while the warehouse reports revenue or conversions.
- WARN: no link and not required (raw history starts only from the link date); streaming only for revenue reporting (best effort, gaps possible).
- PASS: daily export of every production stream with nothing needed excluded.
- Agent adds: check the identity shown on the link in the UI; older links can use the legacy service account, and Fresh Daily on 360 needs the newer built-in identity (answer 9823238).
- Change hint: P3; to move the dataset, prefer cross-region dataset replication (copy or move), which keeps the link and avoids a gap; deleting and recreating the link leaves a gap (answer 9823238).

### G18 bigquery-volume-and-health

- Evidence: `bigQueryLinks.list` dailyExportEnabled; per day, the higher of R02 eventCount for exported streams minus excluded events and BigQuery `events` (consent-denied pings included); BigQuery results (tables, newest table, default table expiration, per-table expiration from BQ2b, daily counts).
- FAIL (standard): with daily export on, any day above 1,000,000 exported events (the limit is on daily export; streaming export has no event limit, answer 9823238); a dataset default table expiration shorter than the history needed (needs_history_months x 30.4 days); a daily table that expires before the history needed; the newest daily table more than 3 days old.
- Loss or risk: a day over the limit is data loss in progress only when the BigQuery results show the newest daily table more than 3 days old or a table missing on or after the first day over the limit. With complete tables it stays a FAIL marked "at risk" and prints under Data loss risk, as it does when no BigQuery results were supplied or they hold no BQ1 table list. Expiration and staleness are always loss.
- WARN: any day at 800,000 or more; a missing daily table in the last 30 days; export and Data API daily counts differ by more than 2% after the consent adjustment, on tables more than 3 days old; daily tables carry an expiration but BQ2b was not run or `earliest_expiry` could not be read.
- PASS: under the limit, fresh, complete, kept long enough; with streaming only, no daily limit applies and the partial note says so.
- N/A: no link (G17 rates a required one).
- Why: Google pauses the daily export when a standard property keeps exceeding the limit, may pause at once after a large excess, and does not reprocess paused days; editors and administrators get an email each time the limit is exceeded (answer 9823238). Google can change a daily table for up to 3 days after its date (answers 7029846, 9358801), so the count comparison skips newer tables. BigQuery sandbox tables expire after 60 days. A table keeps the expiration it was created with: changing the dataset default affects new tables only, so after a sandbox upgrade the old daily tables still expire unless each table's expiration is updated (BigQuery dataset and sandbox docs).
- Agent adds: on advanced consent mode, compare `events_not_denied` with the Data API count; denied cookieless pings are in the export (answer 9358801) but not in reports. Google does not say whether the limit counts them, so the engine takes the higher of R02 and the BigQuery count as the conservative figure. Verify on the live BigQuery Export pages.
- Change hint: P1; options are excluding low-value events, streaming export, or 360. Each changes cost or scope. For expiring tables, the BigQuery owner removes or extends each table's expiration as well as the dataset default.

## G. Custom definitions

### G19 custom-definition-design

- Evidence: customDimensions, customMetrics, calculatedMetrics (or getMetadata prefixes on route A); serviceLevel.
- WARN: any quota at 80% or more (standard 50 event, 25 user, 10 item dimensions, 50 custom metrics, 5 calculated metrics; 360: 125, 100, 25, 125, 50); a registered ID or timestamp (client_id, session_id, ga_session_id, user_id, event_id, order_id, transaction_id, timestamp, gclid, uuid); a definition that duplicates a predefined dimension; legacy display names ending in a bracketed event name.
- PASS: quotas under 80%, no IDs or duplicates.
- Why: Google advises against unique-ID custom dimensions; after reaching a limit, wait 48 hours after removing a definition before adding one; archiving cannot be undone and breaks audiences that use the definition (answers 14240153, 12436143).
- Change hint: P4.

### G20 custom-definition-data

- Evidence: R14 per registered event and user dimension (28-day fill, one-day distinct count, email-like and phone-like counts); getMetadata.
- WARN: only (not set) for 28 days (wrong parameter name or scope, or a dead slot); more than 500 distinct values in one day; parameters on key events in BigQuery that are not registered.
- PASS: every probed definition populated, under 500 values a day.
- N/A: no event or user dimensions registered.
- Why: more than 500 values a day is Google's high-cardinality warning sign, which leads to (other) rows (answer 12226705).
- Agent adds: PII-like values score in G24, not here.
- Change hint: P4.

## H. Attribution and channels

### G21 attribution-settings

- Evidence: `getAttributionSettings` model and lookback windows; config `sales_cycle_days`; intake change history; annotations.
- WARN: GOOGLE_PAID_CHANNELS_LAST_CLICK without a decision; other key events lookback shorter than the sales cycle; acquisition lookback 7 days without a decision; a model or lookback change in the last 12 months without an annotation.
- PASS: documented model, lookback covers the sales cycle.
- Why: defaults are 30 days (acquisition) and 90 days (other key events). Model changes apply to historical and future data; lookback changes apply forward and also shorten how long direct sessions inherit an earlier source (answers 10597962, 9191807).
- Change hint: P3; the measurement owner decides.

### G22 channel-groups-and-unassigned

- Evidence: channelGroups (systemDefined, primary, rule order and expressions); R04 and R04P sessions by sessionDefaultChannelGroup and sessionPrimaryChannelGroup; R06 key event credit by channel.
- FAIL: Unassigned is 10% or more of sessions in the default group or in a custom primary group, or 5% or more of key events are credited to Unassigned.
- WARN: Unassigned between 2% and 10% of sessions in either group (the report states both shares); Direct up 10 percentage points or more on the previous 28 days; in the primary custom group, an AI assistant channel after Referral, no channel for medium ai-assistant and no AI assistant channel at all, or a rule matching (not set); a primary group change in the last 90 days without an annotation.
- PASS: Unassigned under 2% in both groups, group order sound.
- Why: the first matching channel wins; custom groups apply to history, a new primary group applies forward only; standard properties allow 2 custom groups of up to 50 channels (answer 13051316). The AI Assistant channel and ai-assistant medium arrived on 13 May 2026 (release notes). A custom group copied before that date has no rule for the new medium, and a Referral rule on medium referral does not catch it, so that traffic can be Unassigned only in the custom group the team reads. Verify the rule expressions at run time.
- Agent adds: hand causes to ga4-unassigned-not-set-diagnosis; never propose relabelling as the fix.
- Change hint: P3.

## I. Consent and privacy

### G23 consent-signals

- Evidence: intake Consent settings (EEA share, per-stream advertising and behavior analytics signals), Google tag Manage default consent settings and consent mode override; config `consent_mode` and `tags_wait_for_consent`; Google Ads links; BigQuery `privacy_info` shares.
- Primary: at least one `consent.stream.*` intake row, unless `consent.eea_share` shows 0% traffic.
- FAIL: a stream with EEA traffic shows ad_user_data or ad_personalization not received while the property shares data with Google Ads (a link with personalization, imported key events or audiences); Manage default consent settings is Yes while consent mode is not implemented (consent_mode none or empty) and tags load before consent (tags_wait_for_consent false).
- WARN: Manage default consent settings is Yes while consent mode is basic or advanced (Google does not apply the setting while consent mode is used, but Yes still blocks the consent mode override and would label data as consented if consent mode were removed; set it to No); Yes with the consent setup not recorded (confirm tags load only after consent); behavior analytics signals flagged; analytics_storage unset on 5% or more of EEA events in BigQuery; consent mode override regions that differ from `decisions.md`.
- PASS: signals present for every stream with EEA traffic. This says what GA4 received, not that the visitor's choice was honoured: the default's owner and timing are gtm-container-audit R02, and runtime behaviour is tracking-audit CON-01 to CON-07 and consent-debugging. A G23 PASS passes neither.
- N/A: no traffic from regions where consent is required.
- Why: ad_user_data is needed for ads measurement and ad_personalization for personalization; the Consent settings page can take 48 to 72 hours to update after a fix (answers 14275483, 13802165). Yes in Manage default consent settings labels data sent to Google as consented when consent mode is not used; if consent mode is used, the Google tag default settings are not applied, and the consent mode override works only when the setting is No (answer 12131703).
- Agent adds: rate against the documented decision only; runtime proof goes to consent-debugging. gtm-container-audit R02 applies the same Manage default consent rule to the container's Settings tab.
- Change hint: P1.

### G24 pii-and-redaction

- Evidence: R15 PII probe (reduced rows, or count-only rows on route A); R14 email-like and phone-like counts; dataRedactionSettings per web stream; userProvidedDataSettings; account dataSharingSettings; intake Measurement Protocol secrets.
- Primary: R15 and the data redaction settings of every web stream.
- FAIL: a URL with an email-like value, or, with the key name normalised (lower case, letters and digits only), a query key from the PII list (email, emailaddress, mail, phone, phonenumber, tel, mobile, firstname, lastname, fullname, address, dob, birthdate, dateofbirth, ssn) or the secret list (password, passwd, pwd, token, accesstoken, authtoken, idtoken, resettoken, refreshtoken, apikey, secret, clientsecret, otp, auth, reset, and any key ending in token, password or secret) with a value; a custom dimension with email-like or phone-like values.
- WARN: ambiguous keys (name, zip, zipcode, postcode, id) for human review; email redaction off; query parameter redaction off when PII keys were found; user-provided data collection or automatic detection on without a decision; Measurement Protocol secrets present; account data sharing with Google products & services on without a recorded decision (`data_sharing` in decisions.md).
- PASS: no PII rows, redaction on.
- Why: Google's policy prohibits sending personal data to Analytics. Redaction is web-only, best effort and client-side, on by default only for newer properties, and does not cover Measurement Protocol or data import (answer 13544947).
- Reporting rule: never print, store or paste values. Report host, path, key names, the rule matched and event count. A key name that looks like an email or is not a plain parameter name is written as REDACTED. Phone-like means a leading + or a separated 3-3-4 pattern; plain digit strings are not counted.
- Change hint: P1; remove the data at the source first, then turn on redaction; the privacy owner decides about data already collected.

## J. Reporting integrity

### G25 thresholding-sampling-cardinality

- Evidence: metadata of every report (subjectToThresholding, samplingMetadatas, dataLossFromOtherRow); R13 probes; R02 average daily events; serviceLevel.
- WARN: sampling horizon under 90 days, computed as 10,000,000 (standard) or 100,000,000 (360 default; up to 1,000,000,000 with more detailed results) divided by average daily events; the key event probe without demographics is thresholded; the demographic probe is thresholded and demographics are used; the 28-day page report has an (other) row.
- PASS: horizon 90 days or more, needed reports not thresholded.
- Why: explorations sample above 10 million events per query on standard; 360 properties start at a default of 100 million per query and reach 1 billion when the analyst selects more detailed results (answer 13331292). The caveat states the default exploration limit. Thresholds are system defined and cannot be set (answer 9383630). The Data API allows 120 potentially thresholded requests per hour per property.
- Change hint: P4; the fix is usually the analyst's query scope or BigQuery, not a setting.

### G26 diagnostics-and-change-records

- Evidence: intake notifications, diagnostics and Task Assistant data issues; property change history for 12 months (UI; the API method needs analytics.edit); reportingDataAnnotations.
- WARN: an open diagnostic older than 30 days with no owner; a change in the last 12 months that moves reported numbers (filters, key events, retention, consent) with no annotation. Attribution changes score in G21 and channel group changes in G22.
- PASS: diagnostics owned, changes annotated.
- NOT CHECKED: UI evidence not supplied.
- Why: a diagnostic for missing aggregate identifiers (gbraid, gad_) arrived on 30 July 2026; Task Assistant (29 April 2026) groups recommendations including data issues; system annotations are orange and read-only.
- Change hint: P4; proposed annotations come from the report.

## Failure mode catalogue

- debug_mode false on production: Developer filter in Testing matches most events. G07.
- Internal traffic defined but never filtered: rule and filter values differ, or Testing for months. G06.
- Internal IP rule about to delete customers: Testing match of 20% or more, or purchases at the normal rate. G06.
- Ads personalization inflates key events: session_start marked, session key event rate near 100%. G11.
- Purchase counted once per session after the goal migration. G12.
- form_submit as the lead key event. G11 and G03.
- Payment provider takes key event credit. G09.
- Second owned domain as a referral, or listed as an unwanted referral. G09 and G10.
- Staging or a copied tag sends to production. G08.
- Hostname Include filter drops a new checkout host. G08.
- Two streams on one site. G02.
- BigQuery daily export over 1 million events, on the higher of R02 and the BigQuery count. G18.
- Sandbox export expires after 60 days. G18.
- Upgraded from the sandbox, old daily tables still expire. G18.
- Purchase excluded from the export, or a stream not exported. G17.
- GA4 purchases far from the order system's count, or item revenue apart from purchase revenue. G13.
- Lead action in Google Ads counting every conversion per click. G16.
- Behavioural modelling expected but not active, or not under Blended. G05.
- Custom primary channel group without a rule for medium ai-assistant. G22.
- Account data sharing switched on without a decision. G24.
- Custom dimension slots spent on IDs. G19 and G20.
- Dead custom dimension: only (not set) for 28 days. G20.
- PII in page URLs, redaction off, Measurement Protocol not covered. G24.
- EEA consent signals missing with Google Ads linked. G23.
- Data marked consented by Manage default consent settings when consent mode is not used. G23.
- Two-month retention. G04.
- Silent attribution change without an annotation. G21 and G26.
- Custom channel group mislabels (AI after Referral, rules matching (not set)). G22.
- google / cpc without Google Ads data. G15.
- Same purchase counted twice for bidding. G16.
- Analysts read sampled explorations. G25.
- Thresholded rows read as zero. G25 and G05.
- Silent event edits. G14.
- Page-based key event duplicated by a create rule. G14 and G12.
