Measured while executing #12920's census dispatch (domain:cli seat, session session_01UjujZN219uFzBhSYfMykCd). Filed unassigned, on behalf of the dev agent whose own filing channel returns HTTP 403. Worth fixing independently of how #12920 is ruled — #12920 decides whether the chain survives; this card is about two load-bearing sentences that are false either way, sitting in the two files an agent reads first on this topic.
Re-verified by me against origin/main at adf70f763 before filing.
Site 1 — packages/rest/src/rest-server.ts:8846-8857
// [#7486] `reference` FIRST — it is the canonical
// key on `FieldSchema`, and `data/field.zod.ts`
// folds `relatedTo` / `referenceTo` / `target` /
// `targetObject` / `lookupObject` all onto it at
// parse. […] The legacy
// spellings stay after it for stored pre-fold rows,
// which never went through the alias table.
data/field.zod.ts does not fold them. It refuses them. The table at packages/spec/src/data/field.zod.ts:772 —
relatedTo: 'reference',referenceTo: 'reference',target: 'reference',targetObject: 'reference',lookupObject: 'reference',
— is not a normaliser. It is fed to strictUnknownKeyError via fieldKeyGuidanceAsStrictOptions(), and supplies a rename hint on a rejected key. FieldSchema is z.object(shape).strict() plus an error map.
Driven against the BUILT packages/spec/dist/data/index.mjs, with a three-level control that separates accept / alias-refusal / unknown-key-refusal:
| input | result |
|---|
reference: 'sys_user' (positive control) | ACCEPT — parses to .reference = 'sys_user' |
zzz_not_a_key (negative control) | REFUSE unrecognized_keys, no rename hint |
| all five aliases | REFUSE unrecognized_keys, with a rename hint (Did you mean …) |
The three-level control is what makes this a measurement rather than a blanket deny: the instrument distinguishes accept from refuse and alias-refusal from unknown-key-refusal. Same result via ObjectSchema on a whole object-metadata document.
Site 2 — packages/rest/src/public-form-lookup-picker.test.ts:359-361
The guard test's rationale — "Stored pre-fold rows never went through the alias table, so the legacy spellings are live data, not history" — rests on the same misreading. There was never a fold for a row to be "pre-" of. ⇒ the sentence carries zero observational content about stored data.
⚠️ That matters beyond tidiness: #12920's card cites this test as the strongest surviving evidence that stored legacy rows exist.
⭐ A third, smaller divergence found in the same read
The comment names five aliases; the chain immediately below reads four members, only two of which are on that list:
referenceTo=def?.reference??def?.referenceTo??def?.target??def?.options?.objectName;
relatedTo, targetObject and lookupObject are named by the comment and read by nothing; options.objectName is read and named by nothing. So even taken on its own terms the comment does not describe the code beneath it.
⚠️ What this card does NOT claim
⛔ It does not claim the legacy chain is dead code. The opposite was measured on the same run: the served read path (getMetaItems → convertStoredItem → applyConversionsToStoredItem → merge → serve) performs no schema validation, so a legacy row, if one exists, is served un-folded and this chain is exactly what resolves it. Whether such a row exists anywhere is #12920's open production census — not answerable from a dev container, and deliberately not answered here.
⛔ It also does not propose changing the chain. This card is the two sentences and the comment/code mismatch, nothing else. Whoever rules #12920 decides the chain.
Why it is worth its own card
The natural next action for an agent that reads rest-server.ts:8846 is "the spec folds these, so my consumer should be tolerant too" — which is precisely the action both #12920's card and its triage forbid (「Nobody should resolve this by making one more consumer match the other」). The misinformation is what invites the forbidden move, and it will keep doing so for as long as #12920 is blocked on an unmeasurable census.
Related: #12920 (the chain itself), #11567 / #12252 (the same rejected spelling on the SQL side), #13053 (a fixture spelling a key ObjectSchema refuses).
Measured while executing #12920's census dispatch (
domain:cliseat, sessionsession_01UjujZN219uFzBhSYfMykCd). Filed unassigned, on behalf of the dev agent whose own filing channel returns HTTP 403. Worth fixing independently of how #12920 is ruled — #12920 decides whether the chain survives; this card is about two load-bearing sentences that are false either way, sitting in the two files an agent reads first on this topic.Re-verified by me against
origin/mainatadf70f763before filing.Site 1 —
packages/rest/src/rest-server.ts:8846-8857data/field.zod.tsdoes not fold them. It refuses them. The table atpackages/spec/src/data/field.zod.ts:772—— is not a normaliser. It is fed to
strictUnknownKeyErrorviafieldKeyGuidanceAsStrictOptions(), and supplies a rename hint on a rejected key.FieldSchemaisz.object(shape).strict()plus an error map.Driven against the BUILT
packages/spec/dist/data/index.mjs, with a three-level control that separates accept / alias-refusal / unknown-key-refusal:reference: 'sys_user'(positive control).reference = 'sys_user'zzz_not_a_key(negative control)unrecognized_keys, no rename hintunrecognized_keys, with a rename hint (Did you mean …)The three-level control is what makes this a measurement rather than a blanket deny: the instrument distinguishes accept from refuse and alias-refusal from unknown-key-refusal. Same result via
ObjectSchemaon a whole object-metadata document.Site 2 —
packages/rest/src/public-form-lookup-picker.test.ts:359-361The guard test's rationale — "Stored pre-fold rows never went through the alias table, so the legacy spellings are live data, not history" — rests on the same misreading. There was never a fold for a row to be "pre-" of. ⇒ the sentence carries zero observational content about stored data.
⭐ A third, smaller divergence found in the same read
The comment names five aliases; the chain immediately below reads four members, only two of which are on that list:
relatedTo,targetObjectandlookupObjectare named by the comment and read by nothing;options.objectNameis read and named by nothing. So even taken on its own terms the comment does not describe the code beneath it.⛔ It does not claim the legacy chain is dead code. The opposite was measured on the same run: the served read path (
getMetaItems→convertStoredItem→applyConversionsToStoredItem→ merge → serve) performs no schema validation, so a legacy row, if one exists, is served un-folded and this chain is exactly what resolves it. Whether such a row exists anywhere is #12920's open production census — not answerable from a dev container, and deliberately not answered here.⛔ It also does not propose changing the chain. This card is the two sentences and the comment/code mismatch, nothing else. Whoever rules #12920 decides the chain.
Why it is worth its own card
The natural next action for an agent that reads
rest-server.ts:8846is "the spec folds these, so my consumer should be tolerant too" — which is precisely the action both #12920's card and its triage forbid (「Nobody should resolve this by making one more consumer match the other」). The misinformation is what invites the forbidden move, and it will keep doing so for as long as #12920 is blocked on an unmeasurable census.Related: #12920 (the chain itself), #11567 / #12252 (the same rejected spelling on the SQL side), #13053 (a fixture spelling a key
ObjectSchemarefuses).