|
| 1 | +--- |
| 2 | +"@objectstack/metadata-core": minor |
| 3 | +"@objectstack/metadata-protocol": minor |
| 4 | +"@objectstack/objectql": patch |
| 5 | +--- |
| 6 | + |
| 7 | +fix(metadata-protocol): a `/meta` object read serves the effective runtime schema, whichever layer answered (#6562) |
| 8 | + |
| 9 | +`GET /api/v1/meta/object/:name` answered a **different set of fields** depending |
| 10 | +on which link of its resolution chain produced the answer, for the same object: |
| 11 | + |
| 12 | +- **registry-backed** → the schema AFTER `applySystemFields`, so it carried the |
| 13 | + injected system columns — `created_at`, `created_by`, `updated_at`, |
| 14 | + `updated_by`, `organization_id`, `owner_id`, `owning_business_unit_id` — even |
| 15 | + when the author declared none of them; |
| 16 | +- **overlay-backed** (a `sys_metadata` customization row, or a MetadataService |
| 17 | + body) → the stored document VERBATIM, so every one of those columns was simply |
| 18 | + absent. |
| 19 | + |
| 20 | +Whether an object carries an overlay is invisible to the caller, so the same |
| 21 | +request reported the platform's own columns or not, and nothing in the response |
| 22 | +said which had happened. `/meta` is the machine-readable contract clients and AI |
| 23 | +authors code against: an author reading an overlay-backed object saw no |
| 24 | +`created_at` / `owner_id` / `organization_id` and reasonably concluded the |
| 25 | +columns do not exist — while every one of them is real in the database, |
| 26 | +filterable, orderable, and enforced read-only on write. |
| 27 | + |
| 28 | +**Every `/meta` object read exit now serves the effective schema.** The |
| 29 | +single-item read, the list, the cached/ETag branch, both draft reads and the |
| 30 | +layered read's `effective` layer all report the injected columns, with the same |
| 31 | +`readonly` / `system` markers the engine enforces (`owner_id` stays |
| 32 | +`readonly: false` — ownership is transferable). This is the presence half of the |
| 33 | +seam #4513 closed the value half of. |
| 34 | + |
| 35 | +Three things deliberately did **not** change: |
| 36 | + |
| 37 | +- **`?layers=1`'s `overlay` layer stays byte-verbatim.** Injection happens at the |
| 38 | + read exits only, so Studio's "what you customised" diff never shows a column |
| 39 | + nobody wrote. Only `effective` is injected. |
| 40 | +- **A `GET` → `PUT` round-trip still persists a byte-identical body** (#4326). |
| 41 | + The write path gained the strip counterpart: a field byte-identical to the |
| 42 | + platform's own definition is removed again on save, so a served document handed |
| 43 | + straight back stores exactly what it stored before — same checksum, same |
| 44 | + history diff. A declared `owner_id` carrying the author's own label is *not* |
| 45 | + the platform's definition and survives untouched. |
| 46 | +- **A declared system column stays the author's.** Injection only ever adds a |
| 47 | + column nobody declared; it never rewrites one that was. |
| 48 | + |
| 49 | +Which columns an object carries is `resolveInjectedSystemColumns` |
| 50 | +(`@objectstack/spec/data`, #5378) — the same derivation `applySystemFields` |
| 51 | +consumes — so every opt-out (`systemFields: false`, `managedBy: 'better-auth'`, |
| 52 | +`systemFields.audit`/`.tenant`, `tenancy.enabled: false`, the per-tier |
| 53 | +`ownership` table, the `sys_*` namespace) is answered in one place and re-derived |
| 54 | +in none. **What** each column looks like moves to `@objectstack/metadata-core` |
| 55 | +(`AUDIT_FIELD_DEFS` and the three tenancy/ownership anchors, re-exported from |
| 56 | +`@objectstack/objectql` so the symbols still resolve there) — the same relocation, |
| 57 | +for the same dependency cycle, as the audit-governance table in #4513: |
| 58 | +`@objectstack/objectql` depends on `@objectstack/metadata-protocol`, so the read |
| 59 | +path could not import the definitions from the registry that provisions them. |
| 60 | +One table now feeds the injection pass and the read exits, so they cannot drift. |
| 61 | + |
| 62 | +One key is deliberately not carried onto a served document: `organization_id`'s |
| 63 | +`indexed`. It is not a `FieldSchema` key — removed in the 16.x line (#2377, |
| 64 | +ADR-0049) and rejected by name by the strict schema — and its only consumer is |
| 65 | +`driver-mongodb`'s schema builder, which reads the registered schema and never a |
| 66 | +served document. It stays at the injection site; that the registry-backed read |
| 67 | +answers `_diagnostics: { valid: false }` because of it is filed as #6810. |
0 commit comments