You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4443642
Browse filesBrowse the repository at this point in the historyBrowse files
An email template edited through `PUT /api/v1/meta/email_template/:name` (the Studio editor's door) now keeps the admin's wording in `sys_email_template` across a restart. Before, the boot sweep wrote the package wording back over the sending row while `GET /meta` kept serving the admin's, so mail went out with the package wording after every boot.
6
+
7
+
Clause-②: no
8
+
9
+
- The cause: on a deployment with a Default Organization the admin's save is an org-scoped overlay, and boot hydration keeps org-scoped overlays out of the registry the sweep read. An env-wide overlay was already kept.
10
+
-`EmailServicePlugin`'s boot sweep now projects the effective template: the layered list `protocol.getMetaItems` serves, read in the organization `tenancy.defaultOrgId()` names. That is the Default Organization under the `single` posture. A host without a `protocol` service reads the registry as before.
11
+
- A failed effective read projects nothing for that boot, so the rows keep their last projection. It does not fall back to the package wording.
12
+
- Seed-not-clobber is unchanged. A row an admin created (`managed_by: 'admin'`) or edited through the data API (`customized: true`) is still never overwritten.
13
+
- The published API is unchanged. The exported `bootstrapDeclaredEmailTemplates` keeps its signature and still reads the registry, so a caller outside the plugin sees the same behaviour as before.
The content hash keeps the order of an object's `fields`, so a pure field reorder is a new version instead of "no change" (#21790).
6
+
7
+
Clause-②: yes
8
+
9
+
-**`canonicalize(value, type?)` and `hashSpec(value, type?)`** take the metadata type. For a type whose body has a map the spec declares ordered, that map keeps its insertion order in the canonical form. Today that is one map: `object.fields`, whose traversal order is the field order the platform presents. Every other map stays key-order independent, including the keys around `fields` and the keys inside each field definition. Called without a type, both functions return exactly what they returned before.
10
+
-**`orderedMapKeys(type?)`** is a new export. It returns the top-level keys of a `type` body whose map keeps its order (`['fields']` for `object`, `[]` otherwise).
11
+
-`InMemoryRepository` and the repository contract suite hash as `ref.type`. Invariant 4 now reads `item.hash === hashSpec(item.body, item.ref.type)`.
12
+
-**Stored hashes.** An object whose `fields` are already in sorted key order hashes exactly as before. Any other object hashes differently from the hash stored before this release. A stored hash is still that row's version token: `@objectstack/metadata-protocol` keeps it as written and compares content to decide whether a save changed anything.
13
+
14
+
`minor` because two exports widen: a new parameter and a new function. No metadata key, accepted value, wire payload or error code changes.
`FileSystemRepository` hashes each item as its metadata type, so a reorder of an object's `fields` is a change (#21790). A reordered object is written, and an external edit that only reorders `fields` is reported as an update.
6
+
7
+
Clause-②: yes
8
+
9
+
Event-log entries written before this release carry the order-blind hash. For an object whose `fields` are not in sorted key order, `get()` and `list()` no longer find that entry until the item next changes. They fall back to the defaults: no parent hash, sequence `0`, the filesystem actor, and the epoch timestamp. The body and the version hash are unaffected.
Publishing a pure field reorder of an object now saves it. The object designer's drag-to-reorder used to answer success on publish, keep the old order and delete the draft (#21790).
6
+
7
+
Clause-②: yes
8
+
9
+
-`SysMetadataRepository` hashes each body as its type, so a reorder of an object's `fields` is a content change. It is written, recorded in history and served by `GET /api/v1/meta/object/:name`.
10
+
-**Rows stored before this release** keep the `checksum` they were written with. That value is still the version token: reads return it, `If-Match` tokens are derived from it, and the optimistic lock compares against it, so upgrading raises no conflict.
11
+
- For an object, "is this save a no-op?" is decided by hashing the stored body under the current rule, not by comparing against the stored checksum. An identical re-save of an old row writes no history row and keeps its checksum. A reorder into sorted key order is written too. Its new hash equals the old order-blind checksum, so a checksum comparison would have dropped it.
12
+
- A save that changes nothing returns the stored checksum as its version, so the receipt's token is the one the next `If-Match` save must send.
A declared `PATCH` AI route is reachable over HTTP: the dispatcher's `/ai/*` method wildcards mount `patch` beside `get`, `post`, `put` and `delete`, so `PATCH /api/v1/ai/conversations/:id` (the SDK's `ai.conversations.update`, the console's conversation rename) reaches its handler instead of answering `405` (#21806).
6
+
7
+
Clause-②: no
8
+
9
+
-**Where it failed.** On a host where the wildcards are the only door into `/ai/**`, a `PATCH` never reached the dispatcher. The server adapter answered `405 METHOD_NOT_ALLOWED` with `Allow: DELETE, GET, HEAD, POST, PUT`, because the path matched the wildcards under the four other verbs. Both bases the wildcards serve are fixed: `/api/v1` and `/api/v1/environments/:environmentId`.
10
+
-**An undeclared method still answers `405`.** The AI route table now tells its two misses apart. A method the table does not declare on a path it does declare answers `405 METHOD_NOT_ALLOWED`, with an `Allow` header that names exactly the declared methods, and no handler runs. A path the table declares under no method still answers `404 ROUTE_NOT_FOUND`. The rule is the same for every verb.
11
+
-**What a caller sees change.** A `GET`, `POST`, `PUT` or `DELETE` that names a declared AI path under the wrong method used to answer `404 ROUTE_NOT_FOUND`. It now answers `405` with `Allow`. A `PATCH` to an AI path the table does not declare at all used to answer the adapter's `405`. It now answers `404 ROUTE_NOT_FOUND`. No request that was refused before is served now, except a `PATCH` to a route the table declares.
Copy file name to clipboardExpand all lines: content/docs/concepts/metadata-lifecycle.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -146,7 +146,7 @@ The switches are operator-only where one install serves several organizations. T
146
146
147
147
Every `put()` requires a `parentVersion` (or `null` for fresh creates). If the head has moved since the caller read it, the repository throws `ConflictError(ref, expectedParent, actualHead)` with code `METADATA_CONFLICT`. `protocol.ts` catches it at every write entry point (save, publish, rollback, overlay-delete) and re-throws an API error with code `metadata_conflict`, **HTTP 409 Conflict**, and the `expectedParent` / `actualHead` pair attached. Clients are expected to re-read the item and retry; the console does not yet surface a dedicated conflict-resolution UI.
148
148
149
-
The hash is `sha256:` + 64-hex of a canonical (sorted-keys, no-undefined) JSON serialization of the body. Identical bodies produce identical hashes — `put()` short-circuits on no-op writes.
149
+
The hash is `sha256:` + 64-hex of a canonical JSON serialization of the body, with `undefined` values dropped. The canonical form sorts object keys, except in the maps the spec declares ordered. Today that is one map: an `object`'s `fields`, which keeps its declared order, so a pure reorder of fields is a new version. `put()`still short-circuits on a write whose body is unchanged. A row written before this release keeps its stored hash as its version token until its content next changes.
0 commit comments