diff --git a/.changeset/15437-validation-messages-migration-route.md b/.changeset/15437-validation-messages-migration-route.md new file mode 100644 index 0000000000..40e60349cb --- /dev/null +++ b/.changeset/15437-validation-messages-migration-route.md @@ -0,0 +1,51 @@ +--- +'@objectstack/spec': patch +--- + +The `translation-validation-messages-removed` migration text names the object-scoped bundle key, not just the authored literal + +`validationMessages` was retired in 17.0.0 (#4667). The ADR-0087 conversion that +migrates it told an author to author the message on the rule +(`object.validations[].message`) and stopped there. Since 17.3.0 (#14381, +#14253) that message has a translation route — +`objects.._validations..message`, resolved on the write +path — and the sibling prescription ten metres away in the same package +(`TRANSLATION_KEY_GUIDANCE.validationMessages`, the text the strict door +returns) already names it. + +⛔ Nothing the old text said was false, and none of it is deleted. The defect is +**silence**: this is the *migration* text, read by exactly the population that +authored the retired key — the authors who wanted their rule messages +translated — and it steered them to a plain authored literal without mentioning +that the bundle key now exists. The literal advice stays; the route is added +after it. + +**Two texts in the file carried the narrow prescription, not one.** The +conversion's `summary` is the one the card named; the docblock above it asserted +that rule messages are *"not translated through a group"*, which would have sat +directly above the corrected summary. Both are completed. The docblock keeps its +17.0.0 sentence — still true of the retired key — and says what 17.3.0 changed, +including why the object-scoped group is not `validationMessages` returning (the +retired one was keyed by rule name at the top level, could not tell two objects' +rules apart, and had no reader). + +**This is shipped, which is why it carries a changeset rather than +`skip-changeset`.** `packages/spec/src/conversions/registry.ts` is not a +`.zod.ts`, so it is not shipped as source — but two published paths move, +measured on the built tree rather than reasoned about: + +- `dist` is in `files[]`, and the new sentence is emitted into six built files + (`dist/index.js` / `.mjs`, `dist/shared/index.js` / `.mjs`, + `dist/browser/index.js` / `.mjs`); a negative control string scored 0 on the + same tree. An author running `os migrate meta --from 16` reads the changed + notice out of that runtime string. +- `spec-changes.json` is itself listed in `files[]`, and it carries the summary + twice. It is generated (`gen:spec-changes`), and `check:generated` caught it + stale — the conversion registry feeds two generated artifacts, not one. + +`docs/protocol-upgrade-guide.md` is the third, regenerated with +`gen:upgrade-guide` and verified by `check:upgrade-guide`; all three are +regenerated, never hand-edited. + +⛔ No behaviour changes. The conversion id, its `apply`, its accept set and its +fixture are untouched; no authorable key is added or removed. diff --git a/docs/protocol-upgrade-guide.md b/docs/protocol-upgrade-guide.md index 075e5691de..2f9e3dca0c 100644 --- a/docs/protocol-upgrade-guide.md +++ b/docs/protocol-upgrade-guide.md @@ -280,7 +280,7 @@ Finally it removes the 'pdf' member of `view.exportOptions` formats (#8010, main | `mapping-inert-keys-removed` | `mapping.extractQuery / mapping.errorPolicy / mapping.batchSize` | mapping keys 'extractQuery'/'errorPolicy'/'batchSize' removed (#4509 — no exporter reads a mapping, error handling belongs to the import request, and the write path sizes its own batches) | retired — `migrate meta` only | | `book-translations-removed` | `book.translations / book.groups.translations` | book keys 'translations' (book-level and group-level) removed (#4667 — no resolver read them; the tree endpoint and portal render labels verbatim, so a localized book served its authoring locale to everyone). Localize the docs instead: `doc.translations` is live | retired — `migrate meta` only | | `job-id-removed` | `job.id` | job key 'id' removed (#4667 — nothing read it; `name` is the job's identity everywhere, so two jobs differing only in `id` were the same job, and the key's own description advertised an override that did not exist) | retired — `migrate meta` only | -| `translation-validation-messages-removed` | `translation.validationMessages` | translation key 'validationMessages' removed (#4667 — no resolver read it, so a translated rule message was stored and never shown; #3778's migration table had been steering retired `errors:` authors into it). Author the message on the rule itself (`object.validations[].message`) | retired — `migrate meta` only | +| `translation-validation-messages-removed` | `translation.validationMessages` | translation key 'validationMessages' removed (#4667 — no resolver read it, so a translated rule message was stored and never shown; #3778's migration table had been steering retired `errors:` authors into it). Author the message on the rule itself (`object.validations[].message`), and translate it under the object-scoped group `objects.._validations..message`, which the write path resolves (17.3.0, #14381) | retired — `migrate meta` only | | `datasource-config-driver-key-aliases` | `datasource.config` | datasource config keys → canonical per driver: sqlite 'file'/'database' → 'filename', postgres/mysql 'connectionString' → 'url' and 'user' → 'username', mongo 'uri' → 'url' and 'user' → 'username' (#4456 — driver-factory `??` fallback graduation) | retired — `migrate meta` only | | `datasource-driver-mongo-to-mongodb` | `datasource.driver` | datasource driver id 'mongo' → 'mongodb' — the canonical id both boot hosts, the driver package and the published DRIVER_CATALOG already used (#6345) | live — protocol 17 loader accepts the old shape | | `flow-node-script-branch-keys-removed` | `flow.node.script.config.actionType / flow.node.script.config.template / flow.node.script.config.recipients / flow.node.script.config.variables / flow.node.script.config.script` | script flow-node config keys 'actionType' (→ 'function' when it was shorthand for one; otherwise removed — 'email'/'slack' were logger-backed stubs that delivered nothing), plus 'template' / 'recipients' / 'variables' (fed those stubs) and 'script' (inline JS the runtime never executed) (#4343) | retired — `migrate meta` only | diff --git a/packages/spec/spec-changes.json b/packages/spec/spec-changes.json index d2f7cc209a..d59761365c 100644 --- a/packages/spec/spec-changes.json +++ b/packages/spec/spec-changes.json @@ -274,7 +274,7 @@ }, { "surface": "translation.validationMessages", - "to": "translation key 'validationMessages' removed (#4667 — no resolver read it, so a translated rule message was stored and never shown; #3778's migration table had been steering retired `errors:` authors into it). Author the message on the rule itself (`object.validations[].message`)", + "to": "translation key 'validationMessages' removed (#4667 — no resolver read it, so a translated rule message was stored and never shown; #3778's migration table had been steering retired `errors:` authors into it). Author the message on the rule itself (`object.validations[].message`), and translate it under the object-scoped group `objects.._validations..message`, which the write path resolves (17.3.0, #14381)", "conversionId": "translation-validation-messages-removed", "toMajor": 17 }, @@ -1423,7 +1423,7 @@ }, { "surface": "translation.validationMessages", - "to": "translation key 'validationMessages' removed (#4667 — no resolver read it, so a translated rule message was stored and never shown; #3778's migration table had been steering retired `errors:` authors into it). Author the message on the rule itself (`object.validations[].message`)", + "to": "translation key 'validationMessages' removed (#4667 — no resolver read it, so a translated rule message was stored and never shown; #3778's migration table had been steering retired `errors:` authors into it). Author the message on the rule itself (`object.validations[].message`), and translate it under the object-scoped group `objects.._validations..message`, which the write path resolves (17.3.0, #14381)", "conversionId": "translation-validation-messages-removed", "toMajor": 17 }, diff --git a/packages/spec/src/conversions/registry.ts b/packages/spec/src/conversions/registry.ts index df7ab777f7..5ef7cffd5d 100644 --- a/packages/spec/src/conversions/registry.ts +++ b/packages/spec/src/conversions/registry.ts @@ -3120,6 +3120,16 @@ const jobIdRemoved: MetadataConversion = { * rewritten in the same change to say rule messages are authored on the rule * (`object.validations[].message`), not translated through a group. * + * Since 17.3.0 (#14381, #14253) there is a group again and that same guidance + * entry names it: `objects.._validations..message`, + * which the write path resolves. It is not `validationMessages` returning — + * that one was keyed by rule name at the TOP level, so it could not tell two + * objects' rules apart, and nothing read it; this one is object-scoped and the + * rule evaluator reads it through the existing `i18nService` channel. The + * sentence above is what 17.0.0 said and it stays true of the retired key; an + * author arriving at this conversion needs both halves, so the summary below + * carries the route too. + * * Removed from the shared `translationDataShape()`, so it retires at BOTH doors * at once — the bundle entry and the registered item. #3778's original guard * ran on the item door only, which is exactly how the key survived this long in @@ -3130,7 +3140,7 @@ const translationValidationMessagesRemoved: MetadataConversion = { toMajor: 17, retiredFromLoadPath: true, surface: 'translation.validationMessages', - summary: "translation key 'validationMessages' removed (#4667 — no resolver read it, so a translated rule message was stored and never shown; #3778's migration table had been steering retired `errors:` authors into it). Author the message on the rule itself (`object.validations[].message`)", + summary: "translation key 'validationMessages' removed (#4667 — no resolver read it, so a translated rule message was stored and never shown; #3778's migration table had been steering retired `errors:` authors into it). Author the message on the rule itself (`object.validations[].message`), and translate it under the object-scoped group `objects.._validations..message`, which the write path resolves (17.3.0, #14381)", apply(stack, emit) { return mapCollection(stack, 'translations', (t, path) => stripKeys(t, ['validationMessages'], emit, path));