Audience: customers building integrations that mutate the same content from multiple writers (e.g., a sync pipeline + the admin UI editing the same article, or two import jobs racing on the same slug). Most customers don't need to think about this.

The fuzz battery (FUZZ-F7, 2026-06-27) flagged that parallel POST /api/import/json + DELETE /api/content/:type/:slug on a single slug is non-deterministic: the DELETE can "win" or "lose" depending on which mutation lands at the engine writer last.

This page documents the actual semantics, why they're what they are, and what to do if you need stronger guarantees.


What you get today

Each single mutation is atomic. The engine writer is single-instance + sequenced; one create/update/delete lands as one writer transaction. There's no half-state.

Across mutations on the same slug, last-writer-wins. If you fire:

T+0ms   POST /api/import/json   (with the slug "x" in the batch)
T+5ms   DELETE /api/content/article/x

The order they arrive at the writer determines the final state. If DELETE lands first, then the import recreates the slug. If the import lands first, DELETE removes it. There's no version negotiation between the two.

This is intentional — the engine is single-writer, sequenced, and append-only. It's a coherent contract; it's just not a locking contract.


When this matters

Scenario Risk
One admin user editing one article None — single source.
Sync pipeline + admin both touching the same slug DELETE in admin can be silently undone by next sync tick.
Two import jobs racing on overlapping slugs One job's writes overwrite the other's; no error surfaced.
MCP agent editing while a build is publishing Build reads a frozen snapshot — safe.

What to do if you need stronger guarantees

1. Idempotent imports

If your sync pipeline is the source of truth, design imports so the current state of the source is what lands — and DELETE in the admin will get clobbered on next sync. This is the cleanest model.

2. ETag / If-Match (planned, not yet shipped)

A future surface will accept an If-Match: <etag> header on PATCH/DELETE. The etag will be the content node's updatedAt. Mismatched etag returns 409 + the current updatedAt so the caller can resync. This is the standard optimistic-concurrency pattern.

Until shipped, the CMS doesn't enforce this. Track [#XXX] if it matters to you.

3. Don't race in the first place

If you have one workflow that DELETEs and another that imports, gate the import on "is the content currently deleted?" via a GET before the POST. Race window shrinks dramatically and is acceptable for most pipelines.

4. Use bulk endpoints with skipExisting: true

POST /api/import/json {records: [...], skipExisting: true} won't overwrite existing slugs — its writes use MERGE ... ON CREATE SET so a row already present is a no-op. Combined with explicit DELETE only via the admin, this prevents most race outcomes.


Why we don't lock

Locking on every slug would:

Sequenced last-writer-wins is the right default for an append-only content store. Stronger guarantees are layered on top (etags, opaque session tokens, leases) — and those are surfaces we'll add when customer demand justifies the engineering cost.


Audit trail still works

Even when last-writer-wins, the bitemporal audit chain records every mutation. You can ask "what did article X look like at 14:23:01" and the answer is exact, even if 14:23:02 then deleted it and 14:23:03 recreated it. The history is whole; the current state is whatever the last writer said.


Related

Related docs

Resolved at build time via {% similar %} — cosine similarity over embeddings, not tag overlap. Zero arguments.

Data durability & retention
66% match
Audit chain — how tamper-evident history works
62% match
Product positioning — StaticOwl Docs
62% match