Skip to content

spec: retiredKey() tombstones are emitted into a repeater row's items.properties, so the served schema offers six columns the parse door refuses #17502

Description

@os-bill

Found while surveying #17232 (PR #17500), deriving the row schema of every type: 'repeater' form field. Out of that card's scope, which is about MISSING titles on live keys.

Same offer-vs-door family as #5280 (app's metadata form offered three retiredKey()-retired keys), one layer down: #5280 was the form's DECLARED field list, this is the JSON-Schema-derived row of a repeater table, which the form layout does not enumerate at all.

The measured half — the served schema carries a tombstone as a row property

retiredKey() (packages/spec/src/shared/retired-key.ts) is z.never({ error: () => guidance }).optional().describe('[REMOVED] ' + guidance). Authoring the key is a hard parse rejection.

z.toJSONSchema still emits a property node for it, and it lands in the repeater's items.properties. Measured on origin/main at e758131b39, flow.nodes:

items.properties keys = id, type, label, config, connectorConfig, position,
                        timeoutMs, inputSchema, outputSchema, waitEventConfig,
                        boundaryConfig
outputSchema.description = "[REMOVED] `flow.nodes[].outputSchema` was removed in
                            @objectstack/spec 17.0.0 (audit close-out) ..."

Lit control: inputSchema, the live sibling one line above it, is present and carries no [REMOVED] prefix. Dark control: a key that was never declared at all is absent from the map.

dashboard.widgets carries five more of them, so six tombstones sit in repeater rows across the registry today.

The unmeasured half, named as unmeasured

Whether Studio paints a column header for a { "not": {} } node is an objectui question and was NOT measured here — this session has no console to drive. The console reads items.properties[k].title ?? k to build the header row, and a tombstone is a member of items.properties, so the offer exists in the payload; what the renderer does with it is the half still owed. Both readings are worth having before anything is changed: if it does render, an author is invited to fill a key that is refused at publish; if it does not, the exclusion still belongs in the served schema rather than in each renderer.

Why the spec side is the place to look first

packages/spec/src/kernel/repeater-item-titles.test.ts (landing in PR #17500) already has to exclude these by hand — it filters on the [REMOVED] description prefix — precisely so it does not demand an authoring title for a key the parse door refuses. That exclusion is a local workaround for a payload that should arguably not carry the node at all. One place to decide it, not one per consumer.

Carrier: packages/spec/src/shared/retired-key.ts and packages/metadata-protocol/src/protocol.ts (toJsonSchemaSafe), with the render half in objectui.

Back-links: #17232, PR #17500, #5280.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions