|
| 1 | +--- |
| 2 | +'@objectstack/metadata-protocol': minor |
| 3 | +--- |
| 4 | + |
| 5 | +fix(metadata-protocol)!: the metadata save door never sets, changes or removes an item's lock: a body's `_lock*` family that would do so is refused with a located `400 VALIDATION_ERROR`, an echo of the served lock is accepted and ignored, and the row keeps the lock it stores (ADR-0010 §3.5, §4.1) |
| 6 | + |
| 7 | +Clause-②: no (narrowing) |
| 8 | + |
| 9 | +<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: the `_lock*` keys stay declared on every type that declares them, a code package's `protection` block keeps translating into them at load, and no spec key, authorable spelling, export or stored shape is added, removed, renamed or re-shaped. No stored row is rewritten or unlocked: a row keeps its bytes and loads unchanged, so there is nothing for `objectstack migrate meta` to rewrite. What narrows is one runtime write door: a body saved through the metadata API whose `_lock*` family would set, change or remove the item's lock is refused, and the remedy is in the request a caller sends to that door, never a rewrite of a package source or a stored row. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and no exported declaration is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). --> |
| 10 | + |
| 11 | +**BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes. |
| 12 | + |
| 13 | +The `_lock` family (`_lock`, `_lockReason`, `_lockDocsUrl`, `_lockSource`) is the runtime's private lock envelope. A code package's loader stamps it from the package's `protection` block, and an install or a migration's re-save carries a stored one. ADR-0010 strips it on write and reattaches it on load (§4.1), and an item saved through the metadata API is authored in the environment, which takes no package lock (§3.5). |
| 14 | + |
| 15 | +**This entry supersedes one sentence of the entry `23096-save-door-protection`, in this same release.** That entry says, under its unchanged behaviour, "A body without `protection` saves as before". What holds now: a body without `protection` saves as before only when it also sends no `_lock*` key, or sends back exactly the `_lock*` family a read of that item serves; a body whose `_lock*` family would set, change or remove the item's lock answers `400 VALIDATION_ERROR` and writes nothing, as this entry states. Where the two entries disagree, this one is current. |
| 16 | + |
| 17 | +**FROM.** `PUT /api/v1/meta/{type}/{name}` stored a body's `_lock*` family verbatim. A hand-written `{ "_lock": "full" }`, or one an author or an AI copied from another item's served body, answered `200`, and the item then read `lock: 'full'`, `editable: false`, `deletable: false`: its next save and its delete both answered `403 ITEM_LOCKED`, and no door unlocks it. Any holder of the authoring capability could make an environment item permanently uneditable and undeletable. The other way round, an author save that sent no `_lock*` key dropped a lock the platform had stamped on the row, so a `no-delete` stopped refusing the delete. |
| 18 | + |
| 19 | +**TO.** An author's save keeps the lock family the row it writes already stores. |
| 20 | + |
| 21 | +- **No `_lock*` key in the body.** The save goes through, and the row keeps its stored lock. A body that says nothing about the lock does not remove it. |
| 22 | +- **An echo is accepted and ignored.** Studio and the AI authoring surfaces send the served body back on every edit, so a family equal to one a read of the item serves passes: the family the item read, the layered read's `effective` and the list serve (the item's resolved lock), or the family stored on the row the save writes (what the draft read and the layered read's `overlay` layer serve). The row still keeps its own stored family, so an echo of a packaged item's lock on its first overlay stores none in the environment row; the package's lock still binds. |
| 23 | +- **Anything else is refused**, `400 VALIDATION_ERROR`, and nothing is written: a family that would set a lock (on a new item, or on one that has none), change one (its state or its prose) or remove one (`_lock: 'none'`, or a partial echo that drops part of it), and a family copied from another item. The message names each `_lock*` key sent and the door that does lock an item, and `issues[]` carries each key as a path, so the Studio designer can highlight it. |
| 24 | +- **Every authoring write**: the REST and dispatcher saves, draft and publish, the external-table import, the unmanaged install, and the domain doors that save through them (the flow and permission-set clones, the permission-set and position write-throughs, the package publish's app flip). A type with no schema (`external_catalog`) is judged too. |
| 25 | +- **Unchanged.** A `_lock*` value the type's declaration refuses (`_lock: 'locked'`, a non-string reason) is still the schema's `422 INVALID_METADATA`, so the unmanaged install's refusal of such an item does not move. The server-side rewrites (`os migrate meta --stored --apply`, the stored flow credential move, package duplication) keep the family their body carries. A packaged item's lock still binds, and a save against a locked item still answers `403 ITEM_LOCKED` first. No lock stored today is released. |
| 26 | +- **Remedy.** Delete the `_lock*` keys from the body you send to the metadata API, or send them back exactly as a read of that item served them. To lock an item, declare `protection` on it in a code package's source, where the package loader translates it into the item's lock. |
0 commit comments