# Contract Compatibility Policy (YSD-2010)

Version 1.0.0 — 2026-07-18. Governs every versioned study-workspace artifact
class: **contracts, events, taxonomies, saved views, and portable exports**.
Enforced automatically by
`libs/contracts/scripts/generate-study-schemas.ts --check` (CI quality job),
whose `findBreakingChanges` detector diffs the published schemas against the
frozen per-major baseline (`libs/contracts/schemas/study/baseline/v<major>/`).

## Versioning rules (all classes)

1. **Semver, one version per namespace.** The study contracts namespace carries
   `STUDY_CONTRACTS_VERSION`; events, taxonomy vocabularies, saved views, and
   portable-export manifests each carry their own semver version field in their
   envelope (`schemaVersion` / vocabulary version / `viewVersion` / manifest
   version).
2. **Breaking = major.** A change the detector classifies as breaking (removed
   artifact, removed property, property type change, enum narrowing,
   newly-required property, `additionalProperties` tightening, union narrowing)
   MUST bump the major version, ship a new baseline via `--write-baseline`, and
   keep the prior major listed in `SUPPORTED_STUDY_SCHEMA_VERSIONS` for its
   published support window.
3. **Additive = minor.** New optional fields, new enum values, new union
   variants, new artifacts. Old readers handle these through the
   `parseWithCompat` boundary (unknown-optional-field stripping with reported
   paths — never silent), property-tested in `roundtrip.property.spec.ts`.
4. **Never rewrite history.** Baselines and prior-version canonical examples are
   append-only; a supported prior version's canonical examples must keep parsing
   under the current runtime (tested per version).
5. **Version-gated refinements are additive** (clarified 2026-07-19, first
   applied by YSD-2100 in 0.4.0). A runtime refinement that keys on the record's
   own `envelope.schemaVersion` and applies only to versions that did not exist
   when the rule landed can never invalidate a previously-valid record, so it
   ships as a minor bump with the new requirement documented on the field. The
   exempt-version set is frozen in code (e.g. `PRE_IDENTITY_SCHEMA_VERSIONS`)
   and a canonical example at a prior version must prove the exemption keeps
   parsing (rule 4).

## Per-class application

| Class                        | Version carrier                                      | Enters detection when                                                                                                                  | Extra rules                                                                                                                                 |
| ---------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Contracts                    | `STUDY_CONTRACTS_VERSION` + envelope `schemaVersion` | already live (17 published)                                                                                                            | runtime refinements may only tighten with a major bump                                                                                      |
| Events (YSD-6xxx)            | event envelope `schemaVersion`                       | event contracts publish through the same registry (`STUDY_SCHEMA_REGISTRY`) — mandatory at creation                                    | consumers must tolerate unknown event kinds (route to dead-letter, never drop silently)                                                     |
| Taxonomies (YSD-2035)        | vocabulary version                                   | taxonomy term schemas publish through the registry; term REMOVAL is always breaking; deprecation + migration rules ride minor versions | migration rules required per YSD-2035                                                                                                       |
| Saved views (YSD-2158)       | `viewVersion`                                        | saved-view schemas publish through the registry                                                                                        | a view referencing removed query features must open in a degraded-but-honest mode, not fail silently                                        |
| Portable exports (YSD-15xxx) | manifest version                                     | export-manifest schemas publish through the registry                                                                                   | exports embed the schema versions they were written with; importers use `assertSupportedSchemaVersion` and refuse unknown majors explicitly |

The registry is the enforcement chokepoint: an artifact class cannot ship a
schema without entering `STUDY_SCHEMA_REGISTRY` (the manifest-equality test
fails otherwise), and everything in the registry is baseline-diffed in CI.
Adding a class is therefore automatically adding its breaking-change detection —
there is no per-class opt-in to forget.

## Deprecation and support windows

- A major version remains supported (parseable, listed in
  `SUPPORTED_STUDY_SCHEMA_VERSIONS`) for at least one full release cycle after
  its successor ships, and until every persisted record of that major has been
  migrated (YSD-0011 migration plans) — whichever is later.
- Dropping a version from the supported list is itself a breaking change: it
  requires a migration evidence entry in the program evidence log and a major
  bump.

## Escalation

A change the detector flags as breaking that the author believes is safe is NOT
self-approved: it goes to the decision owner as a decision-log entry (same
harness as Section 0.2), because "safe breaking change" judged by the change
author is exactly the failure mode this policy exists to stop.
