@@ -331,7 +331,13 @@ export function validateTree(tree: SchemaElement | null, manifest: Manifest): Va
331331 * down over the member kind `of` declares). The two copies must agree on the accepted
332332 * grammar AND on diagnostic codes/severities — if they drift, the save gate
333333 * and the renderer speak different dialects. Change these functions only
334- * together with the objectui copy. */
334+ * together with the objectui copy.
335+ *
336+ * Known lead: this copy grades every `type-mismatch` and `member-type-mismatch`
337+ * `error` (a literal's coarse type is certain; see `checkType`). objectui's
338+ * copy, `packages/sdui-parser/src/validate.ts` at the `.objectui-sha` pin,
339+ * still grades them `error` only when an `enum` arm is present. Codes and
340+ * messages agree; the severity differs until objectui ports the same rule. */
335341
336342/** The values an `enum` arm admits, flattened from either declaration form. */
337343const enumValues = ( input : ManifestInput ) : unknown [ ] =>
@@ -417,10 +423,14 @@ function memberEntries(value: unknown): Array<[string, unknown]> | null {
417423 * made once, and N copies of it is the noise this repo treats as the thing that
418424 * trains authors to dismiss real reports.
419425 *
420- * Severity mirrors `checkType`'s rule for the same reason — `error` when an
421- * `enum` arm is present, because a closed list is the one fact this layer can
422- * be certain about; `warning` otherwise, since the coarse kind is a KIND claim
423- * and `os validate` / `os build` remain the judge of values.
426+ * Severity mirrors `checkType`'s rule for the same reason, and is therefore
427+ * always `error`. Every member reaching this function belongs to a container
428+ * the parser materialized WHOLE: `interpretBrace` either reads the entire
429+ * braced value as a literal or keeps the entire value as one `{ $expr }`
430+ * marker (`["a", foo]` is one marker, never an array holding one), and a
431+ * marker never gets here. So a member is a literal too, its coarse kind is
432+ * final at compile time, and a member no declared arm accepts is as certain a
433+ * mismatch as a prop no arm accepts.
424434 */
425435function checkMemberTypes ( tag : string , input : ManifestInput , value : unknown ) : Diagnostic | null {
426436 const arms = inputTypeArms ( input . of ) ;
@@ -433,7 +443,7 @@ function checkMemberTypes(tag: string, input: ManifestInput, value: unknown): Di
433443 if ( offenders . length === 0 ) return null ;
434444 const expectation = arms . map ( ( arm ) => armExpectation ( arm , input ) ) . join ( ' or ' ) ;
435445 return {
436- severity : arms . includes ( 'enum' ) ? ' error' : 'warning ',
446+ severity : ' error',
437447 code : 'member-type-mismatch' ,
438448 message : `<${ tag } > prop "${ input . name } " expected every member to be ${ expectation } ` +
439449 ` — ${ offenders . map ( ( [ position ] ) => `[${ position } ]` ) . join ( ', ' ) } ` +
@@ -454,16 +464,29 @@ function checkMemberTypes(tag: string, input: ManifestInput, value: unknown): Di
454464 * as legal, it does not turn the check off. Two properties of the reporting are
455465 * deliberate:
456466 *
457- * - A single-arm input produces the byte-identical diagnostic it always did,
458- * `invalid-enum` included. This change adds a form; it does not restate the
459- * old one.
460- * - A multi-arm input produces ONE diagnostic naming every arm, at the
461- * STRICTEST arm's severity — `error` when an `enum` arm is present, because
462- * an enum's closed list is the one fact this layer can be certain about, and
463- * a value outside it should not become dismissible merely because a second
464- * arm was added next to it. Its code is `type-mismatch` (not `invalid-enum`)
465- * since the reported fact is "fits none of the declared arms", and the
466- * message carries the allowed values so the author still sees the list.
467+ * - A single-arm input produces the code and message it always did, and a
468+ * single `enum` arm's `invalid-enum` is byte-identical, severity included.
469+ * The union form adds a form; it does not restate the old one.
470+ * - A multi-arm input produces ONE diagnostic naming every arm. Its code is
471+ * `type-mismatch` (not `invalid-enum`) since the reported fact is "fits
472+ * none of the declared arms", and the message carries the allowed values so
473+ * the author still sees the list.
474+ *
475+ * Every `type-mismatch` is an `error`, because this layer reports only what it
476+ * can be certain about, and there are two certain facts here:
477+ *
478+ * 1. An `enum` arm's closed list. A value outside it should not become
479+ * dismissible merely because a second arm was added next to it.
480+ * 2. A LITERAL's coarse type. Only literals reach this function: a braced
481+ * value the parser could not materialize is the deferred `{ $expr }`
482+ * marker, which `validateTree` diverts to `inert-expression` (a warning)
483+ * before calling here. A quoted attribute is a string, a bare attribute is
484+ * `true`, and a materialized brace is exactly the literal written. So
485+ * `aggregate="count"` against an input declared `object` is final at
486+ * compile time: no expression stands between the source and the renderer,
487+ * and the tile that reads `aggregate.function` receives a string and
488+ * draws nothing. A warning there let `os build` stay green on a page that
489+ * renders nothing.
467490 */
468491function checkType ( tag : string , input : ManifestInput , value : unknown ) : Diagnostic | null {
469492 const arms = inputTypeArms ( input . type ) ;
@@ -480,7 +503,7 @@ function checkType(tag: string, input: ManifestInput, value: unknown): Diagnosti
480503 }
481504
482505 return {
483- severity : arms . includes ( 'enum' ) ? ' error' : 'warning ',
506+ severity : ' error',
484507 code : 'type-mismatch' ,
485508 message : `<${ tag } > prop "${ input . name } " expected ${ arms
486509 . map ( ( arm ) => armExpectation ( arm , input ) )
0 commit comments