Skip to content

Commit 8e8860e

Browse files
claude[bot]claude
andauthored
docs(adr): record that authored validation-rule messages are translatable again (#15438)
ADR-0049 gets a status-line note and a dated amendment recording that an author-written validation rule's `message` became translatable in 17.3.0 under the object-scoped bundle key `objects.<object_name>._validations.<rule_name>.message` — the enforce-or-remove gate being enforced, not reversed. The 17.0.0 retirement of the top-level `validationMessages` group stands and the two are not the same route. `content/docs/ui/translations.mdx` carried the sentence that change falsified ("Validation messages are not translated through bundles"). The surface table gains the `_validations` row and the boundary bullet is rewritten to the limit that is actually current: the authored sentence is substituted whole, with no interpolation. Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 581d8f8 commit 8e8860e

2 files changed

Lines changed: 70 additions & 6 deletions

File tree

‎content/docs/ui/translations.mdx‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export default defineStack({
6969
| Action labels, confirm text, success messages | `objects.<name>._actions.<action>` |
7070
| Action result dialogs (title / description / acknowledge / field labels) | `objects.<name>._actions.<action>.resultDialog` |
7171
| Form sections | `objects.<name>._sections.<section>` |
72+
| Custom validation-rule messages | `objects.<name>._validations.<rule>.message` |
7273
| App navigation | `apps.<app>.navigation.<id>.label` |
7374
| Dashboard label / description | `dashboards.<name>.label` / `description` |
7475
| Dashboard widget title / description / sub-caption | `dashboards.<name>.widgets.<widgetId>.title` / `description` / `subCaption` |
@@ -245,11 +246,22 @@ up-to-date bundle re-extracts byte-identically.
245246

246247
Honest limits worth knowing before you plan around them:
247248

248-
- **Validation messages are not translated through bundles.** The
249-
`validationMessages` group was removed in 17.0.0 (#4667): it had no runtime
250-
consumer, so a translated rule message was stored and never shown. Author the
251-
message on the rule itself (`object.validations[].message`), which the engine
252-
returns on every rejected write.
249+
- **Validation messages are translatable, but substituted whole — there is no
250+
interpolation.** Author the message on the rule
251+
(`object.validations[].message`), which the engine returns on every rejected
252+
write, and translate it under `objects.<name>._validations.<rule>.message`
253+
(the row in the table above) — live since 17.3.0. The write path swaps the
254+
whole sentence for the bundle's, so only the language changes: an authored
255+
message has no parameter contract, so `{variable}` placeholders in it are not
256+
filled, and a key the bundle does not carry falls back to the authored
257+
literal.
258+
259+
The retired top-level `validationMessages` group is **not** what came back.
260+
It was removed in 17.0.0 (#4667) because nothing read it — a translated rule
261+
message was stored and never shown — and that retirement stands. It was keyed
262+
by rule name alone and so could not tell two objects' rules apart; the route
263+
above is object-scoped and shipped with its reader. ADR-0049's 2026-09-04
264+
amendment carries that record.
253265
- **The `flows` group is declared, not yet applied.** A screen flow's copy has
254266
somewhere to live (#7646) and the keys are addressed the way the runner
255267
resolves them — flow name, screen node id, screen field name — but no shipped

‎docs/adr/0049-no-unenforced-security-properties.md‎

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0049: Spec must not declare security properties the runtime does not enforce (enforce-or-remove gate)
22

3-
**Status**: Accepted (2026-06-15) — implemented: fail-closed `DESTRUCTIVE_OPERATIONS` (`packages/plugins/plugin-security/src/permission-evaluator.ts#DESTRUCTIVE_OPERATIONS`), lifecycle bits RBAC-gated, `apiEnabled` enforced (`runtime/src/api-exposure.ts`), `PolicySchema` removed, EXPERIMENTAL tag convention live. Two gate-valid disposition deviations: agent access-control shipped experimental-tagged (not enforced), `flow.runAs` kept + enforced (not removed). `action.disabled` CEL enforcement to confirm in objectui.
3+
**Status**: Accepted (2026-06-15) — implemented: fail-closed `DESTRUCTIVE_OPERATIONS` (`packages/plugins/plugin-security/src/permission-evaluator.ts#DESTRUCTIVE_OPERATIONS`), lifecycle bits RBAC-gated, `apiEnabled` enforced (`runtime/src/api-exposure.ts`), `PolicySchema` removed, EXPERIMENTAL tag convention live. Two gate-valid disposition deviations: agent access-control shipped experimental-tagged (not enforced), `flow.runAs` kept + enforced (not removed). `action.disabled` CEL enforcement to confirm in objectui. **Amended 2026-09-04** (#14402) — recording that an author-written validation rule's `message` became translatable in 17.3.0 (#14381) under the object-scoped bundle key `objects.<object_name>._validations.<rule_name>.message`. That is this gate being **enforced, not reversed**: the 17.0.0 retirement of the top-level `validationMessages` group stands, and the two are not the same route. See **"Amendment (2026-09-04): validation-rule messages are translatable again — enforced, not reversed"** at the end.
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (artifact vs runtime overlay), [ADR-0010](./0010-metadata-protection-model.md) (package provenance), [ADR-0027](./0027-metadata-authoring-lifecycle.md) (authoring lifecycle)
66
**Consumers**: `@objectstack/spec` (security/identity schemas), `@objectstack/plugin-security` (`PermissionEvaluator`, `SecurityPlugin`), spec authors, the metadata-property liveness audit follow-ups (#1878 P0 cluster).
@@ -114,3 +114,55 @@ property — a smaller spec surface is the stronger default pre-MVP.
114114
their respective issues.
115115
- The P1 (ADR-0021 analytics migration) and P2 (spec hygiene) clusters of
116116
#1878 — non-security, governed separately.
117+
118+
---
119+
120+
## Amendment (2026-09-04): validation-rule messages are translatable again — enforced, not reversed
121+
122+
**This records a fact, it does not change the decision.** As of **17.3.0**
123+
(#14381, merged 2026-09-02 — `@objectstack/spec@17.3.0`,
124+
`@objectstack/objectql@17.3.0`), an author-written validation rule's `message`
125+
is translatable through the object-scoped bundle key
126+
`objects.<object_name>._validations.<rule_name>.message`. **The 17.0.0
127+
retirement of the top-level `validationMessages` group (#4667) stands, and the
128+
two are not the same route.**
129+
130+
It is recorded because Prime Directive #13 makes reversing a recorded decision a
131+
decision in its own right, and until now the 17.0.0 retirement lived only in a
132+
retired-key tombstone (the ADR-0087 conversion
133+
`translation-validation-messages-removed`) and a changeset — precisely the shape
134+
#13 warns is easy to miss. It is recorded *here*, as an amendment rather than a
135+
new ADR, because the reader who would be misled is the one already reading this
136+
gate.
137+
138+
### Why this is the gate being enforced, not reversed
139+
140+
- **The policy is enforce-or-remove.** `validationMessages` was removed because
141+
**nothing read it** — a translated rule message was stored and never shown.
142+
Removal was the correct disposition then and is not revisited now.
143+
- **The new key ships with its reader in the same change** — the other half of
144+
the same policy, not an exception to it.
145+
`packages/objectql/src/validation/rule-validator.ts#authoredRuleMessage`
146+
resolves the key as the violation is built, over the existing
147+
`ValidationMessageContext.translate` hook (#3957) that already localizes
148+
built-in messages and field labels. No second i18n path into objectql.
149+
- **It is not the same key.** The retired group was keyed by rule name at the
150+
**top level** and so could not tell two objects' rules apart. The new one is
151+
**object-scoped**, spelled by
152+
`packages/spec/src/system/i18n-resolver.ts#objectValidationMessageKey`, and
153+
sits beside `_views` / `_actions` / `_tabs`.
154+
- **The ADR-0087 conversion is untouched.** The stored-bundle rehydration seam
155+
still strips the old key. Nothing migrates from the retired group into the new
156+
one; an author who wants the new route writes it.
157+
158+
### Scope note
159+
160+
`validationMessages` is a translation key, not a security property, so its
161+
retirement was never part of this ADR's original P0 cluster — the Non-goals
162+
above place the non-security clusters elsewhere. The record lands here because
163+
the repo cites this ADR as the enforce-or-remove policy for spec-property
164+
retirement generally, including in the prescription an author sees on writing
165+
the retired key
166+
(`packages/spec/src/system/translation.zod.ts#TRANSLATION_KEY_GUIDANCE`). **No
167+
new scope is claimed for this ADR by this amendment**; the decision above is
168+
unchanged in every respect.

0 commit comments

Comments
 (0)