Contract Versioning Guide
This guide explains how to version data contracts and manage their lifecycle in floe.
Overview
Section titled “Overview”Data contracts version independently from data products using semantic versioning. This allows contracts to evolve while maintaining backward compatibility for consumers.
Semantic Versioning
Section titled “Semantic Versioning”Contracts follow Semantic Versioning 2.0.0:
MAJOR.MINOR.PATCH
Example: 2.1.0 │ │ │ │ │ └── Patch: documentation, metadata changes │ └──── Minor: backward-compatible additions └────── Major: breaking changesVersion Bump Rules
Section titled “Version Bump Rules”MAJOR Version (Breaking Changes)
Section titled “MAJOR Version (Breaking Changes)”Increment MAJOR when changes break backward compatibility:
| Change | Example | Consumer Impact |
|---|---|---|
| Remove column | Delete email field | Queries fail |
| Change type | id: int → id: string | Type errors |
| Make optional required | phone now required | Nulls rejected |
| Add required column | New ssn required | Insert fails |
| Relax SLA | Freshness 4h → 8h | SLA degradation |
# Before: version: 1.2.0models: customers: elements: email: type: string required: true
# After: version: 2.0.0 (MAJOR bump - removed required field)models: customers: elements: # email removed!MINOR Version (Additions)
Section titled “MINOR Version (Additions)”Increment MINOR for backward-compatible additions:
| Change | Example | Consumer Impact |
|---|---|---|
| Add optional column | New nickname field | None (optional) |
| Make required optional | phone now optional | Less strict |
| Stricter SLA | Freshness 6h → 4h | Better service |
# Before: version: 1.2.0models: customers: elements: name: type: string
# After: version: 1.3.0 (MINOR bump - new optional field)models: customers: elements: name: type: string nickname: type: string required: false # Optional, so backward compatiblePATCH Version (Metadata)
Section titled “PATCH Version (Metadata)”Increment PATCH for changes that don’t affect data:
| Change | Example |
|---|---|
| Update description | Documentation fix |
| Add/change tags | New categorization |
| Change classification | Mark as PII |
| Update owner | Team transfer |
# Before: version: 1.2.0# After: version: 1.2.1 (PATCH bump - documentation only)models: customers: elements: email: type: string description: "Primary email" # Updated descriptionVersion Validation
Section titled “Version Validation”Compile-Time Check
Section titled “Compile-Time Check”The compiler validates version bumps automatically:
$ floe compile # planned target-state command
ERROR: Contract version bump invalid
Contract: my-customers Previous: 1.2.0 Proposed: 1.2.1
Breaking changes detected: - Removed column: email
Required: MAJOR version bump (2.0.0)
See: https://docs.floe.dev/contract-versioningCLI Validation
Section titled “CLI Validation”# Check if version bump is validfloe contract validate-version \ --old datacontract.yaml.bak \ --new datacontract.yaml
# Output:# Breaking changes detected:# - Removed column: email## Suggested version: 2.0.0 (current: 1.2.1)Deprecation Workflow
Section titled “Deprecation Workflow”Deprecation States
Section titled “Deprecation States”ACTIVE ────► DEPRECATED ────► SUNSET ────► RETIRED (30 days min) (7 days)| State | Behavior |
|---|---|
active | Normal operation |
deprecated | Warnings emitted, new consumers warned |
sunset | Errors for new consumers, existing warned |
retired | Contract removed |
How to Deprecate
Section titled “How to Deprecate”- Announce deprecation:
status: deprecated
deprecation: announced: "2026-01-03" sunset_date: "2026-02-03" replacement: customers-v2 migration_guide: https://wiki.example.com/migrate-customers-v2 reason: "Replacing with unified customer model"- Notify consumers:
The ContractMonitor emits deprecation warnings:
{ "violationType": "deprecation_warning", "message": "Contract my-customers is deprecated. Sunset: 2026-02-03. Migrate to: customers-v2"}- At sunset date:
Change status to sunset:
status: sunsetNew consumers get errors. Existing consumers get urgent warnings.
- After sunset period:
Remove the contract (or mark as retired).
Major Version Strategy
Section titled “Major Version Strategy”Table Namespacing
Section titled “Table Namespacing”For major version changes, use new table namespaces:
# Version 1.x tablesgold.customers_v1
# Version 2.x tables (breaking change)gold.customers_v2This allows:
- Old consumers to continue reading v1
- New consumers to adopt v2
- Parallel operation during migration
Migration Path
Section titled “Migration Path”# v1 contract - deprecatedapiVersion: v3.0.2kind: DataContractname: customers-v1version: 1.5.0status: deprecateddeprecation: replacement: customers-v2 migration_guide: https://wiki.example.com/migrate-v2
# v2 contract - activeapiVersion: v3.0.2kind: DataContractname: customers-v2version: 2.0.0status: activeInheritance and Versioning
Section titled “Inheritance and Versioning”Three-Tier Model
Section titled “Three-Tier Model”Enterprise Contract (v1.0.0) │ ├── Minimum freshness: 24h │ ▼Domain Contract (v1.0.0) │ ├── Domain freshness: 6h (stricter) │ ▼Product Contract (v2.1.0) ← Independent version │ └── Product freshness: 4h (even stricter)Key rules:
- Child contracts have independent versions
- Child contracts can only STRENGTHEN parent requirements
- Parent version changes don’t automatically bump child versions
Inheritance Validation
Section titled “Inheritance Validation”$ floe compile # planned target-state command
ERROR: Contract inheritance violation
Parent: enterprise-base (v1.0.0) - freshness: 24h (minimum)
Child: my-customers (v1.0.0) - freshness: 48h (WEAKER - violates inheritance)
Child contracts cannot weaken parent requirements.Best Practices
Section titled “Best Practices”1. Plan Breaking Changes
Section titled “1. Plan Breaking Changes”Before making breaking changes:
- Document the change rationale
- Identify all consumers
- Create migration guide
- Set deprecation timeline (minimum 30 days)
2. Use Feature Flags for Schema Changes
Section titled “2. Use Feature Flags for Schema Changes”# Add new optional field firstelements: new_field: type: string required: false # Optional initially
# Later, make required in new major versionelements: new_field: type: string required: true3. Communicate Changes
Section titled “3. Communicate Changes”## Contract Change Notification
**Contract:** customers-v1**Change:** Deprecation announced**Sunset Date:** 2026-02-03**Replacement:** customers-v2
### Migration Steps1. Update queries to use new schema2. Handle new required fields3. Update to v2 contract reference
### Breaking Changes in v2- `email` renamed to `primary_email`- `phone` split into `phone_country` and `phone_number`- `updated_at` now required
### Timeline- Jan 3: Deprecation announced- Jan 17: Migration guide published- Feb 3: Sunset (v1 warnings become errors)- Feb 10: v1 retired4. Keep Changelog
Section titled “4. Keep Changelog”# In datacontract.yaml or separate CHANGELOG.md
# Changelog## ## 2.0.0 - 2026-02-01# ### Breaking# - Removed `legacy_id` column# - Changed `id` type from int to string## ## 1.3.0 - 2026-01-15# ### Added# - New optional `nickname` column## ## 1.2.1 - 2026-01-10# ### Fixed# - Updated description for `email` field5. Test Before Release
Section titled “5. Test Before Release”# Validate contract against live datafloe contract test datacontract.yaml --connection staging
# Check version bump validityfloe contract validate-version --old v1 --new v2
# Dry-run compilefloe compile --dry-run # planned target-state commandCommon Scenarios
Section titled “Common Scenarios”Adding a Required Field
Section titled “Adding a Required Field”Wrong approach:
# This breaks consumers!version: 1.3.0 # Minor bump is wrong
elements: ssn: type: string required: true # New required fieldCorrect approach:
# Step 1: Add as optional (minor bump)version: 1.3.0elements: ssn: type: string required: false
# Step 2: After consumers update, make required (major bump)version: 2.0.0elements: ssn: type: string required: trueRenaming a Column
Section titled “Renaming a Column”# Step 1: Add new column, deprecate old (minor)version: 1.3.0elements: email: # Old type: string description: "DEPRECATED: Use primary_email" primary_email: # New type: string
# Step 2: Remove old column (major)version: 2.0.0elements: primary_email: type: string