You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ba42fbd
Browse filesBrowse the repository at this point in the historyBrowse files
fix(spec): `os lint` tells an author that a field's `externalId: true` does nothing
6
+
7
+
Clause-②: no (a liveness-ledger re-grade: no schema, accept set, served body or error code moves; only the advisory `liveness-dead-property` warning on an authored `externalId: true` starts, and the flag's description is reworded)
8
+
Metadata-protocol: none
9
+
10
+
The shipped liveness ledger (`liveness/field.json`) graded a field's `externalId` flag `live` on the strength of one renderer module that was deleted for having no importers. Re-measured across `packages/**` and `ui/**`, nothing reads the flag: the Studio field inspector edits it and a metadata summary copies it, and no upsert matches on it. The row is now `dead`, so `os lint` warns on a field that sets `externalId: true` (the default `false` is not warned). The flag's schema description, and the reference pages generated from it, no longer say it drives upserts.
11
+
12
+
-**What to do.** Remove the flag. To match seed records on a field, name the field in the seed dataset's `externalId` (`name` when it is left out). For an import, name it in the mapping's `upsertKey`.
13
+
-**Unchanged.** Parsing (the key is still accepted, default `false`), seed loading, imports and every upsert.
feat(spec)!: a single-word language name (`Chinese`, `English`, `Japanese`) is refused as a locale
8
+
9
+
The 18.x locale shape refused `zh_CN`, a padded value and a multi-word name such as `Chinese (Simplified)`, but its 2–8 letter language subtag still admitted any single word of that length. An author who wrote `locale: 'Chinese'` got a green save, and the strings were stored under a locale no session ever asks for, so they never rendered. The refusal text already said a language name is refused.
10
+
11
+
The language subtag is now 2–3 letters, the ISO 639 codes every tag in use starts with (BCP-47 reserves 4 letters and registers no 5–8 letter language). Everything else in the shape is unchanged: hyphen-separated 1–8 letter-or-digit subtags (`en`, `zh-CN`, `zh-Hans-CN`, `es-419`, `yue-HK`), and case is not judged. The one shape moves in all three places that hold it:
12
+
13
+
-`LocaleSchema` (`@objectstack/spec/system`): a `translation` item's `locale`, translation bundle keys, `i18n.defaultLocale` / `fallbackLocale` / `supportedLocales`, a seed dataset's `locale` scope and the seed loader's `locale`.
14
+
-`SYS_USER_LOCALE_TAG_PATTERN` (`@objectstack/platform-objects/identity`): the `locale_bcp47_shape` rule on a user's own `sys_user.locale` column.
15
+
-`LOCALE_TAG_SHAPE` (`@objectstack/service-messaging`): the read side that picks a recipient's notification locale, byte-identical to the column rule.
16
+
17
+
Clause-②: no (narrowing) — the locale shape refuses values it used to accept. Nothing is widened.
18
+
19
+
Metadata-protocol: narrowed (a locale whose language subtag is 4–8 letters, in practice a language name, is refused on translation items, bundle keys, the i18n configuration and seed datasets)
20
+
21
+
<!-- adr-0087: not-required (already-registered translation-locale-non-tag-refused) the 18.x D3 entry already prescribes rewriting a language name by hand, and its conversion already lists such a stored row as a TODO through LocaleSchema; this diff narrows the same shape and corrects that entry's subtag length -->
22
+
23
+
**BREAKING** for an author or user who wrote a language name as a locale.
24
+
25
+
-**Before.**`defineTranslation({ name: 'qa_x', locale: 'Chinese', … })` parsed and its strings never rendered; `PATCH` of a user's `locale` to `Japanese` was stored, and that user's notifications fell to the delivery ladder's floor.
26
+
-**Now.** The value is refused at `locale` (or at the bundle key) with a message naming the tag shape, and the user's write is refused by the column's `locale_bcp47_shape` rule.
27
+
-**Fix.** Write the tag, not the name: `Chinese` → `zh-CN`, `English` → `en`, `Japanese` → `ja-JP`. A language name has no single tag it can have meant, so `os migrate meta --stored` lists a stored translation row as a TODO (D3 entry `translation-locale-non-tag-refused`) and you rewrite it by hand. A user whose stored `locale` is already a name such as `Japanese` keeps loading: their notifications fall to the deployment default, and saving that value again is refused until it is replaced with a tag.
fix(cli): `os dev`'s in-place organization-ownership upgrade names what stopped it, and the exact command that carries on
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
When the organization-ownership boot guard refuses a development database, `os dev` copies it and runs the ceremony on it in place. When that upgrade did not finish, it could exit with 「The upgrade did not run to a report」 and no cause.
11
+
12
+
What changes:
13
+
14
+
- The ceremony's own refusal is printed in its own words, also when the terminal forces colour (`FORCE_COLOR`): the refusal lines are read with colour stripped, and the `✗` glyph is no longer printed twice.
15
+
- An apply that stopped with no refusal of its own (an uncaught error, a crash, a signal) says how it ended and prints the end of its log, without the kernel's boot narration.
16
+
- A report file still holding the apply's reservation text, or one that does not parse, is read as "no report", with the cause, never as "a blocker (above)" with nothing above. A blocked report that names no blocker prints the log's cause too.
17
+
- A stale `organization_id` the plan gives the column-drop fate is upgraded as before, but no longer silently: before the apply runs, `os dev` prints per table how many stamped values the drop discards, and names the copy that keeps them.
18
+
- Each failed upgrade ends on the exact command that carries on: `os migrate organization-ownership --dev --database-url URL --apply --plan-file PLAN --backup-taken`, with the database and the plan the upgrade wrote.
19
+
20
+
The copy taken before the upgrade is kept, as before. A development database with a stale `organization_id` column on `sys_flow_credential` and `sys_secret` (objects that no longer declare it) is upgraded and starts under `os dev`, now pinned by a test. The production ceremony is unchanged.
fix(metadata-protocol): on PostgreSQL, the NULL-safe row identity of `sys_setting` and `sys_notification_preference` now lands, and a tightening that did not land is reported instead of answered `created`
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
On PostgreSQL the declared unique both tables start with is a UNIQUE CONSTRAINT (the driver builds it with knex `table.unique()`), not a bare index. The boot migration that replaces it with the NULL-safe key ran `DROP INDEX` on the constraint's index, which PostgreSQL refuses (2BP01, printed on every boot), and then `CREATE UNIQUE INDEX IF NOT EXISTS` under the name the constraint still held, which succeeds as a no-op. Both migrations reported `created` while the plain, NULL-distinct key stayed: two tenant-scope or global-scope settings rows for one (organization, namespace, key) could coexist.
11
+
12
+
-**The drop.** On PostgreSQL the replace step now frees the name the way the driver's own `dropIndexIfExists` does: `ALTER TABLE … DROP CONSTRAINT IF EXISTS`, then `DROP INDEX IF EXISTS`. No `CASCADE`.
13
+
-**The verdict.** On PostgreSQL `created` is answered only after the definition under the real name reads back (`pg_get_indexdef`) equal to the one the probe built. When it does not (something depends on the old constraint and the drop is refused), the boot prints the existing `[metadata-protocol] could not rebuild …` line naming what is not enforced, with the refusal, and keeps running.
14
+
-**Existing duplicates.** When rows already break the NULL-safe key, nothing is deleted or merged and the old key stays, as before. The `cannot tighten …` line now also says how many keys are duplicated and how many rows hold them, on every dialect. It prints no row values.
15
+
- SQLite and MySQL run the same probe, drop and create statements as before, with the same outcomes. The only new statement there is the count, and it runs only on a conflict.
0 commit comments