Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

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

/** Child object names that read as line-items / composition (entered with the parent). */
Expand Down
25 changes: 24 additions & 1 deletion packages/spec/src/data/display-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,30 @@ export function isTitleEligible(fieldDef: TitleEligibleFieldDef | undefined | nu
return TITLE_ELIGIBLE_TYPES.has(type);
}

/** Exact name-ish field names (case-insensitive), highest derivation priority. */
/**
* Exact name-ish field names (case-insensitive), highest derivation priority.
*
* DIVERGES from lint's `NAME_LIKE_FIELDS`
* (`packages/lint/src/data-model-rules.ts`) by exactly one entry: `code`. That
* difference is INTENTIONAL — the two sets answer different questions, and the
* maintainer ruled on 2026-08-10 (#6734) to keep both as they are and write the
* gap down in both places rather than converge them.
*
* This set answers **"what is the record's title?"** — it is tier 1 of
* {@link resolveDisplayField}'s derivation, i.e. the names whose presence is
* strong enough evidence to pick that field as the primary title outright. A
* `code` is an identifier, not a title, so it is deliberately absent here; a
* `code`-only object still gets a title, but at tier 3 ("first title-eligible
* field by declaration order") — by a different rule and a different priority.
*
* Lint R9 (`object/missing-name-field`) asks the looser **"will records be
* anonymous?"** question — does this object have any title FACE at all — and
* for that a `code` counts, which is why its set carries the extra entry. R9 is
* `severity: 'suggestion'` and the `Record #<id>` floor below guarantees a
* title regardless, so the two sets never disagree about an outcome a user
* sees. Do not "fix" either set into the other without a ruling that supersedes
* the one above.
*/
const NAME_ISH_EXACT: ReadonlySet<string> = new Set([
'name', 'title', 'subject', 'label', 'full_name', 'display_name',
]);
Expand Down
14 changes: 14 additions & 0 deletions skills/objectstack-data/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,20 @@ Data-model rules (in addition to naming/label/i18n):
| `field/select-missing-options` | warning | a `select`/`multiselect`/`radio` with no `options` (or options source) |
| `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`) |

> **`code` counts for R9, but is NOT a title-derivation key.** R9's name-like
> list above is the *looser* of two "name-like" sets, and the difference is
> deliberate. R9 asks **"will records be anonymous?"** — is there any readable
> face at all — and a `code` clears that bar. ADR-0079's title derivation
> (`resolveDisplayField`) asks the narrower **"what IS the title?"**, and its
> name-ish set is `name`/`title`/`subject`/`label`/`full_name`/`display_name`
> **without `code`** — an identifier is not a title. So an object whose only
> name-ish field is `code` is R9-clean, yet its title is derived by the
> lower-priority "first title-eligible field by declaration order" tier rather
> than by name. Nothing user-visible turns on this (R9 is `suggestion`, and the
> `Record #<id>` floor guarantees a title regardless), but do not read the R9
> list as the derivation contract — set `nameField` explicitly when the title
> matters.

These same rules are the **rubric for AI-generated metadata** — a generation is
"good" exactly when it is schema-valid and lint-clean:

Expand Down
Loading