# Versioning & Releases

heliaCORE uses [release-please](https://github.com/googleapis/release-please)
to manage versions. Every merge to `main` triggers `.github/workflows/release.yml`:

1. **release-please** scans conventional-commit subjects since the last
   tag, decides whether to open a "Release PR" (bumping the version),
   or — when that PR is itself merged — to create a tag and a GitHub
   Release.
2. **publish-staticlibs** builds static libraries and SDK tarballs for each
   Cortex-M target across GCC, ATfE, and armclang, then publishes SDK tarballs
   plus per-toolchain staticlib bundles with SHA-256 checksums.
3. **publish-ci-image** builds and pushes
   `ghcr.io/ambiqai/ns-cmsis-nn-ci:vX.Y.Z` so consumers can pin a
   reproducible build environment.
4. **publish-pack** generates `Ambiq.NS-CMSIS-NN.<version>.pack`,
   schema-validates it against `PACK.xsd`, and uploads it.
5. **release-unit-tests** and **release-helia-core-tester** run
   against the freshly-built artifacts to verify the release actually
   works before customers see it.

## Tag scheme

```
v<MAJOR>.<MINOR>.<PATCH>
```

- `MAJOR` bumps on `feat!:` / `fix!:` / `BREAKING CHANGE:` footers.
- `MINOR` bumps on any `feat:`.
- `PATCH` bumps on `fix:` and similar.

Other conventional types (`docs:`, `chore:`, `refactor:`, `test:`,
`ci:`, `build:`, `perf:`) do **not** trigger a release on their own,
but their bodies still appear in `CHANGELOG.md`.

## How the version gets stamped

`release-please` bumps `.release-please-manifest.json` and `CHANGELOG.md`,
and every other file that hardcodes the release version is listed in
`release-please-config.json` under `extra-files`. Those all use the `generic`
updater, which rewrites a line only if that same line carries an
`x-release-please-version` (or `-major`/`-minor`/`-patch`) comment. There is
no error when a listed file has no such comment: the updater changes nothing
and the release proceeds, so the file appears covered and drifts a release
behind. Two checks close that gap, from opposite directions:

- `scripts/check_pdsc.py` asserts every `extra-files` path exists, carries a
  working annotation, and agrees with `Include/arm_nn_types.h`. It sees
  everything on the list.
- `scripts/check_stale_version_refs.py` reads the previous release out of
  `CHANGELOG.md` and asserts no tracked file still mentions it, outside an
  allowlist of files whose job is to discuss release history (this page and
  the recovery commands below are on it). It sees the files that are *not*
  on the list.

Adding a new file that hardcodes the version means adding it to
`extra-files` **and** putting the annotation on the value's own line. Where
the value sits somewhere a comment cannot go — a JSON line in a fenced
example, say — state the version in prose next to the block, with the
annotation on that prose line, and leave the block version-free.

## What's in a release

Every GitHub Release contains:

| Asset                                                | Purpose                       |
|------------------------------------------------------|-------------------------------|
| `Ambiq.NS-CMSIS-NN.<version>.pack`                   | CMSIS-Pack                    |
| `ns-cmsis-nn-<cpu>-<toolchain>-<version>.tar.gz`     | SDK tarball (CMake users)     |
| `ns-cmsis-nn-staticlibs-<toolchain>-<version>.zip`   | Bare static archives by CPU   |
| `*.sha256`                                           | SHA-256 of each artifact      |

`<cpu>` is one of `cortex-m0`, `cortex-m4`, `cortex-m55`.
`<toolchain>` is one of `gcc`, `atfe`, `armclang`.

:::note[Cortex-M0/M0+ naming]
The release asset name uses `cortex-m0` for the baseline ARMv6-M package. Use
that artifact for Cortex-M0/M0+ class Apollo targets.
:::

## Required vs optional assets

Not every asset is equally load-bearing. If a release is missing something,
use this table to know whether that's a release-blocking bug or an expected
gap (see [AmbiqAI/ns-cmsis-nn#228](https://github.com/AmbiqAI/ns-cmsis-nn/issues/228)):

| Asset                                                                | Status       | Why                                                                 |
|-----------------------------------------------------------------------|--------------|----------------------------------------------------------------------|
| `vX.Y.Z` tag + GitHub Release (source)                                | **Required** | Created by release-please; the release exists once this lands.       |
| `Ambiq.NS-CMSIS-NN.<version>.pack`                                     | **Required** | Only depends on the `gcc` static libs.                                |
| `ns-cmsis-nn-<cpu>-gcc-<version>.tar.gz`                               | **Required** | Free toolchain, no license dependency.                                 |
| `ns-cmsis-nn-<cpu>-atfe-<version>.tar.gz`                              | **Required** | Free toolchain (LLVM-Embedded-Toolchain-for-Arm), no license dependency. |
| `ns-cmsis-nn-staticlibs-gcc-<version>.zip`                             | **Required** | Same as above.                                                          |
| `ns-cmsis-nn-staticlibs-atfe-<version>.zip`                            | **Required** | Same as above.                                                          |
| `ns-cmsis-nn-<cpu>-armclang-<version>.tar.gz`                          | **Optional** | Promoted to required only when the repository variable `ARMCLANG_REQUIRED` is `true`. Not keyed on whether a license is configured. |
| `ns-cmsis-nn-staticlibs-armclang-<version>.zip`                        | **Optional** | Same as above.                                                          |
| `ghcr.io/ambiqai/ns-cmsis-nn-ci:vX.Y.Z` image                          | **Required** | Needed by `release-unit-tests` / `release-helia-core-tester`.           |
| GitHub Pages docs update                                               | **Required** | Rides along with `publish-pack`.                                       |

Armclang is licensed through Arm's **User-Based Licensing**: the
`ARM_UBL_LICENSE_IDENTIFIER` secret is an activation code, redeemed once per
release run by `armlm activate --code` in the `publish-staticlibs-armclang`
job before any compilation happens. That job builds all three Cortex-M
targets on a single runner precisely so the license is activated once and the
Arm Compiler archive is downloaded once.

The eight `armclang` assets are **optional** unless the repository variable
`ARMCLANG_REQUIRED` is set to `true`. Set it and `release-verify` promotes
them, failing the release if they are missing and taking the required bar
from 17 assets to 25; leave it unset and the bar stays at 17.

The gate is deliberately an operator variable rather than an inference from
whether `ARM_UBL_LICENSE_IDENTIFIER` is configured. That secret was configured
throughout the period in which armclang published nothing at all, so keying
off it would promote assets this pipeline has never once produced — and a
required asset the pipeline cannot produce makes every release red with no
way out, because the recovery such a failure prescribes re-runs the same job.
Turn `ARMCLANG_REQUIRED` on only after a release run has actually published
the eight, and turn it off to ride out an Arm licensing outage.

Independently of that switch: when no license is configured,
`resolve-release-capabilities` reports the capability as unavailable, every
step of `publish-staticlibs-armclang` self-skips (the job succeeds as a
no-op and self-reports via `::notice::`/`::warning::` annotations), and the
`armclang` bundle is omitted from `publish-staticlib-bundles`. Either way the required gcc/atfe assets are
unaffected: they come from a different job entirely, and
`publish-staticlibs-armclang` is `continue-on-error: true`, so armclang can
never hold up the tag, pack, gcc/atfe libs, CI image, or tests.

Before v7.30.0 this gate read `ARMLMD_LICENSE_FILE`, the legacy FlexLM
license-file variable, which has never been configured on this repository —
which is why no release from v7.24.1 onward published any armclang asset
(see [AmbiqAI/ns-cmsis-nn#275](https://github.com/AmbiqAI/ns-cmsis-nn/issues/275)).

## Nightly published-release audit

`release-verify` (in `release.yml`) enforces the table above at release
time, but only for a run that actually reaches it — v7.26.0 through
v7.29.1 all shipped with zero assets before it existed, and a cancelled
run or a later `gh release upload --clobber` failure (which deletes an
asset before replacing it) leaves nothing red behind
([AmbiqAI/ns-cmsis-nn#274](https://github.com/AmbiqAI/ns-cmsis-nn/issues/274)).
The `release-assets-audit` job in `nightly.yml` therefore re-checks the
**published release objects themselves** every night, via
`scripts/ci/audit_release_assets.sh`:

- Every non-draft, non-prerelease release from `v7.26.0` (the first cut
  under the current asset contract) onward must carry the 17 required
  assets for its version. armclang assets are never audited by the nightly sweep at all -- even for releases cut with `ARMCLANG_REQUIRED=true` -- because the variable is point-in-time and the audit keeps no per-tag record of which contract was in force; post-publication loss of armclang assets is covered only by `release-verify` in the release run itself.
- Failures land in one rolling issue (`Published release(s) missing
  required assets`, label `release-audit`), which comments on subsequent
  red nights and closes on the first clean audit — separate from the
  `nightly` code-health issue, because the fix is a recovery dispatch, not
  a code change.
- To record a deliberate decision **not** to repair a historical release,
  add its tag to the repository variable `RELEASE_AUDIT_WAIVED_TAGS`
  (space- or comma-separated). Waived gaps are reported but do not fail
  the audit.

## Recovering assets for an existing tag

`release.yml` also accepts a manual, idempotent recovery run via
`workflow_dispatch` with a `recover_tag` input (e.g. `v7.29.2`). This
**never creates, moves, or re-publishes a tag** — it only re-runs the
staticlib/bundle/pack jobs against an already-published release and re-uploads
(`gh release upload --clobber`) any assets that are missing or need updating:

```bash
gh workflow run release.yml --ref main -f recover_tag=v7.29.2
```

The run fails fast if `recover_tag` isn't already a published GitHub Release,
so it can't be used to sneak out a brand-new tag. See
[Contributing → Release recovery](https://ambiqai.github.io/ns-cmsis-nn/contributing/#release-recovery) for
the full recovery runbook.

Every recovery run — and every normal release run — resolves `recover_tag`
(or the freshly-cut tag) to its exact target commit **once**, via the GitHub
API. A recovery dispatch's own triggering ref (whatever branch was selected
when running the workflow, typically `main`) is never used to build release
assets; only the commit the tag itself points at is.

Historical recovery restores customer GitHub Release assets only:
static-library archives/bundles and the CMSIS-Pack. It deliberately skips
`publish-ci-image`, `release-unit-tests`, and `release-helia-core-tester`.
The CI image is build infrastructure rather than a release asset, and its
tool acquisition is qualified independently of historical asset recovery.
Skipping it also makes recovery incapable of publishing an old versioned image
or repointing `:latest`. Normal new releases continue to require the versioned
CI image and both container test suites.

### Immutable CI tool acquisition

Normal-release CI images install their build and simulation tools from
`ci/tools/manifest.json`. Each entry records an exact version, vendor HTTPS
URL, SHA-256 digest, extraction rule, executable probe, environment export,
and license classification. `scripts/install_ci_tools.sh` validates the
manifest, verifies every archive before extraction, and emits
`/opt/ns-cmsis-nn/tool-env.json` with the resolved paths and provenance.

This repository-owned manifest replaces the retired vcpkg-artifacts/vcpkg-ce
registry. The image build has no package-solver or floating-registry dependency.
Armclang remains installed from Arm's vendor-hosted archive, but licensed
execution is capability-gated separately through
`ARM_UBL_LICENSE_IDENTIFIER`.
Updating any tool requires a reviewed manifest version, URL, and digest change
plus a clean image build and version smoke test.

Recovering a genuinely old tag surfaces two more subtleties, both handled
automatically via an explicit **two-tree checkout architecture**: a
*trusted/current* tree (this repo's current `main`, whatever ref/commit
triggered the run) supplies recovery tooling — helper scripts and the
hardened Dockerfile — while a *distinct, immutable/historical* tree (pinned
to the resolved historical commit) supplies the only source payload that is
ever built, packaged, or uploaded. The two trees always use different
on-disk paths (never the same checkout directory), so neither can silently
overwrite the other:

- **CI-image tooling vs. source.** The reusable Docker build workflow
  checks out the repository twice, into two *different* directories: an
  unpinned checkout at `_tooling/` (whatever ref/commit triggered the run)
  purely to obtain `scripts/ci/resolve_image_tags.sh` — a historical tag
  can predate that helper script entirely — and a second checkout pinned to
  the resolved historical commit at `_source/` for the actual Docker build
  context. `docker build` is then invoked as
  `-f _tooling/.devcontainer/Dockerfile _source`: the **Dockerfile always
  comes from the current, hardened tree** (so a historical, pre-fix
  Dockerfile bootstrap bug can never resurface), while the **build context
  is always the immutable historical source** (so the built image still
  reflects exactly what that historical tag shipped). Giving each checkout
  its own `path:` is required, not cosmetic: an earlier revision of this
  recovery path had both checkouts default to the *same* directory, so the
  historical, pinned checkout silently clobbered the current tooling
  checkout's files — including the Dockerfile — right before the build ran
  (AmbiqAI/ns-cmsis-nn#228). Normal push/direct-dispatch/schedule runs are
  unaffected by this split: with no `recover_tag`, both `_tooling` and
  `_source` resolve to the same `github.sha`, so the two trees are
  identical content living at two paths, and the build behaves exactly as
  it always has.
- **Lightweight vs. annotated tags for `gen_pack.sh`.** Release Please
  creates lightweight tags, but Open-CMSIS-Pack's `gen-pack` (run with
  `PACK_CHANGELOG_MODE=tag`) requires an *annotated* tag with a non-empty
  message. `publish-pack`'s only long-lived checkout is pinned directly to
  the resolved historical commit — that historical tree is the *only*
  source `gen_pack.sh` ever packages. Because
  `scripts/ci/ensure_local_tag_annotation.sh` is itself part of the
  *current* repository, `publish-pack` also checks out the current,
  merged repository into a second, unpinned directory (`_tooling/`) purely
  to obtain that helper, then invokes it explicitly against the historical
  checkout's working directory (`bash
  _tooling/scripts/ci/ensure_local_tag_annotation.sh "$TAG"
  "$GITHUB_REPOSITORY" "$GITHUB_WORKSPACE"`). The helper — for a
  lightweight tag only — creates a **local-only** annotated tag at the
  exact same commit (message sourced from the existing GitHub Release
  body, XML-escaped, or a deterministic fallback) so `gen_pack.sh`
  succeeds. It never runs `git push`; the remote/immutable tag is
  untouched, already-annotated tags are left completely unaltered, and
  `_tooling/` is never read by `gen-pack-action` or packaged into the
  `.pack` output — it exists solely to supply the helper script. The
  `Pack dry-run` workflow rehearses this same `tag`-mode path per PR:
  it creates a synthetic, local-only annotated tag at `HEAD` via the same
  helper, so the changelog path is exercised without cutting a release
  (AmbiqAI/ns-cmsis-nn#395).

## See also

- Maintainer release notes in [Contributing](https://ambiqai.github.io/ns-cmsis-nn/contributing/#maintainer-release-notes)
   explain how to recover when the pipeline fails.
- Toolchain identity is recorded in `manifest.json` inside
  each tarball. See [Toolchain Pinning](https://ambiqai.github.io/ns-cmsis-nn/getting-started/toolchains/).
