Repository navigation
Commit 459afea
fix(lint): a composite record title that must be queried is a stored computed field; a formula title is for display only (#25127)
Closes #25071
Clause-②: no (wording only: no verdict, severity, code, key or export
moves)
Metadata-protocol: none
**Governance: Tier H.** This diff touches `docs/adr/**` (ADR-0079 D3's
migration sentence), a governed surface, so it lands only on an
authorized APPROVED review. It stays a draft until then.
## What changes
Every sentence the card lists now gives the same remedy. A composite
record title that must be queried (matched by an import, sorted,
searched) is a **stored computed field**: a non-formula field (such as a
`text` field) that declares an `expression`, which the engine computes
and stores on every write. That field is designated as `nameField`. A
`formula` field with `returnType: 'text'` stays a legal `nameField` for
a title shown for display only.
| Site | Before | After |
|---|---|---|
| `packages/lint/src/validate-record-title.ts` `title-format-retired`
message and hint | migrate to "a formula field designated nameField" |
stored field with an expression for a queried title; formula is
display-only |
| `packages/lint/src/validate-record-title.ts` `title-unresolvable` hint
| "(or a formula field with returnType: 'text')" | text field with an
expression for a queried composite; text formula is display-only |
| `packages/lint/src/rule-explanations.ts` `title-format-retired`
explanation | composite title becomes a text formula | stored computed
field for a queried title; formula stays legal for display, and the
explanation says why (computed on read, stored nowhere: sort refused,
search never reaches it, import cannot match it) |
| `packages/lint/src/data-model-rules.ts` `object/missing-name-field`
fix line | "a stored text/autonumber field or a `returnType: 'text'`
formula" | a text field with an `expression` for a queried composite;
text formula is display-only. Still inside the 200-character fix-line
bound |
| `docs/adr/0079-record-display-name.md` D3 migration sentence only |
composite title becomes a text formula | stored computed field when
queried, citing addendum item 9 and ruling record 6308445871; text
formula stays legal for display only |
| `content/docs/data-modeling/formulas.mdx` ~:99 comment | migrates to a
text formula | text formula for display only, or a stored computed field
when import, sort or search must match it |
No finding fires on a different object, and no severity, rule code, key
or export changes. Only the text an author reads changes.
## The shape is real on origin/main
- `packages/objectql/src/engine.ts:3028-3050`, `planStoredExpressions`:
every field that is **not** a `formula` and declares an `expression`
(`def.type !== 'formula' && def.expression`, :3046) is computed on every
write and stored in its own column (#24619). Write paths call it at
:16210 (insert) and :17559 (update).
- `packages/rest/src/import-reference-stored-computed-title.test.ts` (on
main) pins the import side end to end: a lookup cell matches a target
titled by a stored computed field, and the same title declared as a
formula answers `reference_not_found` (ruling 6308445871).
- ADR-0079 addendum item 9
(`docs/adr/0079-record-display-name.md:400-406`): a formula value is
stored nowhere, a sort by it is refused (`400 INVALID_SORT`), and search
cannot reach it.
## Tests
The two suites that pin the exact strings are updated:
`packages/lint/src/validate-record-title.test.ts` and
`packages/lint/src/data-model-rules.fix-lines.test.ts`. Both are needed
because the shipped text is pinned verbatim there (the fix-line suite
also enforces the 200-character bound).
Measured at head `e9e0ec7bab`, after merging origin/main `03966ceae0`:
- Lint dependency closure built with `pnpm --filter
'@objectstack/lint^...' build`, under the verify lock: `VERDICT
command-exit 0`.
- `pnpm --filter @objectstack/lint test`: `Test Files 153 passed (153)`,
`Tests 7085 passed (7085)`. `pnpm --filter @objectstack/lint typecheck`
(including `check:test-typecheck`): `VERDICT command-exit 0`.
- `node scripts/pm/dispatch-gates.mjs --ran` with exit codes recorded:
`100 derived famil(ies) accounted for — 99 run, 1 NOT-MEASURED`. Exit 0
on every gate run, including `check:docs`, `check:doc-authoring`,
`check:doc-formula-expressions`, `check-changeset-no-major`,
`check-empty-changeset`, `check-adr-0087-registration`,
`check:changeset-gate-self-tests`, `check-adr-links`,
`check:adr-anchors` and `check:nul-bytes`.
- Three gates first stopped with PREREQUISITE NOT MET or a missing
`dist`, because a sibling build was rebuilding at the same time. After
the build they were re-run and exited 0: `check:skill-examples` (262
prose examples type-check), `check:dts-closure` (128/128 declarations
present) and `check:lean-entry-closure`.
- NOT MEASURED: `check:dual-build-cjs-loads`, reason: it needs every
package's `dist`, which means a whole-repo build, and that is left to
CI. The diff changes no build output shape.
- `scripts/pm/clause2-line.mjs` reads the changeset declarations as
`{"value":"no","arm":null}` and `{"value":"none"}`.
## Changeset
`@objectstack/lint` `patch`. The text of a shipped diagnostic changes,
but nothing a metadata author can write changes, so this is
`Metadata-protocol: none`. Under AGENTS.md Post-Task Checklist item 3,
`none` takes `patch`.
## Acceptance notes
The same "composite title is a text formula" advice exists in other
places that this card did not claim, so this PR leaves them alone. They
are listed in the dev report for the seat to file or route:
- `packages/spec/src/data/object.zod.ts:2843`: the `titleFormat`
describe says "a composite to a formula field designated as nameField".
The generated reference pages repeat it:
`content/docs/references/data/object.mdx:167`,
`content/docs/references/api/metadata.mdx:964` and
`content/docs/references/system/migration.mdx:349,643`. The
reconciliation note
`packages/spec/src/system/metadata-form-zod-reconciliation.test.ts:404`
quotes it too.
- `skills/objectstack-data/SKILL.md:90`: "for a composite title,
designate a `returnType: 'text'` formula field as `nameField`".
Open PR #25074 also edits `packages/lint/src/rule-explanations.ts`, but
in a different region (around lines 3562 and 4880; this PR edits around
2644), so the two do not conflict.
## 维护者速读(草稿)
- **改了什么**:记录标题相关的 lint 提示、`os explain` 说明、ADR-0079 D3
迁移句和一处文档注释,措辞统一为:需要被导入匹配、排序或搜索的组合标题应使用"存储型计算字段"(非 formula 字段加
`expression`,写入时计算并落库);text formula 标题仍合法,但只用于展示。
- **为什么改**:旧文案把作者引向 formula 标题,而 formula
值不落库,导致排序被拒、搜索够不到、导入无法按标题匹配(ADR-0079 补充第 9 条与裁决 6308445871)。
- **风险与代价(含回滚)**:仅文案,不改判定、级别、规则码或导出;回滚即还原本 PR。
- **席位意见**:
- **你要做的**:本 PR 触及 `docs/adr/**`,属 Tier H,需要一次授权的 APPROVED 评审后才能落地。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4c7dcb4 commit 459afea
8 files changed
Lines changed: 40 additions & 19 deletions
File tree
- .changeset
- content/docs/data-modeling
- docs/adr
- packages/lint/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
141 | | - | |
142 | | - | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
143 | 147 | | |
144 | 148 | | |
145 | 149 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
153 | | - | |
154 | | - | |
| 153 | + | |
| 154 | + | |
155 | 155 | | |
156 | 156 | | |
157 | 157 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
738 | 738 | | |
739 | 739 | | |
740 | 740 | | |
741 | | - | |
742 | | - | |
| 741 | + | |
| 742 | + | |
743 | 743 | | |
744 | 744 | | |
745 | 745 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2644 | 2644 | | |
2645 | 2645 | | |
2646 | 2646 | | |
2647 | | - | |
2648 | | - | |
2649 | | - | |
| 2647 | + | |
| 2648 | + | |
| 2649 | + | |
| 2650 | + | |
| 2651 | + | |
| 2652 | + | |
| 2653 | + | |
2650 | 2654 | | |
2651 | 2655 | | |
2652 | 2656 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
134 | | - | |
135 | | - | |
| 134 | + | |
| 135 | + | |
136 | 136 | | |
137 | 137 | | |
138 | | - | |
139 | | - | |
| 138 | + | |
| 139 | + | |
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
82 | | - | |
| 82 | + | |
| 83 | + | |
83 | 84 | | |
84 | 85 | | |
85 | 86 | | |
86 | | - | |
87 | | - | |
| 87 | + | |
| 88 | + | |
88 | 89 | | |
89 | 90 | | |
90 | 91 | | |
| |||
104 | 105 | | |
105 | 106 | | |
106 | 107 | | |
107 | | - | |
108 | | - | |
| 108 | + | |
| 109 | + | |
109 | 110 | | |
110 | 111 | | |
111 | 112 | | |
| |||
0 commit comments