Skip to content

Commit 810b1c4

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-21855-member-params-object-refused
2 parents a6f2307 + 2df3d13 commit 810b1c4

29 files changed

Lines changed: 2059 additions & 237 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/platform-objects': patch
4+
---
5+
6+
Studio's object form offers `imageField`, the record's picture, as a text row beside `nameField`
7+
8+
Clause-②: no
9+
10+
The object form in the metadata form registry now has an `imageField` row, a plain text input placed beside `nameField`. Until now the only way to set the record picture from Studio was the Source tab's raw JSON. The help text says what the parse accepts: a field of this object whose type is `image` or `avatar`. Left empty, the object has no record picture and no placeholder is drawn. The row brings no picker and no validator of its own. A name that is not an `image` / `avatar` field of the object is refused when the object is saved, by the same parse rule as before.
11+
12+
`@objectstack/platform-objects` ships the row's label and help text in its metadata-form translation catalogs, translated for `zh-CN`, `ja-JP` and `es-ES`.
13+
14+
No schema key, accept set, refusal, error code or status changes, and you have nothing to re-author.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/service-datasource': minor
3+
---
4+
5+
fix(service-datasource)!: "Import as Object" saves the imported federated object through the metadata door's own save, so it is durable and reads from its remote table; federated validation stops reporting the platform's injected anchors as missing remote columns (#21788)
6+
7+
**BREAKING** — the import route now answers `400` to some re-imports that used to answer `201`.
8+
9+
Clause-②: no (narrowing)
10+
11+
- **The import was neither durable nor mapped.** `POST /api/v1/datasources/:name/external/tables/:remote/import` held the generated object in the metadata service's memory only. No `sys_metadata` row was written, the object's storage was not synced, and the driver was never told the object's remote table. An object imported under a name that differs from its remote table answered `201` and then `500 DATABASE_ERROR` (`no such table: <name>`) on its first read. Every import was gone after a restart (`404 OBJECT_NOT_FOUND`).
12+
- **It now saves like `PUT /api/v1/meta/object/:name`.** The import calls `saveMetaItem` on the `protocol` service with the request that door sends for an `object`. The object becomes a `sys_metadata` row the next boot binds, it is written through to the engine registry, and it is mapped onto its `external.remoteName` table. An import under a different name and one under the remote table's own name both serve the remote rows, before and after a restart. The save door is looked up when an import runs. A deployment with no metadata save door still refuses the import with "requires a writable metadata store", before any remote introspection.
13+
- **What narrows.** The metadata door's refusals now apply to the import, and the route relays each one as `400 EXTERNAL_IMPORT_ERROR` with the door's message. A re-import that would drop a field the stored object already has, or change its type, is refused as a destructive change. It used to answer `201` with an in-memory overwrite that a restart discarded. An import whose `name` collides with an object the metadata door will not overwrite is refused the same way. The route's request and response shapes are unchanged.
14+
- **If a re-import or a name is refused:** import the table again under a new `name`. To change an object you already imported, save its new definition through `PUT /api/v1/meta/object/:name?force=true`, which accepts the destructive change on purpose. Re-submitting the import with `?force=true` does not help, because the import route reads no `force`.
15+
- **Federated validation compares only what the remote owns.** A stored federated object is read back with the anchors the platform injects without storage (`organization_id`, `created_by`, `updated_by`, `owner_id`, `owning_business_unit_id`). The boot validation gate and `POST …/external/validate` reported each of them as a `missing_column` at error severity. So once a datasource with the default `external.validation.onMismatch: 'fail'` held such an object, it refused to boot. Validation now skips the columns `unprovisionedInjectedColumns` names. This closes the boot abort for objects saved through `PUT /api/v1/meta/object/:name`, not only for imports. A declared column the remote lacks is still a `missing_column` at error severity, and `fail` still aborts on it.
16+
17+
<!-- adr-0087: not-required (no-migration-prescription) a runtime refusal on one REST route: the external-table import now relays the metadata door's own save refusals (a destructive re-import, a name that door will not overwrite) as 400 where it answered 201 with an in-memory write no restart kept. No authorable metadata key, spelling, export, config field or stored shape moves: no schema changes, no sys_metadata row is read or rewritten, and which table or name an operator meant to re-import is not something a ledger entry can rewrite. The validation half refuses less, not more. The other categories are closed on facts: @objectstack/service-datasource publishes (not unpublished); no ADR-0087 id covers this route (not already-registered); and the change is a runtime verdict, not a declaration (not runtime-interface-only or type-surface-only). -->
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
"@objectstack/metadata-protocol": minor
3+
---
4+
5+
fix(metadata-protocol)!: an item's lock is the strictest among the installed packages that ship its name, at the write doors and on both reads (#21803)
6+
7+
Clause-②: no (narrowing)
8+
9+
ADR-0048 lets two installed code packages ship one `(type, name)`. The ADR-0010 `_lock` gate looked the packaged artifact up with no package, so it bound the artifact of whichever package was registered first, while `getMetaItem`, `getMetaItemLayered`, the metadata list and the `getMetaDiagnostics` locked count looked it up with the request's package. One item had two lock answers, and the door's answer depended on registration order.
10+
11+
Now every caller takes the artifact layer from one selection: the artifact of every installed package that ships the name, a disabled package included (it is still installed). The lock is resolved once per shipping package, that package's artifact over the stored rows in scope exactly as before, and the item's lock is the strictest of those answers. With one package shipping the name, or none, nothing changes.
12+
13+
**What moves for consumers.** The door now refuses where it used to depend on registration order:
14+
15+
- one package ships a lock and another ships none: a save or delete, with or without `?package=`, is refused `403 ITEM_LOCKED` under both registration orders, where it was admitted when the unlocked package was registered first;
16+
- one package ships no lock, the stored row in scope declares a lock, and another package ships a lock refusing the other verb (for example `no-overlay` on the row and `no-delete` on the artifact): both a save and a delete are refused, where each was admitted under the registration order that bound the other answer;
17+
- a disabled package's packaged lock binds under both registration orders, where it bound only when that package was registered first.
18+
19+
No write the door refused before is admitted now: the artifact the door bound before is always one of the shipping packages. Both reads report the same lock as the door in `lock`, `editable` and `deletable`, under both orders: a read naming a package that ships no lock now reports `editable: false` when another installed package ships one. The refusal and the reads carry the prose of the binding package (the request's own package first, then the others by package id), and no prose when no single package's answer is the strictest. Content stays prefer-local: a read naming a package is still served that package's own artifact, under that package's provenance.
20+
21+
A served body (`getMetaItem`'s `item`, `getMetaItemLayered`'s `effective`, the list items) now carries exactly the lock family of the resolution's answer, and no `_lock` key when nothing binds. Before, a body served from a stored row outside the lock's scope kept that row's `_lock` while the envelope reported `none` (an organization holding only another package's row, with the request's package served its env-wide row), and an explicit `_lock: 'none'` stayed on a body. No key, export, status or error code changes.
22+
23+
<!-- adr-0087: not-required (no-migration-prescription) the write doors refuse, by the strictest lock among the installed packages that ship an item's name, writes they used to admit when an unlocked package was registered first. No authorable key, spelling, export or stored shape moves: every artifact and stored row keeps parsing, nothing is read or rewritten at rest, and which package an administrator meant to lock is not something a ledger entry can rewrite. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers the lock gate (not already-registered); and the change is a door verdict, not a declaration (not runtime-interface-only or type-surface-only). -->
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/metadata': patch
3+
---
4+
5+
`DatabaseLoader` now persists a `register` whose only change is the order of an object's `fields` (#21828). Before, the loader's checksum sorted every map, so a field reorder hashed equal to the stored row and was never written. The running process still saw the new order, but the persisted `sys_metadata` row kept the old one.
6+
7+
Clause-②: no
8+
9+
- **One hash vocabulary in `sys_metadata.checksum`.** The loader now stamps the hash `SysMetadataRepository` stamps on the same column: `hashSpec(body, type)` from `@objectstack/metadata-core`, written as `sha256:` + 64 hex. It is hashed as the item's metadata type, so a reorder of an object's `fields` is a change and every other map is still key-order independent. Its history rows carry the same value as the row they record. Before, the loader wrote bare hex from `calculateChecksum`. That function is unchanged and still exported, but nothing writes the column with it.
10+
- **Rows stamped before this release.** Whether a `register` is a no-op is now decided by re-hashing the stored body, not by comparing the stored checksum. A row with an unchanged body is not rewritten: no version bump, no history row, and it keeps its old checksum until its content next changes. The first real change rewrites it with the new stamp. A reorder into sorted key order is written too, even against a stored checksum that sorted every map.
11+
- **ETags.** `load()` and `stat()` report the row's checksum as `etag`, so a row the loader writes from this release on reports a `sha256:` value.

0 commit comments

Comments
 (0)