<!-- OpenSmartRoute: Versions and channels. https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/RELEASES -->
# Versions, channels and releases

One version number names everything OpenSmartRoute ships - the Python package, the `osr` command line, the
npm client, the platform API and web images, the Helm chart, the Compose bundle, the Azure Marketplace
package, the browser extension and the desktop app - and every deployment tells you which build it runs. This
page says what the numbers mean, how to read them on each surface, and how a release is made.

## One version

The release version is `X.Y.Z` ([Semantic Versioning](https://semver.org)). Every artifact of a release - the
packages, images, chart, extension and installers - is stamped from one source, and a consistency check fails
any build where a version drifts. There
is no separate extension version, desktop version, SDK version or platform version: `pip install
opensmartroute==1.3.0`, `npm install @opensmartroute/sdk@1.3.0`, `osr-platform-api:1.3.0`, chart `1.3.0`, the
extension `1.3.0` and `OpenSmartRoute-1.3.0-win-x64.exe` were all built from the tag `v1.3.0`.

- A **minor** release (`1.3.0` -> `1.4.0`) adds; the public Python API only grows.
- A **patch** release (`1.3.0` -> `1.3.1`) fixes; nothing new is announced.
- A **major** release may remove what was deprecated two minors earlier.
- `SECURITY.md` lists the supported versions: the current minor gets every fix; an earlier minor of the same
  major gets security fixes for **90 days after the minor that superseded it shipped** (the table names the
  date); earlier majors get nothing. The table is computed from the changelog's release dates, so the
  window never depends on how quickly minors follow each other.

### Cadence

Minor releases are cut when the `[Unreleased]` section carries something users need, at most **once a week**
unless a security fix or a broken release forces a patch; patch releases ship whenever a fix is ready. Every
release soaks as a developer preview on the hosted platform first (below). A minor that ships faster than
that is not wrong, but the 90-day window above is what protects the people who installed the one before it.

## Two channels

Every build is either a **stable release** or a **developer preview**, decided
from the git tags alone - no one types a build number:

| Channel | Built from | Version | Where it goes |
|---|---|---|---|
| **stable** | the tag `vX.Y.Z` | `X.Y.Z` | PyPI, npm, the image tags `X.Y.Z` / `X.Y` / `latest`, the release downloads, `extension/latest.json`, `desktop/latest.json` + `latest*.yml`, the browser stores |
| **developer preview** | any other commit on `main` | `<next>-preview.<N>` | the hosted platform (opensmartroute.ai), `extension/preview.json`, `desktop/preview.json` + `preview*.yml` |

`<next>` is the release the preview leads up to: the pyproject version while its tag does not exist yet
(a merged release pull request, about to be tagged), otherwise the next patch after the last release.
`N` counts the commits since the last release tag, so previews sort and read in order:
`1.3.0` -> `1.3.1-preview.1` ... `1.3.1-preview.89` -> `1.4.0-preview.3` (release PR merged) -> `1.4.0`.

The hosted platform deploys every merge to `main` by default, so what you use at opensmartroute.ai is the
developer preview of the next release - the `[Unreleased]` section of the [changelog](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/changelog.md) is what
it carries beyond the last stable release. Every release also redeploys the platform from its tag, so the
deployment moves to the stamped stable build (`channel: stable`, footer without the preview badge) once the
release is cut. An operator can pin production to releases only by setting `OSR_DEPLOY_PREVIEWS=false` in the
deployment's configuration: `main` keeps building and testing its images, the preview is reported but not
rolled out, and production changes only when a release is tagged. Self-hosted installations pull stable images
and stay on releases either way.

Browser manifests take up to four integers, so a preview extension carries `X.Y.Z.N` (`1.3.1.89`) in
`manifest.json`; Chrome and Edge show the readable label (`1.3.1-preview.89`) as `version_name`. A release
carries `X.Y.Z`. Previews are never submitted to the browser stores. The desktop installers carry the label in
their file names (`OpenSmartRoute-1.3.1-preview.89-win-x64.exe`) and a preview build's updater follows the
`preview*.yml` feed, so an installed release only ever updates to the next release.

## Reading the version

| Surface | What it shows |
|---|---|
| `osr --version`, `opensmartroute.__version__` | the package release, `1.3.0` |
| `GET /api/v1/info` -> `build`, `GET /healthz` -> `build` | `{"version": "1.3.1-preview.89", "release": "1.3.0", "channel": "preview", "commit": "4ced2b2..."}` on a published image; `channel` is `null` for a checkout or a hand-built image, which report the release alone |
| the website footer | `v<build version>`; a **developer preview** badge when the deployment runs `main` ahead of the next release |
| `GET /api/v1/releases` | every release's notes, the running `build`, and `unreleased` - what has landed since the last release (`GET /api/v1/releases/{version}` with `unreleased` as the version returns that section alone) |
| `GET /extension/release.json` | the extension build offered for download with its `channel`; `?channel=preview` for the preview, `?channel=stable` for the release only |
| `GET /desktop/release.json` | the same for the desktop app's installers |
| the dashboard's *What's new* card and the release mail | the stable release notes, sent once per release version - previews never announce |
| image labels | `org.opencontainers.image.version` and `.revision` on `osr-platform-api` and `osr-platform-web` (`docker inspect`) |

The build stamp reaches the images as build arguments (`OSR_BUILD_VERSION`, `OSR_BUILD_CHANNEL`,
`OSR_BUILD_COMMIT`) and the API as the settings
`OSR_PLATFORM_BUILD_VERSION`, `OSR_PLATFORM_BUILD_CHANNEL` and `OSR_PLATFORM_BUILD_COMMIT`. Anyone building the
image by hand can pass the same arguments (`docker build --build-arg OSR_BUILD_VERSION=... `); without them the
image is honest about not knowing its channel.

## Downloads

[opensmartroute.ai/downloads](https://opensmartroute.ai/downloads) always resolves to the current **stable**
release: `/download/<artifact>` redirects to the versioned file and `/download/latest.json` lists them with
SHA-256 checksums. Versioned files never change once published (cached for a year); the `latest.json`
pointers move with each release.

The browser extension follows the same rule with two pointers: `/extension/download/<browser>` offers the
stable release and `/extension/download/<browser>?channel=preview` the developer preview. While no stable
release of the extension has been published yet, the preview stands in and the page says so
([EXTENSION.md](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/EXTENSION.md), *Where the packages come from*). The desktop app is served the same way from
`/desktop` (`/desktop/release.json?channel=preview`, installers and update feeds under `/desktop/download/`).

## Unreleased changes

Every user-visible change lands as a bullet under `## [Unreleased]` in the changelog the moment it merges -
that section is the developer preview's release notes and the draft of the next release's. The website
renders it on the changelog page, `GET /api/v1/releases` returns it as `unreleased`, and it becomes
the dated `## [X.Y.Z]` section when the release is cut. Nothing is announced to users until then:
the release mail and the *What's new* card key off the stable release version.

## How a release is made

Releases are cut in two steps from the main branch; nobody edits a version or a tag by hand.

1. **Prepare**: the version is bumped everywhere at once, `[Unreleased]`
   rolls into `[X.Y.Z] - date`, the compare links and the supported-versions table are refreshed, and the
   change is reviewed like any other. From the moment it merges, main-branch
   builds are `X.Y.Z-preview.N` - release candidates of X.Y.Z.
2. **Release**: the consistency check and the full test and routing gates run, the packages are built, the
   annotated tag `vX.Y.Z` is created (its message is the changelog section), PyPI and npm are published, the
   images are stamped `stable`, the Compose bundle, Helm chart and Marketplace zip land on the downloads page,
   and the extension and desktop builds follow on the tag (stable packages behind `latest.json`, store
   submissions). Everything is then fetched back the way a user would: the PyPI files must match the built
   checksums, the image must report the version and answer `/readyz`, and the extension and desktop pointers the
   website serves and the platform's `/api/v1/info` (build, platform package, SDK) must all answer `X.Y.Z` - a
   channel left behind fails the release by name instead of staying on the previous version unnoticed. Firefox
   signing by Mozilla cannot hold a release back: when its review outlasts the wait, the other browsers ship and
   the signed `.xpi` completes the manifest as soon as Mozilla returns it.
3. **Announce.** The next deploy of the hosted platform runs the new version; its release-news job mails the
   digest of the changelog section to opted-in users once and the dashboard shows the *What's new* card
   ([PLATFORM.md](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/PLATFORM.md), *Campaigns*).

A red verification stage means the release is not usable as published: fix forward with a patch release,
never by moving a tag.
