Skip to content

Commit 06225c3

Browse files
committed
docs(spec): adopt JSON Merge Patch (RFC 7396) for x-gts-traits and add ADR-0004
- Replace prior trait-value merge rule in README §9.7 with chain-applied JSON Merge Patch (RFC 7396) along the $id chain: descendant-last-wins at leaves, nested objects merge recursively, arrays replace wholesale, `null` deletes the key. - Make standard JSON Schema `const` in x-gts-traits-schema the publisher's lock mechanism (no GTS-specific immutability rule); a descendant that attempts to override a const-locked value fails ordinary JSON Schema validation against the effective trait-schema. - Document the principal use of `null` as a fallback to the trait-schema's `default` via ADR-0003 materialization, including the failure mode when the deleted key is required-without-default. - Update OP#13 description to drop the obsolete immutability sentence and point at `const` as the lock mechanism. - Add ADR-0004 with drivers, alternatives (no-merge, shallow last-wins, shallow immutable-once-set, RFC 7396, per-property keyword), decision, worked examples (override / nested merge / const lock / null-to-default fallback), edge cases, and conformance expectations. Signed-off-by: Aviator 5 <ai.agent.tor@gmail.com>
1 parent 4b320a1 commit 06225c3

2 files changed

Lines changed: 538 additions & 9 deletions

File tree

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,7 @@ Implementation notes:
13351335

13361336
### 9.7 - GTS Type Schema Traits (`x-gts-traits-schema` / `x-gts-traits`)
13371337

1338-
**OP#13 - Schema Traits Validation**: Validate that `x-gts-traits` values in derived schemas conform to the `x-gts-traits-schema` defined in their base schemas. Verify that, for non-abstract types, all required trait properties in the effective trait-schema are resolved (via explicit value in the chain-merged `x-gts-traits` or via `default` in the effective trait-schema), and that trait values satisfy the effective trait-schema's other constraints. Trait values set by an ancestor are immutable — descendants MUST NOT override them with a different value. Both `x-gts-traits-schema` and `x-gts-traits` are schema-only keywords and MUST NOT appear in instances. `x-gts-traits-schema` MUST be a valid JSON Schema subschema (object, `true`, or `false`). Uses the same validation endpoints (`/validate-type-schema`, `/validate-entity`).
1338+
**OP#13 - Schema Traits Validation**: Validate that `x-gts-traits` values in derived schemas conform to the `x-gts-traits-schema` defined in their base schemas. Verify that, for non-abstract types, all required trait properties in the effective trait-schema are resolved (via explicit value in the chain-merged `x-gts-traits` or via `default` in the effective trait-schema), and that the chain-merged trait values satisfy the effective trait-schema's other constraints (including `const`, which a publisher uses to lock individual trait values across descendants — see §9.7.5). Both `x-gts-traits-schema` and `x-gts-traits` are schema-only keywords and MUST NOT appear in instances. `x-gts-traits-schema` MUST be a valid JSON Schema subschema (object, `true`, or `false`). Uses the same validation endpoints (`/validate-type-schema`, `/validate-entity`).
13391339

13401340
A **schema trait** is a semantic annotation attached to a GTS Type Schema that describes **system behaviour** for processing instances of that type. Traits are not part of the object data model — they do not define instance properties. Instead, they configure cross-cutting concerns such as:
13411341

@@ -1506,23 +1506,27 @@ Given an inheritance chain `S₀ → S₁ → … → Sₙ`:
15061506
- **Immutable defaults:** `default` values declared in an ancestor's `x-gts-traits-schema` MUST NOT be changed by a descendant's `x-gts-traits-schema`. If a descendant redeclares a trait property with a different `default`, schema validation MUST fail.
15071507

15081508
- **Trait value merge**
1509-
- The registry MUST build an *effective traits object* by collecting all `x-gts-traits` objects encountered in the chain (left-to-right).
1510-
- **Immutable-once-set:** Once a trait key is assigned a concrete value by a schema in the chain, **no descendant may override it**. If a descendant's `x-gts-traits` provides a different value for a key already set by an ancestor, schema validation MUST fail. Providing the **same** value is permitted (idempotent).
1511-
- Defaults declared in the effective trait schema SHOULD be used as normal JSON Schema defaults to produce a complete effective traits object.
1509+
- The registry MUST build an *effective traits object* by walking the type's `$id` chain root → leaf and applying each layer's `x-gts-traits` as a [JSON Merge Patch (RFC 7396)](https://datatracker.ietf.org/doc/html/rfc7396) against the chain-merged object so far. Top-level scalar / array / `null` leaves are overwritten by the descendant (last-wins). Object-valued top-level traits merge **recursively** — fields of an ancestor's object trait that the descendant does not restate are preserved.
1510+
- **Arrays replace wholesale** at any depth (per RFC 7396). Authors who need item-level composability SHOULD model the data as a keyed object instead of an array.
1511+
- **`null` at any depth deletes that key** from the effective object (per RFC 7396). The principal use case is to revert an ancestor-set value and let the trait-schema's `default` re-apply via the materialization step described in the Completeness check below — that is, a descendant writes `"<key>": null` to "fall back to the schema default" without picking a specific value. If the deleted key is `required` and has no `default`, the completeness check fails registration for non-abstract types (the descendant must then either mark itself abstract or accept that "delete + required + no default" is an unresolvable contract). Authors who want `null` as an *intended* trait value cannot express it via this merge and must use a sentinel value documented as part of the trait shape.
1512+
- Defaults declared in the effective trait-schema SHOULD be applied (per ADR-0003 materialization) for top-level keys not present in the chain-merged object.
1513+
- A publisher who wants a trait value to be **locked** across all descendants of a base type SHOULD declare `"const": <value>` for that property in `x-gts-traits-schema`. A descendant attempting to override the value will fail the standard JSON Schema validation that runs against the effective trait-schema (per the Completeness check below). No GTS-specific "immutability" rule is required — `const` is the mechanism.
1514+
- A descendant MAY redeclare a trait value with the same value the ancestor already declared (idempotent restatement).
1515+
- See [`adr/0004-x-gts-traits-merge-strategy.md`](adr/0004-x-gts-traits-merge-strategy.md) for the rationale.
15121516

15131517
- **Validation**
15141518
- **Completeness check** (registration-time): For types whose `x-gts-abstract` is not `true`, the registry MUST verify that the *materialized* effective traits object validates against the effective trait-schema using standard JSON Schema validation. "Materialized" means: defaults declared in the effective trait-schema for properties not present in the chain-merged effective traits object are substituted in before validation. If validation fails — in particular, if a `required` property of the effective trait-schema has no chain-assigned value and no default — registration MUST fail. For types with `x-gts-abstract: true`, this completeness check is skipped; descendants are expected to close any unresolved required traits. See [`adr/0003-x-gts-traits-completeness.md`](adr/0003-x-gts-traits-completeness.md) for the rationale.
15151519
- If the effective trait schema cannot be satisfied (e.g., contradictory constraints introduced across the chain), schema validation MUST fail.
1516-
- If a descendant attempts to override a trait value already set by an ancestor with a different value, schema validation MUST fail.
15171520

1518-
**Example — immutable trait override (failure):**
1521+
**Example — descendant override and `const` lock:**
15191522

15201523
Consider a 3-level chain: `base → audit_event → most_derived_event`.
15211524

1522-
- `audit_event` sets `x-gts-traits.topicRef` to `gts.x.core.events.topic.v1~x.core._.audit.v1`
1523-
- `most_derived_event` attempts to set `x-gts-traits.topicRef` to `gts.x.core.events.topic.v1~x.core._.notification.v1`
1525+
- `base.x-gts-traits-schema.properties.indexed.const = true` — the publisher locks `indexed`.
1526+
- `audit_event.x-gts-traits` sets `topicRef = gts.x.core.events.topic.v1~x.core._.audit.v1`.
1527+
- `most_derived_event.x-gts-traits` sets `topicRef = gts.x.core.events.topic.v1~x.core._.notification.v1`.
15241528

1525-
Validation of `most_derived_event` MUST fail because `topicRef` was already set by `audit_event` and the new value differs.
1529+
Effective traits for `most_derived_event`: `{ "indexed": <chain-derived true>, "topicRef": ".../notification.v1" }`. The override of `topicRef` is permitted (last-wins). If `most_derived_event` also tried to set `"indexed": false`, registration would fail — not because of a GTS-specific immutability rule, but because the materialized effective traits object would not satisfy the `const: true` constraint declared on `indexed` in the effective trait-schema.
15261530

15271531
These rules are intentionally aligned with existing JSON Schema composition semantics and GTS schema chaining practices.
15281532

0 commit comments

Comments
 (0)