# Naming lint rules

Apply these rules to a list of raw names exactly as they are emitted or observed: copied from code, a tracking plan, a GA4 events report, Mixpanel Lexicon, Amplitude Data or a warehouse `event_name` column. Do not clean names before linting; the problems live in the raw spelling.

`scripts/lint_event_names.py` implements every rule marked (script). Rules marked (manual) need context the script does not have; apply them by reading the list. The reserved lists in the script were copied on 2026-10-17; refresh them from `platform-constraints.md` and the live pages before relying on them.

## Contents

- Severity and the approval rule
- How to run
- Event name rules
- Property name rules
- Destination mapping rules
- Manual rules
- Output format

## Severity and the approval rule

- ERROR: blocks approval. Fix the name, or record an approved exception with a reason, owner and date in `martech-context/decisions.md`.
- WARN: needs either a fix or a one-line reason in the registry entry. A warning with no reason counts as unresolved.
- A list is approved when it has zero unresolved errors and zero unresolved warnings.
- A kept Title Case or camelCase taxonomy (R07) is not a per-name exception. Record the convention once in `naming-rules.md` and `decisions.md`, lint with `--convention title_case` or `--convention camel_case`, and judge GA4 on the destination names in the mapping.

## How to run

Call the script by the absolute path of this skill's `scripts/` folder; the input and vocabulary paths are the user's files. The commands below are shortened to `scripts/`.

```bash
python3 scripts/lint_event_names.py events.txt
python3 scripts/lint_event_names.py events.txt --objects objects.txt --actions actions.txt
python3 scripts/lint_event_names.py properties.txt --kind property
python3 scripts/lint_event_names.py user_properties.txt --kind user_property
python3 scripts/lint_event_names.py mapping.csv --kind mapping --objects objects.txt --actions actions.txt
python3 scripts/lint_event_names.py events.txt --convention title_case
```

- Name lists: one name per line; blank lines and lines starting with `#` are skipped; no inline comments.
- Property lists: `name` or `name,type` per line. Types: `string`, `number`, `integer`, `boolean`, `timestamp`, `date`, `array`, `object`.
- Mapping: CSV with the header `canonical_name,destination,destination_name`; destinations `ga4`, `mixpanel`, `amplitude`, `posthog`, `segment` or any other label. Optional columns `canonical_property,destination_property` record one parameter per row, including destination renames (`signup_method` sent to GA4 as `method`). Other columns are ignored, so a `datalayer_event` column can record the GTM dataLayer event beside the GA4 Event Name. GA4 destination properties are linted with the GA4 rules (P01 to P04), P11 and P12 only, counted per event (M09) and checked for `value` without `currency` (M11); Mixpanel and Segment property rules (P06, P07) do not run on them.
- `--convention` applies to event names: `snake_case` (default), `title_case` or `camel_case`. Under the last two, a name that does not follow the declared convention is the N03 error; N05, N08, N10 and the GA4 prefixes of N09 do not run on canonical names, because M03, M04 and M07 check the names GA4 actually receives; N04 becomes a warning. Property rules stay snake_case; record an exception in `decisions.md` if an existing property taxonomy differs.
- Vocabulary files: one term per line, `#` starts a comment. An optional line `forbidden: removed=deleted, cancelled=canceled, clicked` lists forbidden synonyms, with the approved term after `=`. `assets/actions.txt` is a starting action vocabulary; `assets/example-acme-objects.txt` shows an object vocabulary for a fictional product. Build the user's own lists from the backticked terms in `naming-rules.md`, as temporary files outside `martech-context/`, and check that both list the same terms.
- For a name that fails N03, the structure rules (N06, N07, N11, N12) wait for the fixed name; the word rules (N13 to N16) still run on its normalised words.
- The script prints one finding per line, a summary, and exits 1 when any error is found. It needs only Python 3 and changes nothing.
- Self-test after editing the script or its lists: `assets/example-acme-events-input.txt` gives 18 checked, 8 errors, 10 warnings; `assets/example-acme-mapping.csv` with the Acme vocabularies gives 23 checked, 0 errors, 2 warnings. A different count means a rule or list changed; update `assets/example-acme-review.md` with the new output.

## Event name rules

- N01 ERROR (script): leading or trailing whitespace. Whitespace variants become separate events in every tool checked. Blank and whitespace-only lines are skipped as separators, so an empty name in a name list is not reported; M00 reports an empty name in a mapping.
- N02 ERROR (script): the same name appears twice in the list. In a plan this means two definitions of one event.
- N03 ERROR (script): does not follow the declared convention. Default snake_case pattern: `^[a-z][a-z0-9]*(_[a-z0-9]+)*$`; the finding names the cause: uppercase, space, hyphen, dot, colon, dollar sign, bracket, slash, leading digit, leading underscore, double underscore, trailing underscore, non-ASCII. Under `title_case` or `camel_case`, the finding says the taxonomy mixes conventions.
- N04 ERROR (script): longer than 40 characters. GA4 does not log the event and will not report it as a key event. WARN under `title_case` or `camel_case`; the GA4 destination name decides.
- N05 WARN (script, snake_case): longer than 32 characters. House margin so a sibling outcome name or a destination prefix still fits in 40.
- N06 ERROR (script): a single token, or a verb and particle with no object (`log_in`). A name needs an object and an action. GA4 recommended names such as `login` are exempt under every convention: N10 handles them under snake_case, and M04 on the ga4 rows of a mapping under `title_case` or `camel_case` (`Login`, `Sign Up`, `Purchase`).
- N07 WARN (script): more than 5 tokens. Long names usually carry a value or a UI location that belongs in a property.
- N08 ERROR (script, snake_case): equals a GA4 reserved event name of any stream type, ignoring case, including the Firebase app names `app_background`, `app_store_subscription_convert` and `session_start_with_rollout`, plus `screen_view` and `video_complete`. A canonical name avoids every stream's list because it may reach an app stream later. For `ad_impression`, `in_app_purchase` and `screen_view` the message says reserved on web streams and Google's own event on app streams: on an app stream they are allowed only with Google's meaning (ad revenue, purchases, manual screen tracking), recorded as an exception like the web `page_view`.
- N09 ERROR (script): starts with a reserved prefix: `firebase_`, `google_`, `ga_` (Firebase and GA4), `query_id` (GA4 web streams), `$` (Mixpanel and PostHog default events), `[Amplitude]` (Amplitude internal events). Under `title_case` or `camel_case`, only the `$` and `[Amplitude]` checks run on canonical names.
- N10 WARN (script, snake_case): equals a GA4 recommended event name (`sign_up`, `login`, `purchase`, `generate_lead` and the rest). Keep it only when the trigger matches Google's meaning and the required parameters exist. Otherwise use a business name and map it to the recommended name at the GA4 destination.
- N11 WARN (script): the action is not in the action vocabulary. Without a vocabulary file the script checks that the last token looks past tense (ends in `ed` or is a known irregular form such as `sent` or `paid`). A trailing `in`, `out`, `up`, `on`, `off` or `down` is read with the token before it (`user_logged_in`). When the unregistered action or object is on the vocabulary file's `forbidden:` line, R05 replaces N11 or N12.
- N12 WARN (script): the object is not in the object vocabulary; with both vocabularies, the name is not exactly one object plus one action, which means a middle token is unregistered.
- N13 WARN (script): a token contains a digit (`step_3`, `v2`, `2026`). Steps, versions, dates, plan codes and IDs belong in properties.
- N14 WARN (script): a temporary or relative word: `test`, `tmp`, `temp`, `new`, `old`, `final`, `copy`, `legacy`, `latest`. These describe the release, not the fact.
- N15 WARN (script): a UI gesture or element: `click`, `clicked`, `tap`, `tapped`, `button`, `btn`, `link`, `modal`, `hover`, `hovered`, `icon`, `cta`. Name the business fact unless the interaction itself is the question.
- N16 WARN (script): filler: `successfully`, `success`, `event`, `action`. A completed action already means success; use `succeeded` and `failed` only for asynchronous outcomes.
- N17 ERROR (script): two different raw names collapse to the same string after lowercasing, splitting camelCase and replacing spaces, hyphens, dots and colons with underscores. Case-sensitive tools split one action across them; a lowercasing connector merges them.
- N18 WARN (script): likely the same event: the same words in a different order (`created_report`, `report_created`) or singular versus plural (`reports_exported`, `report_exported`).
- N19 WARN (script): the list mixes spellings of one word: `canceled` and `cancelled`, `signup` and `sign_up`, `login` and `log_in`, `logout` and `log_out`, `setup` and `set_up`, `checkout` and `check_out`, `signin` and `sign_in`.

## Property name rules

- P01 ERROR (script): not lowercase ASCII snake_case, or leading or trailing whitespace.
- P02 ERROR (script): longer than 40 characters for an event property (GA4 parameter name), or longer than 24 for a user property (GA4 user property name).
- P03 ERROR (script): a GA4 reserved parameter name such as `user_id`, `session_id`, `customer_id` or `gclid`. WARN for `currency` in a property list, which is allowed only as the ISO 4217 code of `value` or `revenue`; not raised for a GA4 destination property, where M11 checks the pairing instead.
- P04 ERROR (script): starts with `_`, `firebase_`, `ga_`, `google_` or `gtag.`.
- P05 ERROR (script, user properties): a GA4 reserved user property name such as `first_open_time` or `user_id`.
- P06 ERROR (script) for `bucket`; WARN for the other Mixpanel reserved names (`token`, `ip`, `distinct_id`, `time`, `alias`) unless you are deliberately setting Mixpanel's own field. Not run on GA4 destination properties.
- P07 WARN (script): `revenue` or `value`. Segment reserves both for specific meanings; confirm the meaning and units. Not run on GA4 destination properties, where `value` and `currency` are Google's standard parameters.
- P08 WARN (script, needs type): a boolean that does not start with `is_` or `has_`.
- P09 WARN (script, needs type): a timestamp that does not end in `_at`, or a name ending in `_at` that is not a timestamp.
- P10 ERROR (script, needs type): one name sent with two types. Every tool that infers types from data will mix or split them.
- P11 WARN (script): a token contains a digit. Usually a dynamic key such as `feature_1`; use an array or a property value instead.
- P12 ERROR (script): a name that is personal data by definition: `email`, `email_address`, `phone`, `phone_number`, `first_name`, `last_name`, `full_name`, `password`, `ip_address`, `street_address`, `date_of_birth`, `ssn`. WARN when a token is `email`, `phone`, `password`, `passwd` or `ssn` (`email_verified` may be fine as a boolean).
- P13 WARN (script): an unknown type label.
- P14 ERROR (script): two different raw names collapse to one property after lowercasing, splitting camelCase and replacing separators (`plan_code`, `planCode`, `Plan Code`). Mixpanel and Amplitude split the values across them.
- P15 WARN (script, needs type): a suffix contract is broken: `_id` not typed `string`, `is_` or `has_` not typed `boolean`, `_count` not typed `integer`. Under an Amplitude schema that rejects unexpected property types, rejected data is not ingested; the docs (checked 2026-10-17) do not say whether the value or the whole event is dropped, so test with one event before release.

## Destination mapping rules

The script first runs the event name rules on the unique canonical names, then:

- M00 ERROR (script): missing header or missing destination name.
- M01 ERROR (script): two canonical events send the same name to one destination. Allowed only with equivalent triggers and a written dedupe rule.
- M02 WARN (script): two destination names differ only by case or separators in one destination.
- M03 ERROR (script, `ga4`): the destination name fails GA4 syntax, is longer than 40 characters, is a reserved name or starts with a reserved prefix. Approved exceptions: a manual `page_view` sent with Google's meaning on web, and `ad_impression` or `screen_view` sent with Google's meaning on an app stream.
- M04 WARN (script, `ga4`): mapped to a recommended event. Confirm the trigger matches and the required parameters are mapped.
- M05 ERROR (script): a `$` name sent to `mixpanel` or `posthog`, or an `[Amplitude]`, `$identify` or `$groupidentify` name sent to `amplitude`.
- M06 WARN (script): the destination name differs from the canonical name (other than a GA4 recommended mapping). Record why: connector transformation, legacy name or vendor convention.
- M07 WARN (script, `ga4`): the destination name is one GA4 can mark as a key event automatically: `purchase` on every property; `first_open`, `in_app_purchase`, `app_store_subscription_convert` and `app_store_subscription_renew` on app streams; and `add_to_cart`, `add_to_wishlist`, `begin_checkout`, `view_item`, `view_item_list` and `view_search_results` (and `session_start`) when a linked Google Ads account has Ads Personalization on. The app names are also reserved (M03), so M07 matters only for an approved exception such as `in_app_purchase` on an app stream. Record in the mapping whether it will become a key event and which Ads conversion it would feed, and get the owner's approval. See `platform-constraints.md`.
- M08 ERROR (script): one canonical event sends two different names to the same destination.
- M09 ERROR (script, `ga4`): more than 25 distinct `destination_property` values for one GA4 event. List only the parameters you send; check the live collection limits page for which automatically added parameters count.
- M10 WARN (script, `ga4`): the destination name is a case variant of a GA4 reserved or recommended name (`Purchase`, `Page_View`, `Sign_Up`). GA4 event names are case sensitive, so it is a separate custom event and does not count as Google's event. Fix the mapping (for Segment, the Custom Event mapping's Event Name or Lowercase Event Name field).
- M11 ERROR (script, `ga4`): an event maps `value` without `currency`. Google requires `currency` whenever `value` is set (an ISO 4217 code).

## Manual rules

- R01 ERROR: the name describes a different fact than its trigger. `report_created` must not fire on the click that requests the report. Check the trigger in the contract.
- R02 ERROR: one name, two meanings. Two producers (browser and server, two apps) send the same name at different moments. Ask who emits it and when.
- R03 WARN: the action is an intent (`clicked`, `opened`, `submitted`) for something that has a committed outcome. Add the outcome event or rename to the outcome.
- R04 WARN: a variant is in the name (`report_exported_pdf`, `plan_pro_selected`). Move it to a property with allowed values.
- R05 WARN (script when the vocabulary file has a `forbidden:` line; manual otherwise): a synonym of an existing vocabulary term (`registered` next to `signed_up`, `removed` next to `deleted`). Pick one and record the other on the `forbidden:` line, so `report_removed` is reported as R05 with the approved term instead of a generic N11.
- R06 ERROR: personal data, free text or a full URL with a query string in a name or property value.
- R07 WARN: the list is an existing, consistently implemented Title Case or camelCase taxonomy. Do not migrate it only for style. Record the convention once in `naming-rules.md` and `decisions.md`, lint with `--convention`, map it per destination and judge GA4 on the mapped destination names. This replaces per-name N03 exceptions.

## Output format

Report findings grouped by name, errors first, with the rule ID, the raw name in quotes and the fix:

```text
ERROR N03 'Report Created': not lowercase snake_case: uppercase letter, space. Fix: report_created
ERROR N17 'Report Created' / 'report_created': same name after normalising. Fix: keep report_created; run the overlap test in the renames file before any merge
WARN  N13 'onboarding_step_3_completed': token '3' contains a digit. Fix: onboarding_step_completed with step_number
```
