Skip to content

Commit 862035a

Browse files
committed
Merge origin/main into claude/issue-28404-override-declaration
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
2 parents 5d79f48 + 52306d4 commit 862035a

7 files changed

Lines changed: 899 additions & 56 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/objectql': patch
3+
---
4+
5+
fix(objectql)!: a tenant's reworded copy of a packaged validation rule is one rule with the package's, run once with the tenant's sentence: the registry fold merges validation rules by name (#27949)
6+
7+
Clause-②: no (narrowing) — a tenant overlay's same-named copy of a packaged or extension-contributed rule no longer runs its own condition: the package's rule runs, with the copy's `message` and `active`. A write the copy's condition alone refused, or that only the package's rule refuses, is judged by the package's rule. Rules of different names are unchanged.
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and no stored row needs rewriting: the fold reads a stored overlay row as it is, taking a copied rule's message and active from it and the rest from the package. Nothing is tombstoned, and an author has no code to change. -->
11+
12+
**BREAKING** for a tenant overlay that copies a packaged rule (`@objectstack/objectql`, served by every object read and the write door through the registry fold). ADR-0029 D9.2a gains a dated note: "validations stay additive" means rules of different names add; a same-named rule merges by name, with the condition owned by the package.
13+
14+
- **Before.** The fold concatenated `validations`. A Studio GET, edit and PUT of an object stores a copy of every rule the read served, so a tenant who reworded an extension's rule held a copy beside the extension's original: both ran, and a refused write reported the rule twice, once in each sentence. A copy of the owner's rule ran as stored, so a later package fix to that rule never reached the tenant.
15+
- **Now.** A rule's identity within an object is its `name`. Two rules of one name are one rule. A tenant overlay's copy of a rule a code layer ships (the owner or an extension) runs as the package declares it, with the copy's `message` and `active`. `active: false` switches the rule off. A refused write reports it once, in the tenant's sentence, and a package fix to the rule reaches the tenant.
16+
- **A copy that changes more than `message` and `active`** (its `type`, its condition, or any other key the package owns) does not run its own version. The package's rule runs, and the registry names the copy once at `error` (`validation_rule_copy_changed`), with the prescription: to change the condition, add a rule under a new name, or change it in the package. Loading a stored copy only reports it and never fails the boot or an upgrade. `SchemaRegistry.tenantRuleCopyRefusal(name, body)` returns that refusal (`ValidationRuleCopyRefusedError`, `INVALID_METADATA`, 422) for a body before it is stored; refusing such a save at the metadata save door is a follow-up change.
17+
- **Unchanged.** A rule of a new name, the tenant's or an extension's, is added. A rule the overlay leaves out stays out. Between two code layers a later layer's same-named rule replaces the earlier one, as a field does. `getPackagedObjectBase` serves the packaged rules merged by name the same way.
18+
- **Who sees it.** A tenant whose overlay rewords or switches off a packaged rule: the rule now runs once. A tenant whose overlay changed a packaged rule's condition: the package's condition runs instead, and the boot log names the rule. To keep a different condition, add it as a rule under a new name.

‎docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,42 @@ Binding consequences:
540540
were considered and **rejected**. Dropping scalars from the fold entirely
541541
(#8284's arm B) remains rejected and is not this clause.
542542

543+
> **Note (2026-10-30, #27949; ruling of record 6524229156, letter C′; where the
544+
> refusal sits: ruling of record 6545641964, letter B, 2026-10-31): "validations
545+
> stay additive" means rules of DIFFERENT names add. A same-named rule merges by
546+
> name, with the condition owned by the package.**
547+
>
548+
> A validation rule's identity within an object is its `name` (the schema's
549+
> "unique rule name"). The fold therefore merges `validations` by name across the
550+
> layers, the way `fields` merge by key, instead of concatenating them: two rules
551+
> of one name in one object are one rule. Measured before this note: a tenant
552+
> overlay that reworded an extension's rule (the Studio GET → edit → PUT of the
553+
> folded object stores a copy of every rule it served) held a copy beside the
554+
> extension's original, and both ran, so a refused write reported the rule twice.
555+
>
556+
> - **What the tenant owns.** A tenant overlay's copy of a packaged or
557+
> extension-contributed rule supplies the rule's `message` and `active`
558+
> (`active: false` is the explicit, visible deactivation). Everything else —
559+
> the `type`, the condition and every other key the evaluator reads — is the
560+
> package's: the fold enforces the code layer's rule with the tenant's
561+
> sentence and switch, so a later package fix to the rule reaches the tenant.
562+
> This is the opposite of whole-rule replacement, which would freeze the
563+
> enforced rule against package fixes.
564+
> - **A changed condition or type is refused, loudly, at the save door, with the
565+
> prescription "to change the condition, add a rule under a new name, or change
566+
> it in the package"** — never two rules running beside each other, never a
567+
> silent replacement. The overlay LOAD path never refuses: a copy stored before
568+
> the condition changed (a package upgrade) is indistinguishable from one the
569+
> tenant changed, so the fold never runs a copy's condition and names each such
570+
> copy once, at `error`, with that prescription; it never drops the tenant's
571+
> layer for it, so an upgrade never fails on stored data.
572+
> - **Different names stay additive**, as this clause has always protected: an
573+
> extender's or a tenant's rule of a new name is added. A rule the overlay
574+
> leaves out stays out, as D9.2's replace means. Between two code layers a
575+
> later layer's same-named rule replaces the earlier one, as a field does.
576+
> - **Idempotence (#8027) holds by the merge itself:** a folded base re-folds to
577+
> the same rules, so `validations` are no longer subtracted before the fold.
578+
543579
#### D9.3 — selection is by KIND; priority stays descriptive
544580

545581
`contributors.sort((a, b) => a.priority - b.priority)` (`#contributors`) totals the whole

0 commit comments

Comments
 (0)