Repository navigation
Commit 386f60f
fix(spec)!: the Field.select string shorthand keeps what it does not document transforming, so a hyphenated option is refused instead of declared as another value (#25122)
Fixes #25013
Clause-②: no (narrowing: a `Field.select` shorthand string the helper
used to rewrite silently, e.g. `'no-overlay'` → `nooverlay`, is refused
instead)
Metadata-protocol: narrowed
## What changed
- **`packages/spec/src/data/field.zod.ts` `selectWithOptions`**: the
string shorthand's transform is now only what its JSDoc documents:
lower-case, and whitespace runs become `_`. It no longer deletes every
other character outside `a-z0-9_`. A shorthand string that is no system
identifier (`'no-overlay'`) therefore reaches `SelectOptionSchema.value`
unchanged and is refused by the rule an explicit `{ value: 'no-overlay'
}` already met. No new error code, no parallel validator, and
`SelectOptionSchema.value` / `SystemIdentifierSchema` accept and refuse
exactly what they did before. The explicit-object branch keeps its
lower-casing (documented, and not a strip). The `Field.select` JSDoc
states the rule and the explicit spelling to use.
- **`packages/metadata-core` `sys_metadata_audit.lock_state`**: the one
in-tree shorthand the strip altered. It declared `none`, `nooverlay`,
`nodelete`, `full`, while its writer (`metadata-protocol` `protocol.ts`,
`lock_state: entry.lockState ?? 'none'`) stores the `MetadataLock`
spelling (`no-overlay`, `no-delete`). A select option value cannot hold
a hyphen, so the column is now `Field.text({ maxLength: 255 })`,
read-only as before, like the sibling `code` column whose closed set
also lives with its writer. Writer and reader unchanged, no stored row
rewritten.
- **Translations** (`platform-objects`, regenerated with `node
scripts/check-i18n-bundles.mjs --write`): the four `lock_state` option
labels drop out; the field label stays.
- **ADR-0087**: a semantic entry
`field-select-shorthand-keeps-written-value` (major 18), so the upgrade
guide and `os migrate meta` carry the TODO for a TypeScript author whose
shorthand now refuses. The changeset's disposition is `registered`.
- **Engine-double ledger**: `scripts/engine-double-contract.pinned.json`
records the new save-door test's fake engine
(`check-engine-double-contract.mjs --write`).
## Premises verified before writing code (ruling part ②)
- **(a) `lock_state` is the only shorthand the strip alters.** A
TypeScript-AST census of every `Field.select(...)` call in all 9,792
tracked `.ts/.tsx/.mts/.js/.mjs` files at `ae58b26f9` (140 calls, 189
shorthand strings), comparing the old strip with the documented
transform and with the identifier rule: 2 hits, both
`sys-metadata-audit.object.ts:190` (`no-overlay`, `no-delete`). The
non-literal option lists it could not read statically
(`BUILTIN_MEMBERSHIP_ROLE_OPTIONS`, `InvitationStatus.options`,
`SYS_ACTIVITY_BUILTIN_TYPES`, and the `.map(...)` builders that write
explicit values) were read by hand: all identifiers. A regex census of
the 1,016 tracked `.md/.mdx` files (published skills, docs, templates;
changelogs excluded): 51 shorthand strings, 0 hits. **Positive
control**: the same AST census at `ba244f1^` (before PR #25099) reports
the third hit, `sys-oauth-application.object.ts:449`
`'user-agent-based'`.
- **(b) `lock_state` can hold what its writer writes without widening
the option-value accept set and without rewriting rows.** `Field.text`
holds the hyphenated spelling; no option value is involved. Schema drift
was measured with `diffManagedTable` on a `varchar(255)` column (the
width the select column was created as) on postgres, mysql and sqlite:
`select` 0 entries; `text` with no `maxLength` 1 entry on postgres and
mysql (`manual_widen_varchar_to_text`, `needs_confirm`); `text` with
`maxLength: 32` a destructive `narrow_varchar`; `text` with `maxLength:
255` 0 entries on all three. Hence `maxLength: 255`.
## Where the refusal fires (H3, measured)
- **TypeScript authoring, module load**: `ObjectSchema.create` (the
shape every platform object and `os init` template uses) throws a
`ZodError` with one issue, `invalid_format` at
`fields.FIELD.options.N.value`, message `System identifier must be
lowercase, starting with a letter, …`. Before the `lock_state` fix,
importing `sys-metadata-audit.object.ts` would have thrown exactly this.
- **The object save door**: `saveMetaItem` answers `422
INVALID_METADATA` with the issue at `fields.status.options.1.value`,
code `invalid_format`, and stores nothing; the shorthand and the
explicit spelling produce identical `issues`.
## Tests
- `packages/spec/src/data/field.test.ts`: the hyphen is kept; shorthand
and explicit meet the same issue at the same path; `ObjectSchema.create`
refuses at module load (`invalid_format` at
`fields.lock_state.options.1.value`); the documented transform still
parses (`In Progress` → `in_progress`, and `v1.2` now declares `v1.2`).
-
`packages/metadata-protocol/src/protocol.select-shorthand-refusal.test.ts`:
the save door's `code` + `status` + issue path, the explicit spelling's
identical issues, and a control that the documented transform saves.
-
`packages/metadata-core/src/objects/sys-metadata-audit-lock-state.test.ts`:
`lock_state` is text, `maxLength: 255`, no options, read-only; exactly
`no-overlay` and `no-delete` of `MetadataLockSchema` are values a select
option cannot declare; the object parses.
- **Ablation** (one, on the behaviour the spec tests pin):
`scripts/ablation-replace.mjs` re-inserted the strip into `field.zod.ts`
(anchor 1 → 0, blob `9d7a024c0fbc` → `3162adcde7d2`); `field.test.ts`
went 4 failed / 280 passed, the four new cases; restored, `git diff
HEAD` empty.
- Suites (on `88440a148`, before the ledger entry and merge; neither
touches these packages' sources): `@objectstack/spec` 706 files / 21,463
tests passed; `@objectstack/metadata-core` 19 / 791;
`@objectstack/metadata-protocol` 252 passed + 3 skipped files / 20,835
passed + 19 skipped tests; `@objectstack/platform-objects` 86 / 1,250.
After the ledger entry: spec `src/migrations` + `field.test.ts` 6 files
/ 512 tests, and `typecheck` green for spec, metadata-core,
metadata-protocol and platform-objects.
- Full-repo pin sweep: `git grep` for `nooverlay`, `nodelete`,
`useragentbased` and `toSnakeCase` outside changelogs finds only the
regenerated translation bundles, the earlier OAuth changeset and the
CLI's own unrelated `toSnakeCase`. No fixture asserted the stripped
values, so no pin flips.
## Acceptance notes
- The translated `lock_state` option labels (`禁止叠加`, …) never displayed:
they were keyed `nooverlay` / `nodelete`, which no stored row holds.
Dropping them changes nothing a user saw; the column now shows the
stored spelling.
- `'v1.2'` used to declare `v12` and now declares `v1.2`, a valid
identifier: a value change, not a refusal. The census found no in-tree
shorthand with a dot.
- The explicit-object branch still lower-cases `value` (`{ value:
'Active' }` → `active`). That is documented and not a strip, so it is
left alone here; recorded in the report as an open question.
---
_Generated by [Claude
Code](https://claude.ai/code/session_0123qkvzKLQfRRRnuR8wanHJ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent db9c02a commit 386f60f
12 files changed
Lines changed: 304 additions & 36 deletions
File tree
- .changeset
- packages
- metadata-core/src/objects
- metadata-protocol/src
- platform-objects/src/apps/translations
- spec/src
- data
- migrations/entries/semantic
- scripts
| 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 | + | |
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
186 | 186 | | |
187 | 187 | | |
188 | 188 | | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
189 | 200 | | |
190 | | - | |
| 201 | + | |
191 | 202 | | |
192 | 203 | | |
193 | 204 | | |
| 205 | + | |
194 | 206 | | |
195 | 207 | | |
196 | 208 | | |
| |||
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3245 | 3245 | | |
3246 | 3246 | | |
3247 | 3247 | | |
3248 | | - | |
3249 | | - | |
3250 | | - | |
3251 | | - | |
3252 | | - | |
3253 | | - | |
3254 | | - | |
| 3248 | + | |
3255 | 3249 | | |
3256 | 3250 | | |
3257 | 3251 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3245 | 3245 | | |
3246 | 3246 | | |
3247 | 3247 | | |
3248 | | - | |
3249 | | - | |
3250 | | - | |
3251 | | - | |
3252 | | - | |
3253 | | - | |
3254 | | - | |
| 3248 | + | |
3255 | 3249 | | |
3256 | 3250 | | |
3257 | 3251 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3245 | 3245 | | |
3246 | 3246 | | |
3247 | 3247 | | |
3248 | | - | |
3249 | | - | |
3250 | | - | |
3251 | | - | |
3252 | | - | |
3253 | | - | |
3254 | | - | |
| 3248 | + | |
3255 | 3249 | | |
3256 | 3250 | | |
3257 | 3251 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3245 | 3245 | | |
3246 | 3246 | | |
3247 | 3247 | | |
3248 | | - | |
3249 | | - | |
3250 | | - | |
3251 | | - | |
3252 | | - | |
3253 | | - | |
3254 | | - | |
| 3248 | + | |
3255 | 3249 | | |
3256 | 3250 | | |
3257 | 3251 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1485 | 1485 | | |
1486 | 1486 | | |
1487 | 1487 | | |
| 1488 | + | |
| 1489 | + | |
| 1490 | + | |
| 1491 | + | |
| 1492 | + | |
| 1493 | + | |
| 1494 | + | |
| 1495 | + | |
| 1496 | + | |
| 1497 | + | |
| 1498 | + | |
| 1499 | + | |
| 1500 | + | |
| 1501 | + | |
| 1502 | + | |
| 1503 | + | |
| 1504 | + | |
| 1505 | + | |
| 1506 | + | |
| 1507 | + | |
| 1508 | + | |
| 1509 | + | |
| 1510 | + | |
| 1511 | + | |
| 1512 | + | |
| 1513 | + | |
| 1514 | + | |
| 1515 | + | |
| 1516 | + | |
| 1517 | + | |
| 1518 | + | |
| 1519 | + | |
| 1520 | + | |
| 1521 | + | |
| 1522 | + | |
| 1523 | + | |
| 1524 | + | |
| 1525 | + | |
| 1526 | + | |
| 1527 | + | |
| 1528 | + | |
| 1529 | + | |
| 1530 | + | |
1488 | 1531 | | |
1489 | 1532 | | |
1490 | 1533 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3050 | 3050 | | |
3051 | 3051 | | |
3052 | 3052 | | |
3053 | | - | |
3054 | | - | |
3055 | | - | |
3056 | | - | |
3057 | | - | |
3058 | | - | |
3059 | | - | |
| 3053 | + | |
| 3054 | + | |
| 3055 | + | |
| 3056 | + | |
| 3057 | + | |
| 3058 | + | |
3060 | 3059 | | |
3061 | 3060 | | |
3062 | 3061 | | |
| |||
3163 | 3162 | | |
3164 | 3163 | | |
3165 | 3164 | | |
| 3165 | + | |
| 3166 | + | |
| 3167 | + | |
| 3168 | + | |
| 3169 | + | |
| 3170 | + | |
3166 | 3171 | | |
3167 | 3172 | | |
3168 | 3173 | | |
| |||
0 commit comments