Backward Compatibility Policy
Versioning strategy, deprecation timelines, breaking change policy, and migration support for ObjectStack
ObjectStack follows strict backward compatibility guarantees to ensure predictable upgrades and long-term stability for production deployments.
Applies to: @objectstack/spec and all packages in the ObjectStack ecosystem.
Versioning Strategy
While the launch window is open, a breaking change ships as a MINOR release — not a MAJOR one. Every published @objectstack/* package versions in lockstep, so the version number alone is not an upgrade-safety signal. This rule is in force today and overrides the MAJOR/MINOR mapping in the tables below wherever they disagree; read Launch Window at the end of this page before you plan an upgrade.
ObjectStack follows Semantic Versioning 2.0.0 (MAJOR.MINOR.PATCH). The table below describes what each component means; the launch-window rule above governs which component a breaking change actually lands in today.
| Version Component | When Incremented | Guarantee |
|---|---|---|
| MAJOR (X.0.0) | Incompatible API changes | May contain breaking changes |
| MINOR (0.X.0) | New features — and, during the launch window, breaking changes | Existing code may require migration; the release notes lead with what broke |
| PATCH (0.0.X) | Bug fixes, backward-compatible | No behavior changes, only fixes |
SemVer Guarantees
- Zod schemas are part of the public API surface. Adding optional properties is a MINOR change; removing or renaming properties is a breaking change, which during the launch window also ships in a MINOR release.
- TypeScript types inferred from Zod (
z.infer<typeof Schema>) follow the same guarantees as their source schemas. - Helper functions (e.g.,
defineStack,defineStudioPlugin) maintain their call signatures within a MAJOR version. - Input formats —
defineStack()accepts both array and map (Record) format for all named metadata collections. Both formats are guaranteed stable within a MAJOR version. - Enum values are append-only within a MAJOR version. Existing values are never removed or renamed in MINOR/PATCH releases.
- Default values in schemas are not changed in MINOR/PATCH releases unless fixing a documented bug.
// These imports are guaranteed stable within a MAJOR version
import { FieldSchema, ObjectSchema } from '@objectstack/spec/data';
import { defineStack } from '@objectstack/spec';
import { defineStudioPlugin } from '@objectstack/spec/studio';Deprecation Timeline
When a feature, schema property, or API is deprecated, ObjectStack follows a structured wind-down process:
Phase 1: Deprecation Notice (MINOR release)
- Feature is marked
@deprecatedin JSDoc comments - Zod schema property gets a
.describe('DEPRECATED: ...')annotation - Runtime warning is emitted on first use (once per session)
- Migration path is documented in the CHANGELOG
Phase 2: Migration Period
- Deprecated feature continues to function without behavior changes
- Documentation is updated with migration guides
- Code examples are updated to use the replacement
- CLI tooling may provide automated migration commands
Phase 3: Removal (MINOR release, during the launch window)
- Deprecated feature is removed from the schema
- TypeScript types no longer include the property
- Runtime code no longer supports the feature
- The removal is called out in the release notes and marked
**BREAKING**in the changeset
Shipped examples: 17.2.0 retired http_request_errors_total and sys_position.permissions under ADR-0049 enforce-or-remove, and 15.1.0 removed tenancy.strategy and tenancy.crossTenantAccess — all three in Minor Changes sections.
Timeline Summary
flowchart TD
A["Deprecation notice — feature marked deprecated (warning emitted)"] --> B["Migration period — deprecated feature keeps working, no behavior change"]
B --> C["Removal — retired at the boundary the ADR-0087 registry records (MINOR release, during the launch window)"]
No minimum survival window. ObjectStack does not guarantee a deprecated feature survives for a fixed number of releases — ADR-0049's dispositions for a flagged property are enforce, experimental, or remove, and none of them carries a dwell time. A deprecated feature is retired at the boundary the ADR-0087 registry records for it; during the launch window that removal itself lands in a MINOR release, so a MINOR bump is where you should expect a removal to arrive.
Breaking Change Policy
What Constitutes a Breaking Change
Read the Breaking? column, not the version number: during the launch window every row below — breaking or not — ships in a MINOR or PATCH release, so the bump size does not tell you whether you have work to do.
| Change Type | Breaking? | Version Impact (launch window) |
|---|---|---|
| Removing a schema property | Yes | MINOR |
| Renaming a schema property | Yes | MINOR |
| Changing a property from optional to required | Yes | MINOR |
Narrowing a type (e.g., string → enum) | Yes | MINOR |
| Removing an enum value | Yes | MINOR |
| Changing default values | Yes | MINOR |
| Adding a new optional property | No | MINOR |
| Adding a new enum value | No | MINOR |
Widening a type (e.g., enum → string) | No | MINOR |
| Adding a new schema/export | No | MINOR |
| Fixing validation to match documented behavior | No | PATCH |
| Fixing typos in descriptions | No | PATCH |
Breaking Change Process
- RFC (Request for Comments) — Breaking changes are proposed as GitHub issues with the
protocol:breakinglabel. - Deprecation — The old behavior is deprecated in a MINOR release (see timeline above).
- Migration Guide — A detailed migration guide is published before the removal lands, in the release notes for the version that carries it.
- Release — During the launch window the breaking change ships in the next MINOR version, carrying a changeset entry marked
**BREAKING**.scripts/check-changeset-no-major.mjsfails any pull request that declares amajorbump, because under lockstep onemajorwould promote all 70 published packages.
Migration Support
Linting Your Project
The CLI's lint command checks your metadata for style and convention issues and surfaces them. Adding --fix prints the suggested fix for each issue (dry-run; it does not modify files):
# Check metadata for style and convention issues
os lint
# Show what could be auto-fixed (dry-run)
os lint --fix
# Scan your source for deprecated ObjectStack patterns
os doctor --scan-deprecations
# Compare two configs and detect breaking changes between versions
os diff <before> <after> --breaking-onlyThere is no lint --deprecations flag — deprecation scanning lives on os doctor --scan-deprecations. An automated codemod command is referenced in the migration guides but is not yet available; until then, combine os doctor --scan-deprecations, os diff, and the CHANGELOG migration notes (below) to upgrade across MAJOR versions.
Migration Guides
Each MAJOR release includes:
- CHANGELOG.md — Complete list of changes with migration notes
- RELEASE_NOTES.md — High-level summary and upgrade instructions
- Schema Diff — Use
os diff <before> <after>to produce a machine-readable (--json) diff of breaking changes between two configs
Support Windows
| Version | Status | Support Level |
|---|---|---|
| Current MAJOR (N) | Active | Full feature development, bug fixes, security patches |
| Previous MAJOR (N-1) | Maintenance | Critical bug fixes and security patches only |
| Older versions (N-2+) | End of Life | No support; upgrade recommended |
Spec Package Guarantees
The @objectstack/spec package provides additional stability guarantees:
Schema Stability
- All Zod schemas are validated by comprehensive test suites
- Schema changes require both Zod and JSON Schema output validation
- Property additions must include
.describe()annotations and.optional()or.default()markers
Export Stability
- All public exports are listed in the package's
index.tsbarrel files - Removing an export is always a breaking change; during the launch window it ships in a MINOR release
- Internal modules (prefixed with
_or ininternal/directories) are not covered by SemVer guarantees
Runtime Behavior
Schema.parse()results are guaranteed consistent within a MAJOR version- Default values applied by
.default()are stable within a MAJOR version - Validation error messages may change in MINOR/PATCH releases (do not rely on exact error text)
Launch Window: MINOR Releases Can Contain Breaking Changes
This exception overrides the MAJOR/MINOR mapping above — read it before you plan an upgrade. Every published @objectstack/* package versions in lockstep, and while the launch window is open a breaking change ships as a MINOR release rather than burning a MAJOR. The current release, 17.2.0, is a MINOR and it contains a documented breaking change.
Which surfaces this covers
All of them. This is not scoped to an experimental corner or a pre-release channel: all 70 packages published from this repository belong to a single Changesets fixed group, so they share one version number and one policy. No published surface is exempt.
The convention is enforced rather than informal — scripts/check-changeset-no-major.mjs fails any pull request that introduces a major bump, because under lockstep a single major on one package would promote the entire stack.
What this means for an upgrade
- Do not read a MINOR bump as safe to take unattended. Read the release notes for the version you are moving to: they lead with breaking changes and carry the migration steps.
- Pin exact versions instead of caret ranges if you cannot review each MINOR before it lands.
- Diff your own metadata across the upgrade with
os diff <before> <after> --breaking-only.
MAJOR releases do still happen — 17.0.0 was cut precisely because its breaking density was too high to carry ^16.x consumers across on a caret range — but an individual breaking change does not, on its own, force one.
When it stops applying
The tables and deprecation timeline above now state this rule directly, so the page no longer contradicts itself. Classic SemVer — where a breaking change once again requires a MAJOR — is the settled form the project returns to once the launch window closes: the window closes at GA, and strict SemVer resumes from that point. Until it closes, this section is the operative rule wherever any part of this page disagrees.
Reporting Compatibility Issues
If you encounter an unintended breaking change:
- Check the CHANGELOG — Verify the change was not documented as intentional.
- Open an issue — File a GitHub issue describing the unintended change.
- Include a reproduction — Provide a minimal code sample showing the breakage.
- Reference the version — Specify the exact versions where behavior changed.