← All config-management frames
Flow 1 · Settings editor · Frame (d) · /config

Save failures — each one has a different next action

A save can fail four ways, and the correct user action differs every time. Collapsing them into one "Save failed, try again" toast is what turns a recoverable conflict into silent data loss.

A · VERSION_CONFLICT — re-read and merge, never blind retry

The resolved view moved between the read and the write — someone else edited, or an ancestor changed (the version tracks inherited changes too). The server refused rather than overwrote. Offering "Retry" here would resend expectedVersion stale, and the only way to make a blind retry succeed is to drop expectedVersion — which silently destroys the other edit. So the UI re-reads and asks per key.

Settings changed while you were editing

VERSION_CONFLICT
KeyNow on serverYour changeKeep
notifications.digest.frequency changed by j.okafor · 2 min ago · "aligning with EU digest policy" weekly daily
notifications.retry.backoff changed at portal Acme Portal — inherited change, not a peer edit PT10M PT5M
2 keys to resolve

There is deliberately no "Force save" / "Overwrite anyway" affordance. The only way to take your value is to see the other value and choose it.

B · VALIDATION_ERROR — per field, at the field

The batch is atomic: one invalid value means nothing was written. The message names what was allowed, so the user learns the rule rather than guessing. Errors render against their own inputs — a summary banner alone makes the user hunt.

Retry backoffnotifications.retry.backoff
duration

Not a valid ISO-8601 duration. Expected e.g. PT5M, PT1H, P1D.

Digest frequencynotifications.digest.frequency
enum

Not one of the permitted values: hourly, daily, weekly.

2 invalid · 3 unsaved
C · LOCKED_BY_ANCESTOR — the lock landed mid-edit

The editor disabled nothing, because at read time the key was not locked — an ancestor locked it while the page was open. Proof that the disabled input is a courtesy and the server is the boundary. The row becomes locked in place and names the new lock owner.

SMS channelnotifications.channel.sms
Disabled Locked by portal Acme Portal

🔒 portal Acme Portal locked this value. lockReason: "SMS is disabled tenant-wide pending carrier review."

D · Stored value no longer satisfies its definition (warning)

The key's schema changed after the value was written. The read succeeds — it returns the default and explains why in warning, rather than failing, because one stale value must not break a consumer's boot. The editor must show both numbers: the value in force now, and the orphaned stored value the user last chose. Showing only the default makes their setting look silently discarded.

Digest frequency notifications.digest.frequency
enum
Using the default — stored value is no longer valid

Contract. PUT /v1/config with expectedVersion → VERSION_CONFLICT when the resolved view moved; ConfigWriteResult.version is the next expectedVersion. Errors branch on ErrorBody.code (ErrorCode), never on message — the message is human-facing and may change without a contract version bump. warning on EffectiveConfigEntry drives block D.
Atomicity is a UX property, not just a DB one. Every failure banner says explicitly that nothing was saved, because "some of your changes were applied" is the state users cannot reason about.

← Editor states Next: Flow 2 — key catalog →
Review · config-management approve or reject each flow ↓
settings-editorapproved · izzywdev
key-catalogapproved · izzywdev
secret-auditapproved · izzywdev