eeco

Versioning Policy

The contract for how eeco is versioned, released, supported, and retired.

README · Vision · Cockpit · Usage · Architecture · Public API · Extending · Contributing · Upgrading · Versioning · Changelog · Security

--- This document is the authoritative policy for how eeco is versioned, released, supported, and retired. It is the contract every release of eeco honours; a breach of any clause stated with **MUST** is a bug, not a feature. eeco is, at its first public release, **pre-stability**. It re-launches publicly at `v0.1.0`; the most important thing this document does is mark that line clearly and describe what changes — and what does not — when the project crosses into stability at v1.0.0. The keywords **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). ## 1. Scope This policy governs every artefact released under the [`ajhahnde/eeco`](https://github.com/ajhahnde/eeco) GitHub repository and the corresponding Homebrew and Scoop taps. It applies to every tag of the form `vMAJOR.MINOR.PATCH`, including the current pre-stability `v0.y.z` line, **with the explicit caveats** stated in §2.1 below. ## 2. Grammar eeco follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) over the surface defined in §3. Until v1.0.0, the special pre-1.0 clause of SemVer (§4) applies; see §2.1. A release version is `vMAJOR.MINOR.PATCH` (with the leading `v` preserved on every git tag, GitHub Release, and CHANGELOG section header) optionally followed by a pre-release identifier as defined in §7. | Component | Trigger | |---|---| | **MAJOR** | A breaking change to any item enumerated in [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md); a default-value change that can cause an existing workflow to produce a materially different result; the removal of a previously deprecated feature. *(Reserved pre-1.0: the first MAJOR is v1.0.0, the stability-freeze release — see §2.1.)* | | **MINOR** | Under pre-1.0 (§2.1): any change, including a breaking one, with the migration path called out in the CHANGELOG. Under post-1.0 (§2.2): an additive change only — a new command, flag, config key, builtin workflow, output field, or exit-code value — after which an existing workflow MUST observe identical behaviour unless it opts in to the new surface. | | **PATCH** | A bug fix, performance improvement, documentation change, dependency bump, or internal refactor that does not touch the public surface. PATCH is **always** backwards-compatible, including pre-1.0. | A change that fits more than one bucket MUST take the most disruptive applicable bucket (e.g., a bug fix that, in fixing the bug, also renames a flag is a MAJOR). ### 2.1 Pre-v1.0.0 (current line) Under SemVer 2.0.0 §4 a major version of zero is for initial development and "anything MAY change at any time". eeco adopts the literal reading of that clause for its `v0.y.z` line, which is where v0.1.0 re-launches the project: - A **MINOR** bump (`v0.y.z` → `v0.(y+1).0`) MAY include a breaking change to the surface enumerated in §3. Any such breaking change MUST be called out in the CHANGELOG entry (under `### Changed`, with the migration path) per the project's no-silent-breaking-changes rule. - A **PATCH** bump (`v0.y.z` → `v0.y.(z+1)`) MUST NOT include a breaking change to the surface enumerated in §3. PATCH is backwards-compatible even pre-1.0. - **No support guarantee** is made for any pre-v1.0.0 release. Only the latest pre-v1.0.0 tag receives further attention; once v1.0.0 ships, the entire pre-1.0 line enters the **Archived** tier of §8 permanently. ### 2.2 Post-v1.0.0 (future) From v1.0.0 onward the standard SemVer interpretation applies without the §2.1 carve-out: a breaking change MUST take a MAJOR bump. At that point the deprecation procedure (§9) and the support tiers (§8) become enforceable — in particular §9.3 ("removal MUST happen in a MAJOR") governs the post-1.0 line, which is what reconciles it with the §2.1 rule that lets a pre-1.0 MINOR remove surface. Until then, §8 and §9 describe the model that takes effect at the stability freeze, not the current `v0.x` line. ## 3. Public surface The **frozen public surface** is the union of the items enumerated in [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md). Nothing outside that enumeration is part of the surface, and the policy MUST NOT be interpreted to extend to anything else. ### 3.1 Stability classes | Class | Discoverability | SemVer protection | Removal path | |---|---|---|---| | **GA** | Documented in [`docs/USAGE.md`](docs/USAGE.md) and [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md). | Full. Breaking changes only in MAJOR with the deprecation window of §9. | Deprecate → window → remove in next MAJOR. | | **Preview** | Documented as preview in [`docs/USAGE.md`](docs/USAGE.md); a runtime warning MUST be emitted on stderr the first time per process it is invoked. | None. A preview surface MAY change shape or be removed in any release, including a PATCH. | Drop without notice; CHANGELOG MUST record the removal. | | **Internal** | Not documented in [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md). The Go package surface under `internal/` is the canonical example. | None. | Drop without notice; not mentioned in the CHANGELOG. | A surface MUST NOT be promoted from Preview to GA in a PATCH; the promotion is a feature-add and goes in a MINOR. ### 3.2 Output stability eeco is a CLI; its output channels carry different stability promises. | Channel | Protection | |---|---| | **`--json` stdout** | Frozen top-level keys are enumerated in [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md). Removing or renaming a frozen key is a MAJOR. Adding a frozen key is a MINOR. Nested object fields are **best-effort** and MAY gain keys in a MINOR; a frozen-nested-key contract is opt-in per command via [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md). | | **Human stdout** (default) | **Not** part of the public surface. Optimised for screen reading and SHALL change between MINORs whenever a presentation improvement is shipped. Scripts that parse the human form MUST switch to `--json`. | | **Exit codes** | Documented in [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md) §Workflow contract. Frozen. | | **Stderr** | **Not** part of the public surface. Reserved for diagnostics, deprecation warnings, and operator hints. A consumer MUST NOT parse stderr. | ## 4. Release cadence | Bump | Target cadence | Hard rule | |---|---|---| | **PATCH** | As-ready. A confirmed bug SHOULD reach a tagged PATCH within **7 days**. | Never blocked on a feature. | | **MINOR** | Soft target of one per **~8 weeks**. No hard train — a MINOR ships when its surface is complete and the CHANGELOG entry is final. | Each MINOR MUST be additive over the previous MINOR within the same MAJOR (§2). | | **MAJOR** | As-needed. A MAJOR MUST be announced under `## [Unreleased]` in [`CHANGELOG.md`](CHANGELOG.md) and on the GitHub Releases page **at least 90 days** before its tag. The announcement enumerates every breaking change. | An RC train (§7) MUST precede the GA tag of any MAJOR. | ## 5. Branching and tagging - The default branch is `main`. Every release tag is reachable from `main` at the moment of tagging. - A MAJOR line that is still under any support tier (§8) MUST have a `release-X` branch (e.g., `release-1`) where its PATCH fixes are prepared. The branch is force-push-protected. - A release tag MUST match the regex `^v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$`. - A release tag MUST be annotated (`git tag -a`) and signed with the release workflow's keyless cosign identity. The signed `SHA256SUMS` and the SLSA build provenance produced by the release workflow are part of the release artefact set. - A release tag MUST NOT be deleted, force-moved, or re-pointed. The yank procedure of §10 is the only sanctioned recall path. - The `v0.1.0` initial release was a **one-time pre-policy reset**: the prototype-era tags that preceded it were removed during the public re-launch, before this policy governed the line. That reset is not a §10 yank and not a breach of the no-delete rule above, which binds only tags cut under this policy (`v0.1.0` onward). ## 6. Versioning of generated artefacts Each release ships, at minimum: - `eeco___.tar.gz` (and `.zip` for Windows) — the pre-built binary archive, one per OS×arch in the published support matrix. - `SHA256SUMS` — the sha256 of every archive in the release, signed by cosign keyless OIDC (the release workflow itself). - A GitHub-built SLSA provenance attestation per archive. - A Homebrew formula (`eeco.rb`) and a Scoop manifest (`eeco.json`) published to the respective taps. ## 7. Pre-releases A pre-release tag carries the suffix `-rc.N` (release candidate; `N` is a strictly increasing non-negative integer starting at `0`). `-alpha` and `-beta` pre-release identifiers are **not** used. - An RC MUST be used for every MAJOR. It SHOULD be used for any MINOR that materially changes default behaviour. - An RC MUST be published to GitHub Releases marked as **pre-release** and MUST NOT be pushed to Homebrew or Scoop. - The RC train ends when the corresponding GA tag is cut from the same commit as the last RC, with no behaviour change between the two. - A user installing an RC accepts that the surface MAY differ from the eventual GA by the contents of any further `-rc.N+1` issued for the same target version. An RC is documented in the CHANGELOG under the GA section that supersedes it; no separate `## [vX.Y.Z-rc.N]` section is written. ## 8. Support windows eeco's support model is **Node.js-LTS-inspired** with a single operator and a 12-month maintenance trailing window after each MAJOR. | Tier | Receives | Applies to | Ends | |---|---|---|---| | **Active** | Every bug fix, every applicable feature, every security fix. | The **current MAJOR**. | When the next MAJOR's GA ships. | | **Maintenance** | **Security fixes** and **critical bug fixes** (data loss, crash on cold start, write-scope violation, signing/verification regression). No feature backports. No cosmetic changes. | The **previous MAJOR**. | **12 months after the next MAJOR's GA tag.** | | **Archived** | Nothing. | Every MAJOR older than Maintenance. | Permanent. | ### 8.1 Current support table | MAJOR line | Tier | First tag | Tier ends | |---|---|---|---| | **v0.x** (pre-stability) | None — §2.1 (no support guarantee; only the latest `v0.x` tag is current) | `v0.1.0` | When v1.0.0 GA ships — the pre-1.0 line then enters **Archived**. | The current support table MUST be updated in the same PR that tags a new MAJOR. The new line enters **Active**, the line that was previously Active enters **Maintenance** with the EOL date computed as ` + 12 months`, and the line that was previously Maintenance enters **Archived**. ### 8.2 What "Active" delivers While a MAJOR is in **Active** support, eeco MUST publish: - Every confirmed bug fix in a PATCH within the §4 cadence. - Every applicable feature in a MINOR. - Every applicable security fix in a PATCH (or, if the fix is inherently breaking, in the next MAJOR with the §13 embargo timing). ### 8.3 What "Maintenance" delivers While a MAJOR is in **Maintenance** support, eeco MUST publish: - Every security fix that applies to the maintenance-line surface. - Every critical bug fix as defined in the table above. Maintenance PATCHes MUST NOT introduce a new flag, command, config key, output field, or exit code. They MUST NOT change a default value. ### 8.4 Skew between Active and Maintenance A workspace created by an **Active**-tier release MUST read cleanly under any **Maintenance**-tier release within the same major or the immediately preceding one. The converse — workspaces from a Maintenance line reading on Active — is **always** supported and is the standard upgrade path. ## 9. Deprecation policy A change that will eventually break a frozen-surface item MUST go through deprecation. The procedure follows the Kubernetes deprecation model adapted for a single-operator project. ### 9.1 Announce - A `### Deprecated` section is added to the next release's CHANGELOG entry, naming each deprecated item, the replacement (if any), and the earliest version in which the item MAY be removed. - The deprecated item, when invoked, MUST emit a one-line warning on stderr beginning with `eeco: DEPRECATED: ` and naming the replacement. - [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md) MUST mark the item with a `*(deprecated since vX.Y.0; removed in vM.0.0 or later)*` annotation. ### 9.2 Wait The minimum window between the deprecation MINOR and the removal release MUST be the longer of: - **2 MINOR releases**, and - **6 months** of wall-clock time. The window does **not** restart when a deprecation is re-announced; the clock runs from the first announcement. ### 9.3 Remove - Removal MUST happen in a MAJOR. A PATCH or MINOR MUST NOT remove a deprecated frozen-surface item. - The release that removes the item MUST list it in the `### Removed` CHANGELOG section and in the §1 of [`docs/UPGRADING.md`](docs/UPGRADING.md) release entry. ### 9.4 Surfacing active deprecations Every release MUST surface its still-active deprecations through: - The `eeco doctor` output, listing each deprecated item the workspace is currently using. - The output of `eeco --help` for any command or flag that is deprecated. ## 10. Yank and recall A release MAY be yanked only for one of: - A defect that destroys or corrupts user state. - A signing or verification regression that breaks the release-artefact trust chain. - A security defect for which a fix cannot be issued within the §13 timeline. The yank procedure: 1. The GitHub Release for the yanked tag is edited: title is prefixed `[YANKED]`, body opens with one paragraph naming the yank reason and the recommended replacement version. The release is marked pre-release so it is no longer the latest. 2. The Homebrew tap and the Scoop tap are reverted to point at the previous stable release within 24 hours. 3. The CHANGELOG entry for the yanked release is amended (in a follow-up commit on `main`) to prepend a `**YANKED on YYYY-MM-DD**` notice. 4. The fix is shipped as a follow-up PATCH within 48 hours of the yank. 5. The yanked tag is **never** deleted or force-moved. It remains for audit and for users who pinned to it. ## 11. Security release policy Vulnerability reporting and the safety guarantees in scope are documented in [`SECURITY.md`](SECURITY.md). The version-policy implications of a security release: - A security PATCH MUST be issued to every MAJOR line currently in **Active** or **Maintenance** tier (§8) that carries the defect. - The default embargo window between report and tagged fix is **90 days**, in line with Project Zero industry practice. A shorter window MAY be negotiated on the advisory thread when the fix is ready earlier. - The CHANGELOG entry for a security PATCH MUST link the advisory (typically a GitHub Security Advisory; a CVE identifier when one is assigned). - If the fix is inherently breaking and cannot be made backwards-compatible, it MUST be shipped in the next MAJOR with an exception note in the embargo agreement; the policy MUST NOT silently break a maintenance-line workspace to deliver a security fix. ## 12. Roadmap signalling A breaking change MUST NOT be a surprise. - Every breaking change planned for the next MAJOR MUST be listed under `## [Unreleased]` in [`CHANGELOG.md`](CHANGELOG.md) **before** the first RC of that MAJOR. - The list is updated whenever a candidate breaking change is added or removed; the diff itself is the public signal. - The §4 ≥90-day announcement window starts from the date the `[Unreleased]` block first contains the final list, not from when the RC ships. ## 13. Governance eeco is currently maintained by a single operator. A release MUST be cut by: 1. A commit on `main` (or on a `release-X` branch for a Maintenance PATCH). 2. Bumping the version anchors `version-sync` watches (see [`docs/USAGE.md`](docs/USAGE.md) §5). 3. Adding the release section to [`CHANGELOG.md`](CHANGELOG.md) and the per-release entry to [`docs/UPGRADING.md`](docs/UPGRADING.md). 4. Tagging the commit `vX.Y.Z[-rc.N]` and pushing the tag, which triggers the release workflow. The release workflow MUST sign `SHA256SUMS` with cosign keyless OIDC and produce the SLSA build provenance for every archive. A release that fails the post-release `eeco update` self-verification on any supported platform MUST be yanked (§10). This policy MAY be amended; an amendment MUST itself follow the versioning of this repository — a substantive change to the contract is announced under `## [Unreleased]` and lands together with the next release. A clarifying edit (typo, link rot, formatting) MAY land at any time. --- [← Prev: Upgrading](docs/UPGRADING.md) · [Next: Changelog →](CHANGELOG.md)