Skip to content

Release and support policy: what a version promises, which lines get fixes, and how images and npm map onto it #1870

Open

Description

@joshunrau

@gdevenyi @thomasbeaudry: this follows up on #1822 and my comment there about a hybrid approach. I worked through a full release and support policy. Before #1822 gets reworked to match it, I'd like your thoughts, especially on anything you would push back on and on the two open questions at the end.

Why

  • Every merge to main still re-pushes latest and 2.5.2. The most recent was today, in run 42063797447. main is 564 commits past v2.5.2 (48 feat, 101 fix). A deployment on either tag runs main from whenever it last pulled, and the actual v2.5.2 code is no longer published under any image tag.
  • SECURITY.md promises security updates for "the most recent version" only.
  • Major numbers have marked big features, not compatibility breaks. The 2.0.0 changelog calls it "a non-breaking upgrade."
  • The deployment guide has deployers git clone main, so the compose file they run is unreleased code.

Proposal

1. What a version number promises

A change that breaks any of these is a new major:

  1. Stored data. An upgrade boots against the existing MongoDB and object storage and reads every existing record, with no manual step.
  2. Deployment config. This covers .env variables, compose services, volumes and ports. A new variable needs a default that keeps the old behaviour. Renaming or removing a variable, or making it required, is breaking. It follows that the newest image of a major works with the compose file from any earlier release of that major.
  3. Stored instruments. Every bundle already in a database keeps rendering, and its records keep exporting.
  4. The documented HTTP API (spec.json). Additions are fine; removing or changing anything is breaking.
  5. Export formats. Column and field names.
  6. Access. An upgrade never widens or narrows what an existing user can see or do, unless an admin acts.
  7. The public API and CLI of each published npm package.

Outside the contract: UI layout and workflows, undocumented endpoints, internal workspaces, and the gateway↔api protocol. The gateway and api must run the same version.

2. Which lines are supported

  • The current major is supported in its newest minor only. To get a fix, upgrade to the newest minor, which §1 makes safe.
  • Every superseded major keeps its last minor supported for 36 months after the next major ships.
  • The policy applies going forward only. 1.x is not covered.

3. What each line gets

Fix Current major (newest minor) Newest superseded major Older superseded majors
Security ✓ ✓ ✓
Data integrity ✓ ✓ ✓
Critical bug ✓ ✓ —
Everything else ✓ — —
  • Security means either of two things.

    • A vulnerability that lets someone, in a documented deployment configuration (the compose stack and its documented options, opt-in ones included), do any of these:
      1. read data they aren't authorized to see;
      2. create, change or delete data they aren't authorized to;
      3. act as another user or gain permissions they weren't given;
      4. run code on the server or in another user's browser.
    • Or a Critical-rated CVE in anything a deployment runs: the base image, OS packages, the runtime npm dependencies of api, gateway and web, and the third-party images the compose stack pins (MongoDB, RustFS, Caddy).

    A CVSS score alone qualifies nothing, and availability-only issues (DoS, resource exhaustion, crashes) never get a backport. They're fixed on the current line in its next ordinary release.

  • Data integrity means a bug that silently loses, corrupts or misattributes recorded data, or exports it wrong.

  • Critical bug means the stack fails to start, or a core workflow is unusable with no workaround. The core workflows are administering an instrument, collecting a remote assignment, managing subjects and users, and exporting data.

  • Maintainers judge reachability and whether a workaround exists. The backport PR states which category it falls under.

  • If a fix can't be backported without breaking the line (for example, the fix exists only in the dependency's next major), we publish an advisory saying the line is affected and recommending an upgrade, rather than skipping it quietly.

4. Image tags

Tag Moves when For
<version> never (immutable) production deployments that upgrade by hand
<major> every release on that major, including backports the documented default: fixes and features, never a breaking change
latest every release of the newest major demos and quick starts; crosses majors, so not for production
edge every push to main the demo instance and dev environments
sha-<7> never pinning an exact main build
  • <major>.<minor> is dropped. Under §2 it would stop getting fixes the day the next minor ships, and nothing would tell the people tracking it.
  • .env.template defaults RELEASE_CHANNEL to the current major. increment-version.ts rewrites it on a major bump.
  • Staging tracks <major>, so it runs what production will run.

5. Which compose file a deployment runs

  • Deploy from a release tag, never from main. Install with git clone --branch v<version>, and upgrade with git fetch --tags && git checkout v<newer>. mongo/, rustfs/, sqlite/ and .env are git-ignored, so switching refs never touches data or config.
  • A per-major branch that deployers can git pull would be safer, but CI would need to push to a protected branch with a token we don't hold today. That's a later improvement if people keep pulling main.

6. npm packages

  • The version numbers stay in lockstep with the platform, but npm publishes only from main. That makes every npm publish a latest publish.
  • A release from a branch ships images and a GitHub release, and logs that it is skipping npm. Authors targeting an older major install @^<major>.
  • The release environment stays main only, so the npm trusted publisher never has to trust a branch.

7. Breaking changes

  • Every breaking change is marked !, with a BREAKING CHANGE: footer written for deployers that says what they have to do. The footer becomes the changelog entry, so a major's upgrade notes come from the commits themselves.
  • Merging a ! commit to main is the decision that the next release from main is a major. There is no cadence cap, and main doesn't have to stay releasable as a minor.
  • increment-version.ts refuses a bump lower than the recommended one once a breaking commit is in range, and refuses any ! commit on a release branch.
  • Automatic detection is follow-up work, with an issue for each check:
    • an OpenAPI diff against the last tag;
    • an .env.template and compose diff against the last tag;
    • export-format snapshots;
    • an upgrade test that boots the previous release's data with the new images.

Out of scope: release cadence and gating. Releases stay at my discretion, as now.

What this means right now

  • No release/2.5 branch and no 2.5.3. Under §2, 2.5 isn't a separate line, and a branch cut from v2.5.2 would rebuild code almost no deployment runs.
  • Once ci: release from release branches and keep latest on tagged releases #1822 has landed, the next release is cut from main.
    • Before bumping, audit the commits since v2.5.2 against §1. A first pass over .env.template, compose and the Prisma schema found nothing breaking: BIND_ADDRESS has a default, PROJECT_ROOT left the template, and the schema only gained optional fields and new models. The HTTP API, export formats and access are still unchecked. Permission sets need the closest look.
    • If nothing is breaking: release 2.6.0. Its notes tell deployers to set RELEASE_CHANNEL=2 and check out v2.6.0.
    • If something is breaking: release 3.0.0, with upgrade notes. 2.x then gets no maintenance line, because every 2.x image tag points at moving code and there is no stable 2.x build to support. The policy would start with 3.x.
  • Confirm the Node version in the build log before announcing the CVE fix. main builds FROM node:lts-krypton, which should already resolve to 24.22.1.

What changes in #1822

Area #1822 today Under this proposal
Image tags <version>, <major>.<minor>, latest, edge, sha-<7> <version>, <major>, latest, edge, sha-<7>
Release branches release/<major>.<minor> release/<major>; planRelease checks the version's major against the branch
npm publishes from branches under release-<major>.<minor>; publish-npm.cjs reconciles dist-tags publishes only from main; a branch release logs that it skips npm; dist-tag logic removed
release environment add release/* after merge stays main only
"After merge" plan cut release/2.5, ship 2.5.3 dropped; the next release is 2.6.0 or 3.0.0 from main
Node fix: commit in the PR unchanged
release-channels.md describes the PR's model rewritten to match this policy

These would land as separate small PRs, independent of the workflow:

  • increment-version.ts: refuse a lower bump, refuse ! commits on a release branch, and rewrite RELEASE_CHANNEL on a major bump.
  • .env.template defaulting to RELEASE_CHANNEL=2, and the deployment guide switching to deploying from a tag.
  • A user-facing versioning and support page, and SECURITY.md.
  • The detection issues listed in §7.

Open questions

  1. How should this land? I'd revise ci: release from release branches and keep latest on tagged releases #1822 in place with the table above, then merge it before the next release. Merging as-is would add a dist-tag scheme, a reconciliation script and environment instructions that this proposal then deletes. The cost is that latest keeps moving with every merge until the revision lands. The alternative is to merge ci: release from release branches and keep latest on tagged releases #1822 now and do the table as a follow-up. That's safe only if the follow-up lands before the next release, because until a release happens the merge run only pushes edge.
  2. Where do deployers read the policy? Not discussed yet. Options are a docs page with a table of end-of-life dates, the "Supported Versions" section of SECURITY.md, and whether advisories go through GitHub Security Advisories.

Activity

  1. added
    Priority: HighSignificant or frequently hit user impact; target the next release or two
    DiscussionType: an open design question that needs a decision before work can start
    Area: Outreachapps/outreach and docs/ (marketing site and user documentation)
    Area: InfrastructureDeployment, Docker, CI, logging, CLIs, build and test tooling
    SecurityAuthentication, authorization, data exposure or a vulnerable dependency
    Needs DecisionWell-defined, but blocked on a maintainer decision about direction or design
    on Nov 5, 2026
  2. joshunrau commented on Nov 5, 2026

    @joshunrau
    CollaboratorAuthor

    My recommendations on the two open questions

    1. How this lands: revise #1822 in place

    1. ci: release from release branches and keep latest on tagged releases #1822 is revised following the "What changes in ci: release from release branches and keep latest on tagged releases #1822" table. Most of the change is deletion: publish-npm.cjs and its tests, the npmTag output, the release-<major>.<minor> dist-tag, and the step that adds release/* to the release environment. Two renames: <major>.<minor> becomes <major>, and release/<major>.<minor> becomes release/<major>.
    2. Re-review. The cross-ref concurrency group and the check that refuses a patch older than its line both stay. The dist-tag reconciliation goes, along with publishing npm from a branch. One addition worth making: planRelease should refuse a release/<major> dispatch while no higher major is tagged. Under §2 that branch only exists once its successor has shipped. With the check, a branch release can never plan latest, and the race from the first review can't happen even in principle.
    3. Merge, then the separate PRs listed under the table.
    4. Audit the commits since v2.5.2, then cut 2.6.0 or 3.0.0 from main.

    Why I prefer this to merging #1822 now:

    • Merging as-is would land code this proposal then deletes. It would also land docs (release-channels.md, the playbook and the odc-release skill) that would be wrong on main until the follow-up arrived.
    • One review is cheaper. A single diff that's mostly deletions takes less reviewing than a PR followed by a second PR that undoes part of it.
    • The cost is small. latest keeps moving with every merge until the revision lands, but it has been doing that for six weeks, and a revision should take days.

    If the revision stalls, the fallback is to merge #1822 as-is and land the table as a follow-up before the next release. Until a release happens, the merge run only pushes edge, so that is safe.

    2. Where deployers read the policy

    • One docs page is the source of truth. A new reference page, docs/en/5-reference/5.10-versioning.md ("Versioning and support"), covers §1–§5 of the issue, written for deployers. It ends with an end-of-life table: one row per major, giving its release date, its successor's release date and the end of its support. It's English only, like the rest of the reference section. The page also tells deployers to use Watch → Custom → Releases to hear about new versions.
    • A major release adds its row to the table. increment-version.ts can write the row on a major bump, the same way it rewrites RELEASE_CHANNEL, so the table can't fall behind.
    • SECURITY.md states the rule in two sentences and links to the page rather than copying the table, so the dates exist in one place. It should also point reporters at GitHub's private vulnerability reporting, which is already enabled on this repo but which SECURITY.md doesn't mention.
    • Every fix that counts as Security under §3 gets a GitHub Security Advisory listing the affected and patched versions on each supported line. So does the "can't backport" case in §3. We haven't published any advisories so far, even though 2.5.2 fixed several bugs that pass the impact test: returning an assignment's private key when it was created, and cancelling an assignment without update permission. Private reports already arrive as draft advisories, an advisory can carry a CVE ID, and deployers can subscribe through Watch → Custom → Security alerts.
    • Data-integrity fixes aren't vulnerabilities, so they get no advisory. Their release should still say which versions were affected and what to check again, such as re-running an export. How the release notes carry that is a detail I'd leave to the docs PR.
  3. thomasbeaudry commented on Nov 5, 2026

    @thomasbeaudry
    Collaborator

    Overall this is good, just a few suggestions;

    • production instances set exact version number (i.e. 2.6), not just 2 or latest
    • severe availability problems should qualify for security maintenance providing they don't disrupt data collection. There could be reserachers collecting data on a sensitive timeline and they can't afford to not be able to collect data.
    • 36 month is a very long time to support older releases. We also are pushing out releases at a very past pace. i would consider shortening this window, or create LTS branches that we support for 36 months
    • can we build an upgrade test tool that will check if it worked correctly/
    • I agree that intentional changes to existing user permissions require a major version bump. However, I think we should make an exception for security fixes that correct unintended permissions - e.g. if a user currently has access to data they were never supposed to see, correcting that mistake shouldn't require a major release.

    Open questions:

    1. revise ci: release from release branches and keep latest on tagged releases #1822 first, the merge after. I would also prioritize stopping the current practice of overwriting published version tags
    2. I agree with having a single documentation page listing supported releases and their end-of-support dates, and a
    3. security advisoriey. Ideally each release should have a short, upgrade notice explaining if deployment configuration changes are required, whether DB changes are involved, what testing is recommended, and how to recover if an upgrade fails (for major releases that could make it hard to recover from).
  4. gdevenyi commented on Nov 7, 2026

    @gdevenyi
    Contributor

    I agree with Tom's changes here, I would cut 36 months to 12-24.

    Definitely we need to make explicit upgrade recommendations as we are regularly generating new releases, there needs to be some info for sysadmins to decide if they "need to".

    One thing not covered here, how do we handle "large" version jumps? Do they need to be incremental?

  5. joshunrau commented on Nov 10, 2026

    @joshunrau
    CollaboratorAuthor

    Decisions

    Thanks both. Here is where each point landed.

    1. Production tags. <major> stays the documented default, and <version> stays available for deployments that upgrade by hand. Every release is in the changelog, so admins can decide when to update. @thomasbeaudry, the new workflow should also limit accidental breakages: a breaking change has to be marked !, which makes the next release a major, and increment-version.ts refuses a lower bump once one is in range.
    2. Availability. An availability issue a user can trigger that makes a core workflow unusable, such as data collection, counts as a Critical bug. It gets fixed on the current major and the newest superseded major.
    3. Support window. A superseded major is supported for 18 months after the next major ships. No LTS.
    4. Access. Fixing access that was a Security issue under §3 is not a breaking change. Its advisory says so.
    5. Large version jumps. Upgrading directly to the newest release of a major is supported, because no breaking data-model change lands between minors. How to handle a breaking stored-data change is deferred until one is proposed. The upgrade test covers releases made after the new workflow lands.

    How this lands: a new PR supersedes #1822. It implements the policy above, including stopping merges to main from overwriting latest and <version>. Where deployers read the policy: as recommended above, one docs page with the end-of-life table, SECURITY.md linking to it, and a GitHub Security Advisory for each Security fix.

    Per-release upgrade notices, rollback and breaking data changes are open in #1896.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: InfrastructureDeployment, Docker, CI, logging, CLIs, build and test toolingArea: Outreachapps/outreach and docs/ (marketing site and user documentation)DiscussionType: an open design question that needs a decision before work can startNeeds DecisionWell-defined, but blocked on a maintainer decision about direction or designPriority: HighSignificant or frequently hit user impact; target the next release or twoSecurityAuthentication, authorization, data exposure or a vulnerable dependency

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions