ObjectStackObjectStack

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 ComponentWhen IncrementedGuarantee
MAJOR (X.0.0)Incompatible API changesMay contain breaking changes
MINOR (0.X.0)New features — and, during the launch window, breaking changesExisting code may require migration; the release notes lead with what broke
PATCH (0.0.X)Bug fixes, backward-compatibleNo 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 formatsdefineStack() 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 @deprecated in 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 TypeBreaking?Version Impact (launch window)
Removing a schema propertyYesMINOR
Renaming a schema propertyYesMINOR
Changing a property from optional to requiredYesMINOR
Narrowing a type (e.g., stringenum)YesMINOR
Removing an enum valueYesMINOR
Changing default valuesYesMINOR
Adding a new optional propertyNoMINOR
Adding a new enum valueNoMINOR
Widening a type (e.g., enumstring)NoMINOR
Adding a new schema/exportNoMINOR
Fixing validation to match documented behaviorNoPATCH
Fixing typos in descriptionsNoPATCH

Breaking Change Process

  1. RFC (Request for Comments) — Breaking changes are proposed as GitHub issues with the protocol:breaking label.
  2. Deprecation — The old behavior is deprecated in a MINOR release (see timeline above).
  3. Migration Guide — A detailed migration guide is published before the removal lands, in the release notes for the version that carries it.
  4. 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.mjs fails any pull request that declares a major bump, because under lockstep one major would 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-only

There 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

VersionStatusSupport Level
Current MAJOR (N)ActiveFull feature development, bug fixes, security patches
Previous MAJOR (N-1)MaintenanceCritical bug fixes and security patches only
Older versions (N-2+)End of LifeNo 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.ts barrel files
  • Removing an export is always a breaking change; during the launch window it ships in a MINOR release
  • Internal modules (prefixed with _ or in internal/ 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:

  1. Check the CHANGELOG — Verify the change was not documented as intentional.
  2. Open an issue — File a GitHub issue describing the unintended change.
  3. Include a reproduction — Provide a minimal code sample showing the breakage.
  4. Reference the version — Specify the exact versions where behavior changed.

On this page