Skip to content

Object payloads declare and write enabled, which ObjectSchema refuses by name — the soft-delete path is what puts it on the wire #6238

Description

@yinlianghui

Surfaced by the object-level oracle added to scripts/check-designer-field-key-parity.mjs in objectui#6223. Filed, not fixed — objectui#6223 is scoped to the three keys its own card names (group, sortOrder, relationships), and this one is neither a rename nor the same resolution, so it is ledgered there and adjudicated here.

The measurement

Measured against the installed @objectstack/spec 17.2.0, ESM build (dist/data/index.mjs), on origin/main @ 0409b766d:

const base = { name: 'account', label: 'Account', fields: { n: { type: 'text', label: 'N' } } };
ObjectSchema.safeParse(base) => success = true (control)
ObjectSchema.safeParse({ ...base, isSystem: true }) => success = true (control)
ObjectSchema.safeParse({ ...base, enabled: true }) => success = false
unrecognized_keys keys=["enabled"]

ObjectSchema's accept set is 42 keys and does not contain enabled.

Why the near-spelling is a trap, not the answer

The accept set does contain enable — but it is not a boolean on/off flag:

// @objectstack/spec src/data/object.zod.tsenable: ObjectCapabilities.optional().describe('Enabled system features modules'),

enable is a capabilities module object. So enabled: falseenable: false is not a rename; it fails on the value even where it passes on the name (ObjectSchema.safeParse({ ...base, enable: true }) is also success = false, with a value-level issue rather than unrecognized_keys). What a soft delete should actually write is the open question here.

The writers

packages/app-shell/src/services/MetadataService.ts:

  • ObjectMetadataPayloaddeclaresenabled?: boolean — this is the declaration the parity gate reads. toObjectPayload does not populate it, so the declaration alone is latent.
  • deleteObject (line 235) and deleteMetadataItem (line 206) write the tombstone body directly, bypassing toObjectPayload:
    awaitclient.meta.saveItem('object',objectName,{name: objectName,enabled: false,_deleted: true});
    That is a literal object, not the declared shape, so it is outside the gate's static reach (coverage note 1) — but it is the path that is actually live: deleting an object through the designer PUTs enabled and _deleted, both refused by name.

So the declaration is latent and the soft-delete is live, which is the reverse of the usual shape and worth stating plainly.

Not a mechanical resolution

Three things have to be settled together, and none of them is a spelling:

  1. Whether the metadata API has a soft-delete convention at all, and if so which keys express it (_deleted is refused by name too — measure it).
  2. Whether enabled on ObjectMetadataPayload should be deleted outright (the objectui#4687 shape, since toObjectPayload never populates it) independently of what the delete path does.
  3. Whether object deletion should go through client.reset('object', name) — which MetadataObjectsPage.handleObjectsChange already uses for deletes — rather than a tombstone PUT. Two delete mechanisms exist in the repo today and they disagree.

If the answer needs a spec-side addition, that is a fork: @objectstack/spec is another repository's surface and a maintainer decision.

Ledger

Recorded in KNOWN_UNPARSEABLE_KEYS in scripts/check-designer-field-key-parity.mjs against this card. The ledger ratchets in both directions, so the entry must be removed (not edited) when this is resolved.

Refs: #6223 (the object-level oracle) · #5761 (the gate) · #6043 · #6045 · #4687

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions