API Versioning

How Stella Ops versions its OpenAPI contracts and SDKs, declares deprecations, and keeps specs deterministic and reproducible. This page is for API consumers planning upgrades and for maintainers evolving the contracts; for the wider contract/SDK programme see SDK & OpenAPI Program, and for shared request conventions see the API Overview.

Last updated: 2026-05-30 (deep doc↔code reconciliation against src/Api/StellaOps.Api.OpenApi)

PCD-460 Policy examples, 11b3e6f5ec (2026-10-08). The operation prefixes, bodies and scopes below were checked against the composed Policy host. Other artifact/version details retain their dated review. Recheck Program.cs, ExceptionEndpoints.cs, ExceptionApprovalEndpoints.cs and their request DTOs before treating these source contracts as current installation behavior.

Until the first external install, cutovers remove predecessor routes in the same change that verifies their replacement; there is no legacy alias or deprecation window. The multi-version/deprecation-window material below is post-install design, not authorization to retain unsupported pre-release contracts. Forward-only migrations keep their separate immutable-history rule.

Implementation status. Parts of this page describe a target design that is not yet wired up in the repository. Where a mechanism is aspirational it is marked (PROPOSED — not implemented) so consumers do not depend on it. The composer (compose.mjs) is present, and package.json retains api:compose and api:lint. The former example/compatibility/changelog helpers and aliases are absent; the driving governance workflow is archived under .gitea/workflows-archived/. Those compatibility steps are design history, not a current enforced pipeline.

Principles

How versions are expressed

Current Policy examples (source publication, not deployment acceptance):

OperationBody/response boundaryRequired scope
GET /api/policy/exceptionsFiltered lifecycle list, ExceptionListResponse with items/count/offset/limit; status=pending_review projects approval requests into the Console shape.policy:read
GET /api/policy/exceptions/{id}One mapped exception DTO, or 404 Problem Details when absent.policy:read
POST /api/policy/exceptionsCreateExceptionRequest: type/scope/owner/reason/rationale/expiry; creates a proposed lifecycle record and returns 201.policy:author
POST /api/v1/policy/exception/requestCreateApprovalRequestDto: justification, approval/gate/TTL and optional decision-envelope data; approval rules and signing requirements apply.exceptions:request
GET /api/v1/policy/exception/requests or /request/{requestId} under that groupApproval request list/detail, with approval and signing metadata.exceptions:read

Both groups require tenant context. The host maps both explicitly. Their different DTOs, scopes and lifecycle/approval semantics prevent a mechanical prefix swap.

Deprecations

Deprecation is declared per-operation in the per-service OpenAPI files and enforced by Spectral rules (.spectral.yaml at the repo root):

Compatibility rules

Release & lifecycle

  1. Edit the per-service spec under src/Api/StellaOps.Api.OpenApi/<service>/openapi.yaml, then regenerate the aggregate: npm run api:compose (which runs node src/Api/StellaOps.Api.OpenApi/compose.mjs and overwrites src/Api/StellaOps.Api.OpenApi/stella.yaml).
  2. Run lint + compatibility checks (all via npm run, not pnpm):
    • npm run api:lint — Spectral lint of src/Api/StellaOps.Api.OpenApi/**/*.yaml (rules in root .spectral.yaml).
    • npm run api:examples — example coverage check.
    • npm run api:compat — runs node scripts/api-compat-diff.mjs. The CI invocation is positional: node scripts/api-compat-diff.mjs <old.yaml> <new.yaml> --output text [--fail-on-breaking]. CI compares against the previous commit’s stella.yaml and against the baseline at src/Api/StellaOps.Api.OpenApi/baselines/stella-baseline.yaml. (There is no --baseline <file> flag; the baseline is passed as the <old.yaml> positional argument.)
    • npm run api:changelog — regenerates src/Api/StellaOps.Api.OpenApi/CHANGELOG.md (sections: Additive Operations, Breaking Operations).

    Status: the scripts/api-*.mjs helpers are not present in the repository (the whole scripts/ directory is absent); only compose.mjs is committed. Because the api:examples/api:compat/api:compat:test/api:changelog npm aliases could therefore only fail, they were removed from package.json in SPRINT_20260712_005 (DEAD-3); api:lint and api:compose remain and work. Restoring this step means writing the helper scripts and re-adding the npm aliases.

  3. Changelog lives in-tree at src/Api/StellaOps.Api.OpenApi/CHANGELOG.md. (PROPOSED — not implemented): a per-release out/api/changelog/<yyyyMMdd>-<version>.md with embedded checksum/signature is design intent and does not exist; there is no out/api/ directory.
  4. Post-install design: announce deprecations through release notes and reviewed metadata; runtime headers require an implemented owner contract. This step does not apply a pre-release compatibility window.
  5. SDK freeze: tag the commit api/v{version} (e.g. api/v0.1.0-alpha), compute and commit the SHA256 of stella.yaml (sha256sum stella.yaml > stella.yaml.sha256), and have SDK generators validate it via STELLA_OAS_EXPECTED_SHA256. See the API governance baseline.
  6. Post-install design: execute the recorded sunset after its release gate. Pre-release cutovers use the same-change withdrawal rule stated above.

Client guidance

Testing