Concepts

Releases and reverting

Every publish is a numbered release, and reverting one drafts an ordinary change set that restores the state before it.

A release is a change set that has been published. There is no separate resource: publishing assigns the change set a sequential releaseNumber within the project and that is what makes it a release. The change set already recorded what shipped, who shipped it, when, and — through each item's baseRevisionId — the state every resource was based on. That last field is what makes reverting possible.

$ myna releases list
#   TITLE                  PUBLISHED             BY            ITEMS  STATE
12  Autumn pricing         2026-07-24T09:12:04Z  Deploy agent  3      live
11  Fix launch post typo   2026-07-23T16:40:11Z  Ana Duarte    1      reverted by #12

Release numbers are per project, assigned in publish order, and never reused. A change set that is closed instead of published never becomes a release.

Reading releases

  • GET /v1/projects/:project/releases lists releases newest first, paginated on the release number.
  • GET /v1/projects/:project/releases/:release reads one. :release accepts either the release number (12) or the id of the change set that produced it (chs_01...), so a number copied out of a changelog works directly.

Each release carries itemCount, a resolved publishedBy identity, and both directions of any revert relationship: revertsReleaseNumber when the release was itself a revert, and revertedBy when something has been drafted or published to undo it. To see field-by-field what a release changed, run myna releases diff <n>, or read the diff of its change set directly — the release id is the change set id.

Releases are also addressable from the Content API. ?at=<release> reads a collection as that release served it, which is what lets a repository pin its content and build reproducibly.

Reverting

POST /v1/projects/:project/releases/:release/revert does not publish anything. It creates a new open change set that stages the inverse of every item in the release, and returns it. From there it is an ordinary change set: it validates, previews, accepts comments, runs checks, and honors the project's approval and check gates exactly like a change written by hand.

$ myna releases revert 11
Created change set chs_01J9... — Revert release #11
OP      RESOURCE            RESTORES
update  posts/launch        rev_01J8...
Not published. Review, then: myna changes publish chs_01J9... --confirm-publish

That is deliberate. A revert is usually authored under time pressure, which is exactly when the review path matters most — and it is why the endpoint requires content:write rather than content:publish. Whoever holds content:publish still decides whether the rollback ships.

What the inverse of each operation is

The release did The revert stages Effect on publish
create (nothing was published before) unpublish Clears the published pointer; the entry and its revisions stay
update update with the pre-release data copied into a new revision The old content returns as the current revision
unpublish create or update restoring baseRevisionId The entry is published again
delete create or update restoring baseRevisionId The entry is un-deleted and published again
asset delete asset restore The asset returns to published

Restoring data copies it forward into a new revision rather than repointing at the old one. Revisions are immutable, and history should show that someone reverted and when — not that an old revision quietly became current again. The new revision is stamped with the collection's current schema version, so if the schema has moved on in a way the old data no longer satisfies, the change set's validation and schema check say so before it can publish.

What cannot be reverted

The response separates what will be put back (reverted) from what was deliberately left alone (skipped), each skip carrying a reason:

Reason Meaning
superseded A later release changed this resource, so restoring it would undo that later work too
no_base_state The release recorded no revision preceding its operation, so there is nothing to restore
asset_not_revertible Assets carry no revision history, so publishing one cannot be undone — delete it instead
missing The resource no longer exists

superseded is the one that stops the whole call. If any resource in the release has moved on, the revert is refused with CONFLICT and the full list in the error details, rather than reverting the rest and silently skipping the interesting part. Pass {"force": true} (or --force) to take those resources back anyway, discarding the later work for them; the plan is then rebased on what is published right now.

A revert is also refused when a revert of that release is already open or already published (CONFLICT, with the existing change set id). It is not refused because an entry is mid-edit elsewhere: an entry may be staged on several open change sets, so both proposals stand and whichever publishes second rebases.

Reverting a revert

A revert is a normal release once it publishes, so it gets its own number and can itself be reverted. That is how you roll forward again after deciding the rollback was the mistake.

Events and attribution

Publishing emits change_set.published, which now carries releaseNumber. There is no release.published event, because that would be the same event twice.

Publishing a change set that reverts a release additionally emits release.reverted, carrying the release that was undone and the release that undid it. See the event reference.

Audit records the revert's creation as release.revert_created and its publish as release.reverted instead of change_set.published, so the activity log distinguishes a rollback from an ordinary ship.

Why this matters for agents

Granting an agent content:publish used to be an irreversible decision: publish had no inverse, so the only recovery was to reconstruct the old content by hand, entry by entry. With a one-call revert that produces a reviewable change set, letting an agent ship is a decision you can take back — which is a very different risk to accept.