Repository navigation
Commit dc1b986
fix(spec): narrow the object designer's formula returnType options to the four declared members (#19759)
Fixes #19677
Clause-②: no
## The defect
`FieldSchema.returnType` declares four members — `number`, `text`,
`boolean`, `date`. The object designer's quick-add grid
(`packages/spec/src/data/object.form.ts`, the `fields` repeater)
declared an inline `options` list of **six**, adding `datetime` and
`currency`. An author who added a formula field from the object designer
and picked Datetime or Currency wrote a value the parse rejects.
The failure is silent at the point of choice: the select is populated
from that inline list, nothing reconciles it against the enum, and the
refusal arrives later from the save door naming a key the author never
typed.
## The fix
Narrow the control to the four declared members. One write site, plus a
regression test and a patch changeset.
This is a pull-back to a spelling that already existed in this package,
not a decision about what a formula may return:
- **The enum is unchanged.** `packages/spec/src/data/field.zod.ts` is
untouched — `returnType` accepted exactly these four before this change
and accepts exactly these four after it. No authorable value is removed,
because neither `datetime` nor `currency` was ever accepted; what is
removed is an offer with nothing behind it.
- **The sibling control already spelled it correctly.** The field
designer's own `returnType` select in
`packages/spec/src/data/field.form.ts` carries the same explicit
four-member list, with a comment saying why it is explicit. That file is
untouched too. The object designer was the lone divergent carrier; three
carriers now agree, counting the published reference doc
(`content/docs/references/data/field.mdx`).
- **The producer side agrees with the enum.** Authoring stamps
`returnType` from the inferred CEL type, and `inferExpressionType` in
`@objectstack/formula` is typed `number | text | boolean | date |
unknown`. There is no path by which the platform ever stamps `datetime`
or `currency`, so the six-member list was never a capability that
existed and got broken.
The opposite exit — widening the enum to six — was considered and
rejected on that last reading. It would be a Feature and a clause-②
`yes`, and nothing in the producer, the sibling control or the published
docs suggests the platform means to support datetime- or
currency-returning formulas.
## The regression test
`packages/spec/src/data/form-return-type-options.test.ts`, 13 assertions
over both forms.
The expected set is **derived from `FieldSchema` at runtime** — read
through the same JSON-Schema view the metadata-admin renderer falls back
to when a row declares no `options` at all — rather than hard-coded as
four strings. A test pinned to literals rots exactly the way this defect
did: the enum moves, the literals do not, and the test keeps passing
over a control that has drifted again.
The offer is judged at **value level**, not key level: every offered
value must survive a full `safeParse` on a formula field. A key-level
check would have passed on the broken grid, because `returnType` was
always a recognised key.
The two removed members are additionally pinned as
refused-and-unoffered. That is not a duplicate of the set equality — it
is the assertion that fires if the platform ever decides a formula may
return a datetime or a currency, so that the widening and the two forms
move in one change.
## Verification
All commands run in a dedicated worktree at `593004aa`. Heavy runs went
through `scripts/pm/os-verify-lock.sh`; exit codes captured before any
pipe.
| command | verdict |
|:--|:--|
| `pnpm --filter '@objectstack/spec^...' build` | exit 0 — closure empty
("No projects matched the filters"); `packages/spec` has no workspace
dependencies |
| `pnpm --filter @objectstack/spec build` | exit 0 — 34/34 declaration
files, dist stamp `e3782a80bd03` |
| `pnpm --filter @objectstack/spec exec vitest run
src/data/form-return-type-options.test.ts` | exit 0 — 13 passed |
| `pnpm --filter @objectstack/spec test` | exit 0 — **516 files, 15056
passed**, 1 todo |
| `pnpm --filter @objectstack/spec typecheck` | exit 0 — `tsc --noEmit`
+ scripts + test layer |
| `pnpm --filter @objectstack/spec check:generated` | exit 0 — **all 15
generated artifacts up to date**; no regeneration needed |
| `pnpm lint` (repo-wide, `eslint . --no-inline-config`) | exit 0 — full
population, no narrowing |
| `pnpm check:type-check-coverage` | exit 0 — 77/81 packages, test layer
hides 0 files |
| 10 spec source audits (`check:empty-state`, `variant-docs`,
`yaml-examples`, `exported-any`, `dual-source-exports`,
`entry-nameability`, `browser-reachable-entries`, `llms-txt`,
`duration-unit-keys`, `objectui-pin-citations`) | exit 0 each |
| `check:nul-bytes`, `check:cross-package-test-inputs`,
`check:test-source-alias`, `check:issue-citations`,
`check:doc-authoring`, `check:published-files`,
`check:spec-docblock-symbol-anchors` | exit 0 each |
| `check-changeset-no-major --base origin/main`,
`check-adr-0087-registration --base origin/main`, `check-empty-changeset
--base origin/main` (+ self-tests), `check:objectui-changeset`,
`check:changeset-gate-self-tests` | exit 0 each |
| `pnpm check:type-check-debt` | **NOT MEASURED** — exit 3,
`PREREQUISITE NOT MET`: `--re-measure` refuses without 30 workspace
dependencies built. Nothing was measured, so this is neither a pass nor
a finding. The ledgered packages are not in this diff. CI builds that
closure before the step. |
**Reverse verification.** The fix was committed first (`3f4c20ea`), then
the six-member list was reinstated on disk through
`scripts/ablation-replace.mjs` — anchor 1 occurrence, dropping to 0;
blob `38a494a2c532` moving to `ee2e9994b641`, both counted by the tool
itself rather than by a claim.
The test went **red in the expected direction**: 4 of 13 failed — the
set equality (`received` carrying `currency` and `datetime`), the
value-level parse (`FieldSchema` answering `invalid_value` on
`'datetime'`), the historical-member pin, and the two-designers
agreement. The other 9 stayed green, which is the point: the derivation,
the single-declaration check and the DSL-expressibility check are
orthogonal to the mutation.
Restore verified by the tool on the way out: blob back to
`38a494a2c532`, equal to the blob at HEAD, and `git diff HEAD` empty. No
ablation artefact remains in the tree.
No `dist` preflight was needed for it: this test imports `./object.form`
relative, inside its own package, so vitest resolves the source directly
and no build sits between the mutation and the assertion.
## The changeset
`packages/spec` publishes this file. Measured rather than assumed: the
changed `helpText` string resolves in 8 files under
`packages/spec/dist`, and a positive control taken from the same source
file (`Add the columns this object will store`) resolves in the same 8.
`dist` is in the package's `files` whitelist, so the fix ships and takes
a `patch` changeset — `skip-changeset` would have been wrong.
## Acceptance notes
- **No reconciliation gate.** Nothing mechanically holds a form's
`options` list to its schema enum; both this grid and the sibling
control are hand-maintained lists. The dispatch put that outside this
round — a new required gate is the maintainer's call, not a rider — so
the regression test covers this one key rather than the class. Noted,
not filed.
- **Repo-wide, this was the only divergent carrier.** Every `returnType`
occurrence under `packages/`, `content/` and `examples/` on
`origin/main` was read: the six-member list existed in `object.form.ts`
alone. Every other carrier already agreed with the enum.
- **Stored documents remain unmeasured**, exactly as the filer left
them. Whether any authored field carries `returnType: 'datetime'` or
`'currency'` today is not read here, and the fix does not depend on it
either way — the parse that refuses those values is the one that already
ran.
- **The objectui side is unestablished.** Whether that repo's own field
designer offers the same six is outside this radius and was not read.
## Scope
Three files. `field.zod.ts` and `field.form.ts` were read-only this
round and are untouched; taking either would have been the other exit.
---
_Generated by [Claude
Code](https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a754563 commit dc1b986
3 files changed
Lines changed: 243 additions & 2 deletions
File tree
- .changeset
- packages/spec/src/data
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
288 | 288 | | |
289 | 289 | | |
290 | 290 | | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
291 | 298 | | |
292 | | - | |
293 | | - | |
| 299 | + | |
| 300 | + | |
294 | 301 | | |
295 | 302 | | |
296 | 303 | | |
| |||
0 commit comments