Surfaced by the census in objectui#6528, deliberately left out of that PR's scope.
objectui#6528 narrowed both relationship-target resolvers to the single spec SPELLING reference. It did not touch the CARRIER — the shape the value may take — because that is a separate axis and its own census was not in the charter. Measuring it turns up the same class of unfounded leniency.
Measurement
@objectstack/spec 17.2.0, ObjectSchema.safeParse, one lookup field, three carriers for the canonical reference key:
| carrier |
verdict |
reference: 'crm_account' |
ACCEPTED (positive control — parses green, value survives) |
reference: ['crm_account'] |
REFUSED — invalid_type: Invalid input: expected string, received array |
reference: { object: 'crm_account' } |
REFUSED — invalid_type: Invalid input: expected string, received object |
FieldSchema.reference is a plain string. Both resolvers nonetheless still branch on all three:
packages/core/src/utils/chart-series.ts — resolveRelationshipTarget
packages/app-shell/src/views/metadata-admin/inspectors/useDatasetFields.ts — resolveReferenceTo
if (typeof raw === 'string' && raw) return raw;
if (Array.isArray(raw) && typeof raw[0] === 'string') return raw[0];
if (raw && typeof raw === 'object') { /* { object } */ }
The array branch additionally makes a silent product decision: given a multi-target value it returns raw[0] and discards the rest.
Why it was not cut in #6528
Scope discipline, and one unfinished measurement. The spelling census had a clean population count behind it (445 of 565 lookup / master_detail defs in the framework tree spell the key reference); the carrier axis does not yet. A grep for producers emitting an array carrier returned zero in both trees, but that zero has not been run down to the same standard — in particular whether any polymorphic / multi-target lookup is intended to carry a list. Cutting on an unfinished census is exactly what #6528 was filed to stop.
The question to settle
Same one AGENTS.md #0.1 asks: a lenient consumer is where a wrong producer hides. If nothing produces an array or { object } carrier, both branches are dead code that reads like a measurement and should go, in both files in one pass (they must not diverge). If something DOES produce one, that is a producer-side defect or a spec question about multi-target lookups — fix it there, not by keeping the tolerant read.
Whoever takes this: the refusal pins added by objectui#6528 (refuses the legacy spelling ...) are the pattern to extend, and the two carrier assertions currently live in useDatasetFields.test.ts and chart-series.test.ts as green pins that would need the same triage — updated with the helper, not preserved as cover.
Surfaced by the census in objectui#6528, deliberately left out of that PR's scope.
objectui#6528 narrowed both relationship-target resolvers to the single spec SPELLING
reference. It did not touch the CARRIER — the shape the value may take — because that is a separate axis and its own census was not in the charter. Measuring it turns up the same class of unfounded leniency.Measurement
@objectstack/spec17.2.0,ObjectSchema.safeParse, one lookup field, three carriers for the canonicalreferencekey:reference: 'crm_account'reference: ['crm_account']invalid_type: Invalid input: expected string, received arrayreference: { object: 'crm_account' }invalid_type: Invalid input: expected string, received objectFieldSchema.referenceis a plain string. Both resolvers nonetheless still branch on all three:packages/core/src/utils/chart-series.ts—resolveRelationshipTargetpackages/app-shell/src/views/metadata-admin/inspectors/useDatasetFields.ts—resolveReferenceToThe array branch additionally makes a silent product decision: given a multi-target value it returns
raw[0]and discards the rest.Why it was not cut in #6528
Scope discipline, and one unfinished measurement. The spelling census had a clean population count behind it (445 of 565 lookup / master_detail defs in the framework tree spell the key
reference); the carrier axis does not yet. A grep for producers emitting an array carrier returned zero in both trees, but that zero has not been run down to the same standard — in particular whether any polymorphic / multi-target lookup is intended to carry a list. Cutting on an unfinished census is exactly what #6528 was filed to stop.The question to settle
Same one AGENTS.md #0.1 asks: a lenient consumer is where a wrong producer hides. If nothing produces an array or
{ object }carrier, both branches are dead code that reads like a measurement and should go, in both files in one pass (they must not diverge). If something DOES produce one, that is a producer-side defect or a spec question about multi-target lookups — fix it there, not by keeping the tolerant read.Whoever takes this: the refusal pins added by objectui#6528 (
refuses the legacy spelling ...) are the pattern to extend, and the two carrier assertions currently live inuseDatasetFields.test.tsandchart-series.test.tsas green pins that would need the same triage — updated with the helper, not preserved as cover.