Commit 74554a3
fix(spec): give the two declared-but-unauthorable metadata keys their form rows (#19182)
Fixes #19085
Clause-②: yes
Two keys the served schema **declares** had **no row** in the form
`METADATA_FORM_REGISTRY` supplies for their type, so the generic
metadata form never rendered them and an author's only door was the
Source tab's free-text JSON.
## Measured first, on `origin/main`
The card's reading was relayed from the objectui#9912 dev, so it was
re-taken here rather than trusted:
| probe | result |
|---|---|
| `relatedListFilter` / `validations` rows across every
`packages/spec/src/**/*.form.ts` | **0** |
| lit control — `maskingRule` | 2 hits (`field.form.ts`,
`object.form.ts`) |
| dark control — a name no form carries | 0 hits |
| `field.relatedListFilter` declared |
`FilterConditionSchema.optional()`, `field.zod.ts:1501` |
| `object.validations` declared |
`z.array(ValidationRuleSchema).optional()`, `object.zod.ts:2050` |
So the zero is a live probe, and both premises hold.
**Neither key is declared-but-dead**, so the ADR-0049 retirement fork
does not fire: the liveness ledger reads `object.validations` **`live`**
(`evaluateValidationRules`, the write-path evaluator), and
`field.relatedListFilter` `planned` + `authorWarn` — whose stated flip
condition (objectui#4664) **has since landed**; it is carded as #19187.
## The face each row gets is a measurement, not a preference
Both keys serve as JSON-Schema **pointer rows** — the shape the card
warned must not be assumed renderable.
- **`field.relatedListFilter` → `widget: 'filter-condition'`.** The
served node is `{ $ref: '#/$defs/…' }` onto the recursive Query-DSL
`FilterCondition`, whose derivation is `allOf: [open record, {
$and/$or/$not }]` with **no top-level `type`** — there is nothing for
the generic renderer to derive a control from. `filter-condition` names
the FilterCondition wire, and this file already uses it one section down
for `summaryOperations.filter`, the sibling `FilterConditionSchema` key.
What the hint renders as **today**, measured at the pinned
`.objectui-sha`, is the announced **raw-JSON editor carrying the hint**
— not a criteria builder: the renderer that consumes this registry is
the metadata-admin `SchemaForm`, whose own `WIDGETS` map registers no
`filter-condition` (the `FilterConditionField` of that name lives in
`@object-ui/fields`, on the ComponentRegistry path `ObjectForm` uses),
and with the pointer unresolved neither structural fallback applies, so
`resolveFieldFace` lands on `{ kind: 'raw-json', hint }` — the same face
`summaryOperations.filter` gets. That editor hands `JSON.parse` output
through verbatim and the save door judges it, so the wire is exact
either way; the hint is the forward-looking half. ⛔ Deliberately **not**
`filter-builder`: that widget consumes a rule **ARRAY** (what
`view.filter`, `dataset.filter` and `page.filterBy` store), so routing
this key there would write metadata the runtime refuses — the authoring
trap this row exists to close, re-created one layer up. `visibleWhen`
mirrors the key's own contract text (`lookup` / `master_detail`), a
meaningfulness gate rather than a parse gate: `FieldSchema` accepts the
key on every type, but the related-list derivation only ever reads it on
the child-side FK.
⛔ **Deliberately not `filter-builder`** — the boundary the card and the
triage both drew. That widget consumes a rule **ARRAY** (what
`view.filter`, `dataset.filter` and `page.filterBy` store); routing this
key there would write metadata the runtime refuses, re-creating the trap
one layer up.
`visibleWhen` mirrors the key's own contract text ("meaningful on a
child's `master_detail`/`lookup` field"): a **meaningfulness** gate, not
a parse gate — `FieldSchema` accepts the key on every type, but the
related-list derivation only reads it on the child-side FK, and offering
a knob the runtime does not deliver is what Prime Directive #10 forbids.
- **`object.validations` → `widget: 'json'`.** The served node is an
array whose items are a **double-hop** pointer (`items.$ref` →
`$defs/__schema1` → `$defs/__schema2`) landing on a `oneOf` over the six
`ValidationRule` members. A repeater would have to resolve both hops
**and** pick a union branch before it could render a row; neither half
is measured for this node, and a repeater that resolves neither renders
an empty row whose values never land — the offer-vs-door defect the
reconciliation gate beside it exists to catch. The Zod parse still
refuses a malformed rule loudly at publish. Precisely: `json` is in that
renderer's passthrough set, but the set is consulted **after** the
structural fallbacks, not instead of them — so this row reaches the
raw-JSON editor because the unresolved double-hop pointer derives
nothing, not because the hint suppresses derivation. Once the pin moves
past objectui's pointer resolution the same hint derives an
`object-rows` repeater over the first `oneOf` branch; that is the
renderer's precedence, not this repo's contract. Same treatment as the
sibling structured-array rows `permission.rowLevelSecurity` and
`email_template.variables`. Upgrading it to a structured control is a
form-face addition, ⛔ not a reconciliation.
⛔ No new form primitive was invented. Upgrading this to a structured
control is a form-face addition — the same boundary the reconciliation
ledger draws for `lifecycle.*.onlyWhen`.
## Why nothing failed while both keys sat unauthorable
`metadata-form-zod-reconciliation.test.ts` reconciles the two directions
asymmetrically, and only one of them at the top level:
| direction | top level | nested lists |
|---|---|---|
| form-only (offered, not declared) | checked | checked |
| retired (offered, tombstoned) | checked | checked |
| zod-only (declared, not offered) | **unchecked** | checked
(ledgerable) |
This card's class is exactly the unchecked cell. Closing it generally is
**not** in this PR: measured on this tree, the top-level zod-only set is
**276 keys across the 17 forms**, each needing an offer or a ledgered
reason. That census is filed as a finding; the new pin holds the two
keys #19085 measured in the meantime.
## Tests
`metadata-form-declared-rows.pin.test.ts` pins both rows, both faces and
the `visibleWhen`, and adds a registry-wide assertion — every row of
every form, at every depth — that **no** form routes a
`FilterCondition`-typed key to the rule-array builder. The negative half
is written so it can fail: a lit control asserts the walk actually
reaches both `field.relatedListFilter` and
`field.summaryOperations.filter` before the misrouted set is reported
empty.
⛔ Not measured here: the **browser** half. At the pinned `.objectui-sha`
the face is the announced **raw-JSON editor carrying the hint**
(`resolveFieldFace` → `{ kind: 'raw-json', hint }`), so what is
unmeasured is whether that editor renders the hint as announced — ⛔ not
a criteria builder's field picker, which is a different renderer on a
different path and is not reached from this registry today. The picker
question becomes live only once the pin moves past objectui's pointer
resolution; whoever does that bump owns re-measuring this row and its
two shape-siblings, `permission.rowLevelSecurity` and
`email_template.variables`.
## Acceptance notes
- `METADATA_FORM_REGISTRY` itself is a pure import map — it declares no
rows. The rows live in `packages/spec/src/data/{field,object}.form.ts`,
so the landing file named at dispatch could not carry this change; the
registry file is unmodified.
- The regenerated `platform-objects` metadata-form bundles are the
mechanical consequence of two new rows (`os i18n extract` walks the
registry): 44 additive lines, translated locales filled with source text
and still needing translation.
- Noted, not filed: `relatedList`, `relatedListTitle` and
`relatedListColumns` — the three siblings of `relatedListFilter` — are
also declared and unoffered. They are part of the 276-key census above,
not separate cards.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_
---
1 parent 564ac2f commit 74554a3
12 files changed
Lines changed: 328 additions & 1 deletion
File tree
- .changeset
- packages
- lint/src
- platform-objects/src/apps/translations
- spec/src
- data
- system
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
556 | 556 | | |
557 | 557 | | |
558 | 558 | | |
559 | | - | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
560 | 575 | | |
561 | 576 | | |
562 | 577 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
249 | 249 | | |
250 | 250 | | |
251 | 251 | | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
252 | 256 | | |
253 | 257 | | |
254 | 258 | | |
| |||
424 | 428 | | |
425 | 429 | | |
426 | 430 | | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
427 | 435 | | |
428 | 436 | | |
429 | 437 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
249 | 249 | | |
250 | 250 | | |
251 | 251 | | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
252 | 256 | | |
253 | 257 | | |
254 | 258 | | |
| |||
424 | 428 | | |
425 | 429 | | |
426 | 430 | | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
427 | 435 | | |
428 | 436 | | |
429 | 437 | | |
| |||
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| 69 | + | |
| 70 | + | |
69 | 71 | | |
70 | 72 | | |
71 | 73 | | |
| |||
161 | 163 | | |
162 | 164 | | |
163 | 165 | | |
| 166 | + | |
| 167 | + | |
164 | 168 | | |
165 | 169 | | |
166 | 170 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
249 | 249 | | |
250 | 250 | | |
251 | 251 | | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
252 | 256 | | |
253 | 257 | | |
254 | 258 | | |
| |||
424 | 428 | | |
425 | 429 | | |
426 | 430 | | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
427 | 435 | | |
428 | 436 | | |
429 | 437 | | |
| |||
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| 69 | + | |
| 70 | + | |
69 | 71 | | |
70 | 72 | | |
71 | 73 | | |
| |||
161 | 163 | | |
162 | 164 | | |
163 | 165 | | |
| 166 | + | |
| 167 | + | |
164 | 168 | | |
165 | 169 | | |
166 | 170 | | |
| |||
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
249 | 249 | | |
250 | 250 | | |
251 | 251 | | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
252 | 256 | | |
253 | 257 | | |
254 | 258 | | |
| |||
424 | 428 | | |
425 | 429 | | |
426 | 430 | | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
427 | 435 | | |
428 | 436 | | |
429 | 437 | | |
| |||
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
| 68 | + | |
| 69 | + | |
68 | 70 | | |
69 | 71 | | |
70 | 72 | | |
| |||
128 | 130 | | |
129 | 131 | | |
130 | 132 | | |
| 133 | + | |
| 134 | + | |
131 | 135 | | |
132 | 136 | | |
133 | 137 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
105 | 148 | | |
106 | 149 | | |
107 | 150 | | |
| |||
0 commit comments