Found while implementing objectui#6519 (extending object-fields-io's retired-key strip set). Filed, not fixed: the two ways out trade against each other and the choice is a maintainer call, not a mechanical edit.
What is measured
packages/app-shell/src/views/metadata-admin/previews/object-fields-io.ts is the single read door for draft.fields across the object designer, and writeFields writes each def back verbatim. objectui#6519 extended its strip set to referenceTo and isSystem. It deliberately did NOT add formula, and that leaves the card's harm live for one key:
FieldSchema refuses formula by name on the installed @objectstack/spec 17.2.0 ("Did you mean `formula` -> `expression`?"), reported through the whole document at path fields.amount, i.e. the hard 422 INVALID_METADATA that blocks EVERY later save of that object.- The premise holds: the Field Designer's formula textarea wrote the key until objectui#6043 retired the control, so stored objects can carry it.
Why objectui#6519 did not just strip it
ObjectFieldInspector is this platform's sanctioned migration surface for that exact key:
value={readPredicate(def.expression??def.formula)}onChange={(v)=>patchDef({expression: writePredicate(def.expression??def.formula,v),formula: undefined, ... })}The legacy value seeds the linting CEL editor and the first edit commits the spec key and clears the alias. Stripping at the read door empties that editor. Measured on the objectui#6519 branch, with formula added to RETIRED_FIELD_KEYS:
FAIL packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.test.tsx
> commits edits to `expression` and migrates the legacy `formula` key
expect(element).toHaveValue(record.amount)
Received: ""
Tests 1 failed | 42 passed (43)
And objectui#6043 refused a blind rename in plugin-designerprecisely because this linting editor exists to migrate the value properly — a strip at the read door would discard what that ruling preserved, on the next save, with no author-visible surface.
The two options
A — strip formula at the read door (finishes objectui#6519's pattern). Every object saves again immediately. Cost: the authored expression text is dropped on the next save, silently; the inspector's migration path and its pin go with it.
B — leave it, and make the blocked state actionable instead. Client validation already parses object drafts against the real ObjectSchema (clientValidation.ts LOADERS), so the author can be told which field carries the key and pointed at the formula editor, where one edit migrates it. Cost: until they do that, the object stays unsaveable, and today the diagnostic does not say what to do about it.
Recommendation: B, on all four axes
- Real business need — the population is objects whose formula fields were authored before objectui#6043. For them the authored expression IS the business content; A destroys it to unblock a save, B keeps it and asks for one edit.
- Long-term soundness — B keeps one adjudicated story (objectui#6043: the legacy text is migrated through a linting editor, never laundered or dropped blindly). A creates a second, contradictory rule at a different layer for the same key.
- Making AI-written metadata hard to get wrong — a strip that silently deletes authored source is the failure mode this platform keeps closing elsewhere; a loud, specific diagnostic is the shape that teaches.
- Startup scope discipline — B is a diagnostic message on a gate that already runs, not new surface. A is cheaper to type and more expensive to own.
If the maintainer prefers A, the tombstone on RETIRED_FIELD_KEYS and the ObjectFieldInspector pin must be updated in the same PR — they currently record the opposite.
Refs: objectui#6519 (the strip set this came out of) - objectui#6043 (the refused rename) - objectui#4644 (the indexed precedent).
Generated by Claude Code
Generated by Claude Code
Found while implementing objectui#6519 (extending
object-fields-io's retired-key strip set). Filed, not fixed: the two ways out trade against each other and the choice is a maintainer call, not a mechanical edit.What is measured
packages/app-shell/src/views/metadata-admin/previews/object-fields-io.tsis the single read door fordraft.fieldsacross the object designer, andwriteFieldswrites each def back verbatim. objectui#6519 extended its strip set toreferenceToandisSystem. It deliberately did NOT addformula, and that leaves the card's harm live for one key:FieldSchemarefusesformulaby name on the installed@objectstack/spec17.2.0 ("Did you mean `formula` -> `expression`?"), reported through the whole document at pathfields.amount, i.e. the hard422 INVALID_METADATAthat blocks EVERY later save of that object.Why objectui#6519 did not just strip it
ObjectFieldInspectoris this platform's sanctioned migration surface for that exact key:The legacy value seeds the linting CEL editor and the first edit commits the spec key and clears the alias. Stripping at the read door empties that editor. Measured on the objectui#6519 branch, with
formulaadded toRETIRED_FIELD_KEYS:And objectui#6043 refused a blind rename in
plugin-designerprecisely because this linting editor exists to migrate the value properly — a strip at the read door would discard what that ruling preserved, on the next save, with no author-visible surface.The two options
A — strip
formulaat the read door (finishes objectui#6519's pattern). Every object saves again immediately. Cost: the authored expression text is dropped on the next save, silently; the inspector's migration path and its pin go with it.B — leave it, and make the blocked state actionable instead. Client validation already parses object drafts against the real
ObjectSchema(clientValidation.tsLOADERS), so the author can be told which field carries the key and pointed at the formula editor, where one edit migrates it. Cost: until they do that, the object stays unsaveable, and today the diagnostic does not say what to do about it.Recommendation: B, on all four axes
If the maintainer prefers A, the tombstone on
RETIRED_FIELD_KEYSand theObjectFieldInspectorpin must be updated in the same PR — they currently record the opposite.Refs: objectui#6519 (the strip set this came out of) - objectui#6043 (the refused rename) - objectui#4644 (the
indexedprecedent).Generated by Claude Code
Generated by Claude Code