# Versioning & Compatibility Strategy

This document defines how Shellops versions the language surface, the engine, and release artifacts, and how compatibility is preserved across releases. It applies to pre-v1 planning and should be treated as policy for implementation and communication.

## Goals

- Give projects a single, explicit statement of which language/engine era they target.
- Make breaking changes predictable and diagnosable.
- Keep release mechanics simple: main plus tags, no long-lived compatibility branches.

## Version declaration

### Project-level pin

The authoritative version pin is **project-level**, not per-file. It is analogous to Rust’s `edition = "2021"` in `Cargo.toml`: one declaration covers the whole project.

- **Location:** a project-level config file (e.g. `shellops.toml` or a similarly named manifest).
- **Semantics:** the pin names the language/engine compatibility era the project expects. Files in the same project are not expected to target different engine versions.

### Optional for v1

For an initial stable line, the pin may be **optional**:

- If absent, the engine assumes the **current** default version for that engine build.
- `ops lint` **warns** when the pin is missing so teams can adopt it deliberately.

### When the pin is present

A declared pin enables:

- **Precise diagnostics**, e.g. “this syntax requires engine ≥ 2.0” instead of vague parse failures.
- **Consistent behavior** across machines when multiple engine versions exist.

## Backwards compatibility policy

### Breaking changes

The following are **breaking** and require a major version bump (or an explicit edition bump if the project adopts an edition model):

- Removing syntax that was valid in a prior release.
- Changing the **semantics** of existing syntax (same tokens, different meaning).
- Removing types, fields, or IR shapes that were part of the public contract for that era.

### Non-breaking changes

The following are **not** breaking:

- Adding new syntax, keywords, or constructs (old code keeps parsing and meaning).
- Adding new types or extending the type system in a backward-compatible way.
- Adding new **warnings** (including stricter lint that does not change successful compile outcomes for previously clean code).
- Adding new **op modifier flags** or optional pipeline features that old code does not use.

The version pin tells the engine **which era** the codebase was written for, so the compiler can accept older programs under old rules while still evolving the language.

## Deprecation lifecycle

Shellops uses a **two-stage** deprecation model for language and built-in surface changes.

### Stage 1: Deprecated

- Emits a **compiler warning** with a **migration hint** (what to write instead).
- Behavior remains **fully functional**; nothing is removed yet.
- The message must always tell the user the **recommended replacement**.

Example (illustrative): renaming `entrypoints` → `scripts` is stage 1 while both names are accepted and the old name warns.

### Stage 2: Removed (next major)

- The old form is **removed**.
- A **clear error** states what replaced it and points to the same migration path.

### User-facing deprecation in ops files

The `deprecated` pipeline marker and doc-comment `@deprecated` annotation already exist for **user-authored** deprecation of ops and steps. Language/engine deprecation follows the same spirit: warn with a path forward, then remove on a major boundary.

## Release channels

Releases are organized into three channels. There are **no long-lived release branches**—only **main** and **tags**.

### latest

- Tracks **main** branch HEAD.
- Always building; may break without notice.
- Intended for contributors and early adopters who accept churn.

### preview

- **Semver pre-release tags** (e.g. `v1.1.0-preview.1`).
- Cut from main when a feature set is ready for wider testing.
- Preview tags are **cheap** to create; they do not imply a separate branch line.

### official release

- **Semver release tags** (e.g. `v1.0.0`).
- Cut from a **preview** that has been validated (tests, dogfooding, docs as appropriate).

### Phasing

During early development, only **main** and occasional release tags may matter; **preview** becomes meaningful once there is an audience that needs staged rollouts. **CI** can publish artifacts from tag patterns (e.g. `v*`, `v*-preview.*`) without maintaining branch-based release trains.

## IR schema versioning

Today the IR carries a safety check and two-component schema version: `$schema: "shellops-ir"` and `ir_version: "1.0"`.

- The **compiler and resolver ship as one binary** and stay **in lockstep**; internal IR evolution does not require a separate consumer migration story.
- **Major** IR changes are breaking shape or semantic changes. **Minor** IR changes are additive fields or new component/decorator kinds.
- **Full migration machinery** becomes important if **third-party tools** consume the IR as a stable interchange format. That is **not planned** for the near term.

**Decision:** use a small `major.minor` IR version and keep the lockstep coupling model until external IR consumers force a richer versioning story.

The canonical human-readable contract is [ir_schema](../ir_schema.md). Detailed IR-specific versioning rules live in [versioning-and-compatibility](../ir/versioning-and-compatibility.md).
