|
1 | 1 | # ADR-0049: Spec must not declare security properties the runtime does not enforce (enforce-or-remove gate) |
2 | 2 |
|
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. |
4 | 4 | **Deciders**: ObjectStack Protocol Architects |
5 | 5 | **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) |
6 | 6 | **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. |
114 | 114 | their respective issues. |
115 | 115 | - The P1 (ADR-0021 analytics migration) and P2 (spec hygiene) clusters of |
116 | 116 | #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