Skip to content

CI breaking-change detection

The plan-diff tool compares two versions of your tracking plan and reports breaking changes. Wire it into your CI pipeline so PRs that would silently drop downstream analytics are blocked before they merge.

The CLI lives in tooling/plan-diff/. From the repo root:

Terminal window
pnpm plan-diff \
--base origin/main:shared/tracking-plan/schema.yaml \
--head HEAD:shared/tracking-plan/schema.yaml

The exit code is non-zero when a breaking change is detected, so a CI step that runs pnpm plan-diff … will fail the PR.

ChangeBreaking?Why
Add an eventNoNew events do not affect existing consumers.
Add an optional prop to an eventNoExisting emits keep working; existing readers ignore unknown fields.
Add a required prop to an existing eventYesExisting emits without the new prop start failing validation.
Remove an eventYesExisting readers and emits break.
Remove a propYesExisting readers expecting it break.
Tighten a constraint (e.g. add pattern, raise minLength)YesPreviously-accepted payloads start failing.
Loosen a constraintNoPreviously-accepted payloads still pass.
Change a prop’s typeYesType contract changes.
Rename an eventYesOld name disappears. (Add the new event in parallel and deprecate.)
Change description, owner, classificationNoMetadata only.

When you genuinely intend a breaking change (e.g. you are renaming subscribed to subscription_started in a coordinated migration), add a top-level intentional_breaks array referencing the change:

version: 1
intentional_breaks:
- "remove event subscribed: superseded by subscription_started in 2026-Q2"
events:
# subscribed: removed
subscription_started: # …

The diff tool reports the break as expected and exits zero, but emits a human-readable warning on stderr. Reviewers see the warning in the PR output and can decide whether the migration plan is acceptable.

A typical GitHub Actions step:

- name: tracking-plan diff
run: |
git fetch origin main:refs/remotes/origin/main
pnpm plan-diff \
--base origin/main:shared/tracking-plan/schema.yaml \
--head HEAD:shared/tracking-plan/schema.yaml

If you keep the plan in a separate repo, the same command works against that repo’s main branch — git fetch it as a submodule or sibling clone.

  • It does not enforce stylistic rules (snake_case, owner attribution, description length). Use a YAML linter for that.
  • It does not validate the plan against the canonical tracking-plan schema. Use the codegen step or a JSON Schema linter for that.
  • It does not detect breaking changes between the plan and the running collector — that is the operator’s responsibility (deploy plan changes alongside the collector restart that picks them up).