# Versioning and change control

Changing an established event definition is expensive once reports, audiences, CRM workflows and ad conversions depend on it. These rules keep the plan, the code and the history aligned.

## Contents

- What counts as a change
- Risk tiers and approvers
- Version numbers
- Shipping a rename
- Shipping a semantic change
- Retiring an event
- Retrofitting a plan onto existing events
- Changelog format

## What counts as a change

Classify every proposal before discussing names:

- Correction: the implementation was wrong and now matches the approved contract. No version change; record the affected date range so analysts know which data was wrong.
- Documentation change: description, display name, owner. No effect on emitted data.
- Compatible change: adds an optional property, adds an allowed value that maps to its own reporting bucket, adds a destination. Existing counts are unaffected.
- Breaking change: changes the trigger, a `must_not_fire` condition, the grain, a required property, a type, an identifier, or removes an allowed value. Old and new data no longer count the same thing.
- Rename: changes the raw event or property name. Even with identical meaning, every consumer that filters on the name breaks.

## Risk tiers and approvers

- Tier 1: the event or field feeds revenue, a paid conversion, an ad-platform conversion, a CRM lifecycle or routing workflow, billing, identity or consent behaviour. Approvers: business owner, data owner and implementation owner; privacy reviewer when a personal field or consent behaviour changes. Evidence: staging trace of every acceptance case, received payload in each destination, and a dated annotation plan for affected reports.
- Tier 2: any other breaking change, a new event, or a rename. Approvers: data owner and implementation owner; the business owner approves any trigger change. Evidence: staging trace of the success, negative and retry cases.
- Tier 3: documentation and compatible changes. Approver: data owner. Evidence: one received payload.

The tier is set on the contract (`risk_tier`) when it is created. A change inherits the higher of the contract's tier and the change's tier. Approval is recorded in the contract changelog with names and date before the code merges.

## Version numbers

- `version` is an integer on each contract. Increment it for every breaking change. Do not increment for corrections, documentation or compatible changes; record those in the changelog.
- A compatible change can still change validation: a new allowed value is rejected by a Protocols or RudderStack schema that lists the old enum. Update the enforcement plan for the current version before the release that sends the value, with no version bump.
- When Segment Protocols validates the event, send the same integer as `context.protocols.event_version`. Protocols validates events with a missing or invalid version against the oldest version in the plan, so a release that forgets the field is checked against the old rules.
- RudderStack describes tracking plans as versioned with events validated against the version they were instrumented with; check how your SDK sends that version before relying on it.
- Keep a plan-level version in the journey file and bump it when any contract in the journey has a breaking change.

## Shipping a rename

First decide whether a rename is needed at all. If only the label is unclear, change the display name in Mixpanel Lexicon or Amplitude Data; both leave raw data unchanged (Amplitude display names apply to active planned events, not custom events). Rename the raw name only when the old name is actively misleading or collides.

When a raw rename is needed:

- Inventory consumers: saved reports and dashboards, funnels, cohorts and audiences, experiments, warehouse models, CDP destinations and filters, CRM workflows and lists, GA4 key events and any ad-platform conversion action built on them, lifecycle messaging triggers.
- Write the old and new definitions, the reason and the release boundary (first app version and deploy date) in the contract changelog.
- Add the new name to the enforcement tool before release (Protocols, RudderStack plan, Amplitude Data branch) so the new name is not blocked or flagged as unplanned.
- Update consumers to read both names with an explicit boundary: old name before the cut-over, new name after. Never let a metric count both during an overlap.
- If old and new names must be emitted together for a period, both carry the same `event_id` as a property and each gets its own `message_id`, derived from its own name (`contract-fields.md`). If both shared one transport key, Segment, RudderStack or Amplitude would drop whichever arrived second, often the new name. Every metric deduplicates on `event_id` across the two names. Ten real actions must not become twenty events.
- Add an acceptance case for the overlap: send one action, expect both names in the CDP debugger and in every mapped destination, and one count in each metric.
- For a GA4 key event, never emit the old and new names from the same client at once: GA4 has no general event dedupe, so both would count. Mark the new name as a key event before the release, not at it: Google states that marking counts from the time it is made and does not change historic data, and an event that has not been collected yet can be created and marked in Admin (Marketer role or above). Confirm the marking in Admin before the deploy. Keep the old one marked only while old app versions still send it, and report the two as one series split by release date or app version. Update any ad-platform conversion imported from the old key event, following that platform's current import docs. Keep the new name at 40 characters or fewer, or it will not report as a key event.
- Remove the old emission after the agreed migration period. Mobile apps need a longer period because old versions keep sending the old name (see `mobile-and-offline.md`).
- Leave the mapping in the changelog permanently. Analysts will need it years later.

## Shipping a semantic change

- If old and new data cannot be compared in the primary metric, prefer a new event name (or a new metric version) over reusing the old name with a new meaning. Example: `report_generated` meaning "job accepted" becomes "report stored and viewable" is a different fact.
- Run old and new definitions on the same controlled period before release and explain the difference with record sets: which units entered or left the numerator or denominator.
- Annotate the break in every affected report. A truthful break beats a smooth line that stitches two definitions together.
- Update `metrics.md` and the metric version when the metric's counting changes.

## Retiring an event

- Check consumers first, using the same inventory as for a rename.
- Move status to `deprecated` with a retirement date, then to `retired` after emission stops and enforcement tools no longer list it.
- Blocking in a destination is not reversible for the lost data. Mixpanel says blocked event data cannot be recovered after you block it; Segment discards blocked events unless they are forwarded to a separate source. Prefer stopping emission at the source.
- Keep the retired contract file. Historical analysis needs to know what the data meant.

## Retrofitting a plan onto existing events

- Export the observed event and property list with volumes (Mixpanel Lexicon, Amplitude Data, Segment or RudderStack catalogues, GA4 events report) and pull sample payloads from test accounts.
- Classify each event: keep (write its contract from observed behaviour, then confirm with the owner), rename with a mapping, merge into a supported definition, or retire after dependency checks.
- For each kept or renamed event, record from which date and app version its semantics are usable. A renamed event does not make old and new records comparable.
- Check identity and timestamps as carefully as names: what was `user_id` historically, was `account_id` recorded at event time, which clock was used.
- Write contracts for the events that support the first decision first. Retrofitting the whole catalogue before any decision uses it rarely finishes.

## Changelog format

Keep one `martech-context/tracking-plan/CHANGELOG.md`, newest first, plus the per-contract `changelog` list:

```text
2026-10-17  report_generated v2  breaking  Trigger moved from job accepted to report stored.
            Release: web deploy 2026-10-20, iOS 4.12.0, Android 4.12.0.
            Consumers updated: workspace_first_report_7d (v2), setup reminder workflow.
            Approved: Head of Product, Analytics lead, Reporting team lead.
```

Record consequential decisions (new identity model, enforcement switched to blocking) in `martech-context/decisions.md` as well.
