Found while resolving objectui#6238 (the object-level enabled member of the #5761 parity family). Filed, not fixed — it is neither that card's key nor its mechanism, and it is outside the key-name parity gate's static reach for the same reason #6238's tombstone was: the keys are not declared anywhere, they arrive from the server.
The measurement
@objectstack/spec 17.2.0 declares the decorations the framework adds to a served metadata document, and ObjectSchema refuses both of them by name:
METADATA_READ_DECORATIONS = ["_diagnostics", "_draft"] // @objectstack/spec/kernel
ObjectSchema.safeParse({ name, label, fields }) => success = true (control)
ObjectSchema.safeParse({ name, label, fields, _diagnostics: … }) => unrecognized_keys ["_diagnostics"]
ObjectSchema.safeParse({ name, label, fields, _draft: … }) => unrecognized_keys ["_draft"]
The spec exports stripReadDecorations for exactly this round trip. Nothing in this repo imports it — grepped repo-wide, 0 hits, against a positive control of several files importing other @objectstack/spec/kernel symbols.
The writer
packages/app-shell/src/services/MetadataService.ts — saveFields reads the served object and spreads it verbatim into the PUT body:
constraw: any=awaitclient.meta.getItem('object',objectName);existingObject=raw?.item??raw??{};constupdatedObject={
...existingObject,// <- whatever the server served, including decorationsname: objectName,fields: fields.map(toFieldPayload),};awaitclient.meta.saveItem('object',objectName,updatedObject);A served document carrying either decoration therefore goes straight back out under PUT /api/v1/meta/object/:name, which ObjectSchema refuses by name — the hard 422 INVALID_METADATA that blocks every subsequent save of that object, with nothing in the designer UI to say which key caused it.
That the decorations really do arrive on served documents in this repo's own model is not hypothetical: metadata-admin reads _diagnostics off served metadata in a dozen places (ResourceEditPage, ResourceListPage, JsonSourceEditor, flow-problems.ts, preview-registry.ts), and packages/types/src/data.ts documents _draft on served rows.
Why this is filed rather than fixed
It is the precedented shape, and the precedent is a deliberate strip on the write side.MetadataObjectsPage.handleObjectsChange already carries exactly this guard for a different key, with the reasoning spelled out on the line:
// `...base` is a verbatim spread of whatever the server sent, so simply// not writing `group` is not enough … Strip it on the way out too — the// objectui#4644 strip-on-load shape, applied on the write side where the spread is.deletemerged.group;
saveFields has the same spread and no such strip. But which strip is the open question, and it is not mechanical:
stripReadDecorations at the call site in saveFields (narrow, one writer).- In the adapter's metadata read, so every consumer of a served document gets an undecorated one (broad — and it would have to not break the metadata-admin surfaces that deliberately read
_diagnostics). - A general "strip everything
ObjectSchema refuses before a write-back", which is the lenient-consumer shape AGENTS.md #0.1 warns about and probably the wrong answer.
Options 1 and 2 have different blast radii and 2 conflicts with live readers, so this wants adjudication rather than a drive-by.
Observational, not a reproduced 422. What is measured is: the spec refuses both keys by name, the spec declares them as read decorations, this repo reads them off served metadata, and nothing strips them before write-back. A run against a live server that actually decorated an object read is not part of this report.
Scope note
Sub-issue of objectui#6240 rather than a free-standing card: #6240 already owns the correctness of the body saveFields PUTs (its fields array-vs-map value-level rejection edits this very expression), so a resolver will be standing on this line — but fixing fields does not fix this, which is why it is recorded separately rather than folded into that description.
Refs: #6240 (the same PUT body, value level) · #6238 (the delete path, key level) · #5761 (the gate) · #6223 · #4644
Generated by Claude Code
Found while resolving objectui#6238 (the object-level
enabledmember of the #5761 parity family). Filed, not fixed — it is neither that card's key nor its mechanism, and it is outside the key-name parity gate's static reach for the same reason #6238's tombstone was: the keys are not declared anywhere, they arrive from the server.The measurement
@objectstack/spec17.2.0 declares the decorations the framework adds to a served metadata document, andObjectSchemarefuses both of them by name:The spec exports
stripReadDecorationsfor exactly this round trip. Nothing in this repo imports it — grepped repo-wide, 0 hits, against a positive control of several files importing other@objectstack/spec/kernelsymbols.The writer
packages/app-shell/src/services/MetadataService.ts—saveFieldsreads the served object and spreads it verbatim into the PUT body:A served document carrying either decoration therefore goes straight back out under
PUT /api/v1/meta/object/:name, whichObjectSchemarefuses by name — the hard422 INVALID_METADATAthat blocks every subsequent save of that object, with nothing in the designer UI to say which key caused it.That the decorations really do arrive on served documents in this repo's own model is not hypothetical: metadata-admin reads
_diagnosticsoff served metadata in a dozen places (ResourceEditPage,ResourceListPage,JsonSourceEditor,flow-problems.ts,preview-registry.ts), andpackages/types/src/data.tsdocuments_drafton served rows.Why this is filed rather than fixed
It is the precedented shape, and the precedent is a deliberate strip on the write side.
MetadataObjectsPage.handleObjectsChangealready carries exactly this guard for a different key, with the reasoning spelled out on the line:saveFieldshas the same spread and no such strip. But which strip is the open question, and it is not mechanical:stripReadDecorationsat the call site insaveFields(narrow, one writer)._diagnostics).ObjectSchemarefuses before a write-back", which is the lenient-consumer shape AGENTS.md #0.1 warns about and probably the wrong answer.Options 1 and 2 have different blast radii and 2 conflicts with live readers, so this wants adjudication rather than a drive-by.
Observational, not a reproduced 422. What is measured is: the spec refuses both keys by name, the spec declares them as read decorations, this repo reads them off served metadata, and nothing strips them before write-back. A run against a live server that actually decorated an object read is not part of this report.
Scope note
Sub-issue of objectui#6240 rather than a free-standing card: #6240 already owns the correctness of the body
saveFieldsPUTs (itsfieldsarray-vs-map value-level rejection edits this very expression), so a resolver will be standing on this line — but fixingfieldsdoes not fix this, which is why it is recorded separately rather than folded into that description.Refs: #6240 (the same PUT body, value level) · #6238 (the delete path, key level) · #5761 (the gate) · #6223 · #4644
Generated by Claude Code