Concepts

Revisions and concurrency

Immutable entry snapshots, derived statuses, optimistic concurrency, and restore semantics.

Every explicit save of an entry creates a revision: an immutable, complete snapshot of the entry's data at that moment. Revisions are never diffs and never mutated — updates append a new revision and move the entry's draft pointer. This is what makes agent output reviewable: every change is attributable, comparable, and restorable.

The revision record

{
  "id": "rev_01...",
  "entryId": "ent_01...",
  "revisionNumber": 4,
  "collectionVersionId": "cver_01...",
  "data": { "title": "Launch day", "body": "..." },
  "createdByType": "agent",
  "createdById": "key_01...",
  "changeSummary": "Tightened the intro paragraph",
  "createdAt": "2026-07-23T09:41:00.000Z"
}
  • revisionNumber increases from 1 per entry and is unique within the entry.
  • data is the full field document, validated against the deployed schema at write time.
  • collectionVersionId binds the revision to the schema version it was written under. Historical revisions stay bound to their original schema version even after later schema pushes; only current heads are re-validated when schemas change or when a change set publishes.
  • changeSummary is an optional caller-supplied note (max 500 characters) passed on create and update requests.

Attribution

createdByType records who produced the snapshot:

Actor type Meaning
user A dashboard session
api_key A scoped API key (SDK, CLI, or direct HTTP)
agent An agent acting through the MCP server or an agent-attributed credential
system Myna itself (migrations, background jobs)

createdById holds the concrete user or key id when applicable.

Entry pointers and derived status

An entry carries three revision pointers: draftRevisionId (latest saved snapshot), publishedRevisionId (what the public content API serves), and lastPublishedRevisionId (the most recently published snapshot, retained across unpublish). Status is never stored — it is derived from the pointers:

Status Condition
deleted deletedAt is set
draft No published revision and never published
unpublished No published revision, but a last-published revision exists
published Draft and published pointers are identical
changed Published, with newer draft edits on top

Publishing a change set points publishedRevisionId (and lastPublishedRevisionId and draftRevisionId) at the validated target revision. Later edits only advance draftRevisionId, moving the entry to changed.

Optimistic concurrency

Concurrent writers to the same change set are detected, not merged. PATCH /v1/projects/:project/entries/:entry accepts expectedRevisionId — the revision the client based its edit on. If it no longer matches the revision the write would actually build on, the write fails with STALE_REVISION (HTTP 409):

{
  "type": "https://myna.sh/errors/stale-revision",
  "title": "Stale revision",
  "status": 409,
  "code": "STALE_REVISION",
  "detail": "The entry was modified by another writer.",
  "expectedRevisionId": "rev_01A...",
  "currentRevisionId": "rev_01B..."
}

Re-read the entry, rebase the edit, and retry.

What the edit builds on depends on the change set. With a changeSetId, it is that change set's own proposal for the entry — or what is published, if that change set has not touched the entry yet. Without one, it is the entry's draftRevisionId. An entry may sit in several open change sets, so "the current draft" is not a single revision, and an edit inside one change set never silently builds on another's unpublished work. Omitting expectedRevisionId performs a last-write-wins save. STALE_REVISION also appears as an item error code during publish validation when an entry was published after a change was staged (a stale base, settled by rebasing). See error codes.

Reading history

$ myna entries revisions posts/launch
# GET /v1/projects/:project/entries/:entry/revisions        — newest first
# GET /v1/projects/:project/entries/:entry/revisions/:revision

Both require content:read. Revision retention is plan-dependent: 30 days on Free, indefinite on Pro — see plans.

Restore creates a new revision

Restoring never rewrites history. POST /v1/projects/:project/entries/:entry/revisions/:revision/restore (scope content:restore) copies the historical revision's data into a new revision with the next revision number and the change summary Restored from revision N. The new revision:

  • becomes the entry's draft (draftRevisionId), clearing deletedAt if the entry was soft-deleted;
  • is bound to the collection's current schema version, not the historical one;
  • is staged as a create or update item on a change set, so nothing reaches the live site until that change set publishes.

POST /v1/projects/:project/entries/:entry/restore is a shorthand that restores a soft-deleted entry from its last-published revision (falling back to its latest draft).