Skip to content

Commit 91d74ca

Browse files
committed
chore(spec): register the select shorthand refusal as an ADR-0087 semantic entry
Claude-Session: https://claude.ai/code/session_0123qkvzKLQfRRRnuR8wanHJ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 62ebb57 commit 91d74ca

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

‎.changeset/25013-select-shorthand-keeps-written-value.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Clause-②: no (narrowing: a `Field.select` shorthand string the helper used to
1010

1111
Metadata-protocol: narrowed
1212

13-
<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: the shorthand is a TypeScript helper that runs in the author's own module, before any metadata exists, and what it hands on is an ordinary explicit option list. No spec key, stored shape or schema accept set changes: `SelectOptionSchema.value` refuses exactly what it refused before, and an object definition already stored or built from the old helper carries explicit values (`nooverlay`) that still parse, so there is nothing for `objectstack migrate meta` to rewrite. The one object definition that changes is `sys_metadata_audit`, a platform object `@objectstack/metadata-core` ships and owns: its `lock_state` column becomes a 255-character text column, its rows are audit records the metadata protocol writes, not metadata, no stored row is rewritten, and an existing column reports no drift. The other categories are closed on facts: every bumped package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
13+
<!-- adr-0087: registered field-select-shorthand-keeps-written-value -->
1414

1515
**BREAKING** (a TypeScript author's shorthand that used to be rewritten is now refused), shipped as `minor` under the launch-window convention for breaking changes.
1616

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import type { SemanticMigration } from '../../types.js';
4+
5+
// A shorthand that silently declared a value nobody wrote, refused instead. The
6+
// Field.select string shorthand used to delete every character outside a-z, 0-9
7+
// and _, so 'no-overlay' was declared as nooverlay. It now keeps the string as
8+
// written beyond lower-casing and whitespace to _, and SelectOptionSchema.value
9+
// refuses what is no system identifier, as it always did for an explicit value.
10+
// A structured TODO, not a D2 conversion: the shorthand runs in the author's own
11+
// TypeScript, so no stored artifact carries it, and which value the author meant
12+
// to store (no_overlay, or the column is not a select at all) is intent no
13+
// artifact records.
14+
export const entry: SemanticMigration = {
15+
id: 'field-select-shorthand-keeps-written-value',
16+
// No backticks in `surface` — build-upgrade-guide renders it inside a code
17+
// span already, and a nested backtick would close it.
18+
surface:
19+
'data.Field.select string shorthand (Field.select([...]) and Field.select({ options: [...] }) with '
20+
+ 'string options) — a string holding a character outside letters, digits, whitespace, _ and . '
21+
+ '(a hyphen, most often) is now refused at options.N.value instead of being declared with that '
22+
+ 'character deleted',
23+
replacement:
24+
'write the option explicitly with the value you store: Field.select({ options: [{ label: '
25+
+ '\'no-overlay\', value: \'no_overlay\' }] }). Where rows already hold the hyphenated spelling, the '
26+
+ 'column is not a select: declare it as Field.text({ maxLength: 255 })',
27+
reason:
28+
'The shorthand built each option as { label: s, value: s lower-cased, whitespace runs turned into _, '
29+
+ 'and every other character outside a-z, 0-9 and _ deleted }. The last step was undocumented and '
30+
+ 'silent: Field.select([\'no-overlay\']) declared nooverlay, and a record written with no-overlay, the '
31+
+ 'value as written, was refused with invalid_option on a writable field and held a value no option '
32+
+ 'declared on a read-only one. The ruling on this defect made the shorthand stop rewriting and join '
33+
+ 'the refusal an explicit { value: \'no-overlay\' } already met, rather than inventing a parallel one '
34+
+ '(ADR-0049: a declaration holds what its writer writes). SelectOptionSchema.value accepts and refuses '
35+
+ 'exactly what it did before; the shorthand only stops hiding a value from it. A dot is now kept '
36+
+ 'rather than deleted, so \'v1.2\' declares v1.2, a valid identifier, where it declared v12. An '
37+
+ 'object built by the old helper and stored carries explicit option values (nooverlay), which still '
38+
+ 'parse, so no stored row or definition needs rewriting. Measured on this tree at the change: the one '
39+
+ 'in-tree shorthand the strip altered was sys_metadata_audit.lock_state, now a text column; no '
40+
+ 'example, template, CLI scaffold or published skill writes such a string. ADR-0049 / ADR-0087.',
41+
acceptanceCriteria:
42+
'Load every object module (os validate or os build): ObjectSchema.create and the object save door '
43+
+ 'report any shorthand option that is no system identifier at fields.FIELD.options.N.value with '
44+
+ 'invalid_format. Rewrite each as an explicit option whose value is the one your writers store, or '
45+
+ 'declare the column as text where stored rows hold the hyphenated spelling. Done when every object '
46+
+ 'loads and every select option value equals a value a writer stores.',
47+
};

0 commit comments

Comments
 (0)