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: false → enable: 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:
- 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). - Whether
enabled on ObjectMetadataPayload should be deleted outright (the objectui#4687 shape, since toObjectPayload never populates it) independently of what the delete path does. - 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
Surfaced by the object-level oracle added to
scripts/check-designer-field-key-parity.mjsin 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/spec17.2.0, ESM build (dist/data/index.mjs), onorigin/main@0409b766d:ObjectSchema's accept set is 42 keys and does not containenabled.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:enableis a capabilities module object. Soenabled: false→enable: falseis not a rename; it fails on the value even where it passes on the name (ObjectSchema.safeParse({ ...base, enable: true })is alsosuccess = false, with a value-level issue rather thanunrecognized_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.toObjectPayloaddoes not populate it, so the declaration alone is latent.deleteObject(line 235) anddeleteMetadataItem(line 206) write the tombstone body directly, bypassingtoObjectPayload:enabledand_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:
_deletedis refused by name too — measure it).enabledonObjectMetadataPayloadshould be deleted outright (the objectui#4687 shape, sincetoObjectPayloadnever populates it) independently of what the delete path does.client.reset('object', name)— whichMetadataObjectsPage.handleObjectsChangealready 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/specis another repository's surface and a maintainer decision.Ledger
Recorded in
KNOWN_UNPARSEABLE_KEYSinscripts/check-designer-field-key-parity.mjsagainst 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