Skip to content

Commit ce97faa

Browse files
committed
docs(spec,lint): record the intentional code divergence between the two name-like sets (#6734)
Two sets answer "is this field name record-title-ish", and they differ by exactly one entry, `code`: packages/lint/src/data-model-rules.ts NAME_LIKE_FIELDS — has 'code' packages/spec/src/data/display-name.ts NAME_ISH_EXACT — does not Per the maintainer's 2026-08-10 ruling on #6734 ("document the gap, no behavior change"), both sets stay exactly as they are. What was defective was that the divergence was invisible, not that it exists — so it is now written down where both sets are defined, each pointing at the other: - lint R9 (`object/missing-name-field`) asks the looser "will records be anonymous?" question — any readable face counts, so `code` counts; - ADR-0079 derivation (`resolveDisplayField`) asks "what IS the title?" — an identifier is not a title, so `code` is deliberately absent from tier 1/2 and a `code`-only object derives at tier 3 instead. `skills/objectstack-data/SKILL.md` taught the lint spelling as the contract, so the drift was being taught; it gains the same distinction note. Nothing user-visible turns on the gap (R9 is `severity: 'suggestion'` and the `Record #<id>` floor guarantees a title regardless). ZERO behavior change: the diff is comments and prose only — no code path, no test file, no generated output moves. `docs/adr/**` is deliberately untouched: ADR-0079 records this as its open question 4, and closing that ledger entry is a maintainer-merged ADR edit. Closes #6734
1 parent f188ed6 commit ce97faa

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

packages/lint/src/data-model-rules.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,32 @@ const NUMERIC_TYPES = new Set([
3333
'number', 'currency', 'integer', 'decimal', 'percent', 'float', 'double',
3434
]);
3535
const OPTION_FIELD_TYPES = new Set(['select', 'multiselect', 'radio', 'enum']);
36+
/**
37+
* Field names that give an object a title FACE, for R9
38+
* (`object/missing-name-field`).
39+
*
40+
* DIVERGES from spec's `NAME_ISH_EXACT`
41+
* (`packages/spec/src/data/display-name.ts`) by exactly one entry: `code`. That
42+
* difference is INTENTIONAL — the maintainer ruled on 2026-08-10 (#6734) to
43+
* keep both sets as they are and write the gap down in both places rather than
44+
* converge them. The two sets answer different questions:
45+
*
46+
* - R9 asks the LOOSER **"will records be anonymous?"** — is there anything
47+
* here a human could read instead of a raw id? A `code` clears that bar, so
48+
* it counts as a title face and is listed below.
49+
* - ADR-0079 derivation (`resolveDisplayField`) asks **"what IS the title?"**
50+
* — which field to PICK as the primary. A `code` is an identifier, not a
51+
* title, so spec deliberately omits it from tier 1 (name-ish exact) and
52+
* tier 2 (name-ish affix). A `code`-only object can still be derived at
53+
* tier 3 ("first title-eligible field by declaration order") — by a
54+
* different rule and a different priority, so the two are not equivalent
55+
* even where they agree on the outcome.
56+
*
57+
* Nothing user-visible turns on the gap: R9 is `severity: 'suggestion'` and
58+
* ADR-0079's `Record #<id>` floor means no object ships without a title either
59+
* way. Do not "fix" either set into the other without a ruling that supersedes
60+
* the one above.
61+
*/
3662
const NAME_LIKE_FIELDS = ['name', 'title', 'subject', 'label', 'full_name', 'display_name', 'code'];
3763

3864
/** Child object names that read as line-items / composition (entered with the parent). */

packages/spec/src/data/display-name.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,30 @@ export function isTitleEligible(fieldDef: TitleEligibleFieldDef | undefined | nu
130130
return TITLE_ELIGIBLE_TYPES.has(type);
131131
}
132132

133-
/** Exact name-ish field names (case-insensitive), highest derivation priority. */
133+
/**
134+
* Exact name-ish field names (case-insensitive), highest derivation priority.
135+
*
136+
* DIVERGES from lint's `NAME_LIKE_FIELDS`
137+
* (`packages/lint/src/data-model-rules.ts`) by exactly one entry: `code`. That
138+
* difference is INTENTIONAL — the two sets answer different questions, and the
139+
* maintainer ruled on 2026-08-10 (#6734) to keep both as they are and write the
140+
* gap down in both places rather than converge them.
141+
*
142+
* This set answers **"what is the record's title?"** — it is tier 1 of
143+
* {@link resolveDisplayField}'s derivation, i.e. the names whose presence is
144+
* strong enough evidence to pick that field as the primary title outright. A
145+
* `code` is an identifier, not a title, so it is deliberately absent here; a
146+
* `code`-only object still gets a title, but at tier 3 ("first title-eligible
147+
* field by declaration order") — by a different rule and a different priority.
148+
*
149+
* Lint R9 (`object/missing-name-field`) asks the looser **"will records be
150+
* anonymous?"** question — does this object have any title FACE at all — and
151+
* for that a `code` counts, which is why its set carries the extra entry. R9 is
152+
* `severity: 'suggestion'` and the `Record #<id>` floor below guarantees a
153+
* title regardless, so the two sets never disagree about an outcome a user
154+
* sees. Do not "fix" either set into the other without a ruling that supersedes
155+
* the one above.
156+
*/
134157
const NAME_ISH_EXACT: ReadonlySet<string> = new Set([
135158
'name', 'title', 'subject', 'label', 'full_name', 'display_name',
136159
]);

skills/objectstack-data/SKILL.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,20 @@ Data-model rules (in addition to naming/label/i18n):
11031103
| `field/select-missing-options` | warning | a `select`/`multiselect`/`radio` with no `options` (or options source) |
11041104
| `object/missing-name-field` | suggestion | an object with no `nameField` (ADR-0079's canonical title pointer) and no name-like field (`name`/`title`/`subject`/`label`/`full_name`/`display_name`/`code`) |
11051105

1106+
> **`code` counts for R9, but is NOT a title-derivation key.** R9's name-like
1107+
> list above is the *looser* of two "name-like" sets, and the difference is
1108+
> deliberate. R9 asks **"will records be anonymous?"** — is there any readable
1109+
> face at all — and a `code` clears that bar. ADR-0079's title derivation
1110+
> (`resolveDisplayField`) asks the narrower **"what IS the title?"**, and its
1111+
> name-ish set is `name`/`title`/`subject`/`label`/`full_name`/`display_name`
1112+
> **without `code`** — an identifier is not a title. So an object whose only
1113+
> name-ish field is `code` is R9-clean, yet its title is derived by the
1114+
> lower-priority "first title-eligible field by declaration order" tier rather
1115+
> than by name. Nothing user-visible turns on this (R9 is `suggestion`, and the
1116+
> `Record #<id>` floor guarantees a title regardless), but do not read the R9
1117+
> list as the derivation contract — set `nameField` explicitly when the title
1118+
> matters.
1119+
11061120
These same rules are the **rubric for AI-generated metadata** — a generation is
11071121
"good" exactly when it is schema-valid and lint-clean:
11081122

0 commit comments

Comments
 (0)