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.
Run the diff
Section titled “Run the diff”The CLI lives in tooling/plan-diff/. From the repo root:
pnpm plan-diff \ --base origin/main:shared/tracking-plan/schema.yaml \ --head HEAD:shared/tracking-plan/schema.yamlThe exit code is non-zero when a breaking change is detected, so a CI step
that runs pnpm plan-diff … will fail the PR.
What counts as breaking
Section titled “What counts as breaking”| Change | Breaking? | Why |
|---|---|---|
| Add an event | No | New events do not affect existing consumers. |
| Add an optional prop to an event | No | Existing emits keep working; existing readers ignore unknown fields. |
| Add a required prop to an existing event | Yes | Existing emits without the new prop start failing validation. |
| Remove an event | Yes | Existing readers and emits break. |
| Remove a prop | Yes | Existing readers expecting it break. |
Tighten a constraint (e.g. add pattern, raise minLength) | Yes | Previously-accepted payloads start failing. |
| Loosen a constraint | No | Previously-accepted payloads still pass. |
| Change a prop’s type | Yes | Type contract changes. |
| Rename an event | Yes | Old name disappears. (Add the new event in parallel and deprecate.) |
Change description, owner, classification | No | Metadata only. |
Marking an intentional break
Section titled “Marking an intentional break”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: 1intentional_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.
Where to wire it
Section titled “Where to wire it”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.yamlIf 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.
What plan-diff does NOT do
Section titled “What plan-diff does NOT do”- 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).