VERSION: GTS specification draft, version 0.12
This document defines GTS — a simple, human-readable, globally unique identifier and referencing system for data type definitions (e.g., JSON Schemas) and data instances (e.g., JSON objects). It is specification-first, language-agnostic, and intentionally minimal, with primary focus on JSON and JSON Schema.
Format Support: GTS Types and GTS Instances can be represented in multiple formats including JSON, YAML, and TypeSpec. See the examples directory for demonstrations in different formats.
The GTS identifiers are strings in a format like:
gts.<vendor>.<package>.<namespace>.<type>.v<MAJOR>[.<MINOR>]
They can be used instead of UUID, ULID, URN, JSON Schema URL, XML Namespace URI, or other notations for identification of various objects and schema definitions like:
- API data types and typed payloads (e.g. custom resource attributes)
- RPC contracts schemas
- API errors, headers and various semantics definitions
- Event catalogs, messages and stream topics
- Workflow categories and instances
- FaaS functions or actions contract definitions
- Policy objects (RBAC/ABAC/IAM)
- UI elements, schemas and forms
- Observability payloads (e.g. log formats)
- IoT/Edge telemetry data (e.g. device message formats)
- Warehouse/lake schemas
- Enumerations and references (e.g. enum id + description)
- ML/AI artifacts (e.g. model metadata or MCP tools declarations)
- Configuration-as-data templates and config instances
- Testing artifacts (e.g. golden records and fixtures)
- Database schemas
- Compliance and audit objects
Besides being a universal identifier, GTS provides concrete, production-ready capabilities that solve common architectural challenges for platform vendors and service providers integrating multiple third-party services under single control plane. In particular:
- Extensible plugin architectures: Third-party vendors can safely extend platform base types such as custom API fields, events, settings, configs, UI elements, user roles, licensing, etc.
- Cross-vendor type safety: Validate contracts (APIs, events, configs, etc.) across multiple vendors with automated compatibility checking in a middleware layer
- Hybrid database storage: Store base type fields in indexed columns for fast queries, vendor-specific extensions in JSON/JSONB—no schema migrations needed
- Granular access control: Use wildcard patterns and attribute-based policies for fine-grained type-based authorization (ABAC) without maintaining explicit lists
- Human-readable debugging: Identifiers encode vendor, package, namespace, and version—instantly comprehensible in logs and traces
- Schema evolution without downtime: Add optional fields, register new derived types, and deploy producers/consumers independently
See the Practical Benefits for Service and Platform Vendors section for more details.
- Global Type System (GTS) Specification
- Terminology
- 1. Motivation
- 2. Identifier Format
- 3. Semantics and Capabilities
- 4. GTS Identifier Versions Compatibility
- 5. Typical Use-cases
- 6. Implementation-defined and Non-goals
- 7. Comparison with other identifiers
- 8. Parsing and Validation
- 9. Reference Implementation Recommendations
- 9.1 Identifier reference in JSON and JSON Schema
- 9.2 GTS operations (OP#1 - OP#13)
- 9.3 GTS entities registration
- 9.4 CLI support
- 9.5 Web server with OpenAPI
- 9.6
x-gts-refsupport - 9.7 GTS Type Schema Traits (
x-gts-traits-schema/x-gts-traits) - 9.8 YAML support
- 9.9 TypeSpec support
- 9.10 UUID as object IDs
- 9.11 GTS Type Schema Modifiers (
x-gts-final/x-gts-abstract)
- 10. Collecting Identifiers with Wildcards
- 11. JSON and JSON Schema Conventions
- 12. Notes and Best Practices
- 13. Testing
- 14. Registered Vendors
| Ver | Status |
|---|---|
| 0.1 | Initial Draft, Request for Comments |
| 0.2 | Semantics and Capabilities refined - access control notes, query language, attribute selector |
| 0.3 | Version compatibility rules refined; more practical examples of usage; remove Python examples |
| 0.4 | Clarify some corner cases - tokens must not start with digit, uuid5, minor version semantic |
| 0.5 | Added Referece Implmenetation recommendations (section 9) |
| 0.6 | Introduced well-known/anonymous instance term; defined field naming implementation recommendations |
| 0.7 | BREAKING: require $ref value to start with 'gts://'; strict rules for schema/instance distinction; prohibiting well-known instances without left-hand type segments |
| 0.8beta1 | Add OP#12 (schema vs schema validation), unified validation endpoint (/validate-entity), and clarify instance -> schema and schema -> schema validation semantics for chained GTS IDs |
| 0.8beta2 | Introduce schema traits (x-gts-traits-schema, x-gts-traits) and OP#13 (schema traits validation) |
| 0.8 | Add alternate combined anonymous instance identifier format |
| 0.9 | Add x-gts-final and x-gts-abstract schema modifiers; enforce final/abstract semantics in OP#6 and OP#12 |
| 0.10 | BREAKING: terminology unified around GTS Type / GTS Instance; rename API fields schema_id → type_id (also old_schema_id/new_schema_id/to_schema_id/selected_schema_id_field); rename API field is_schema → is_type (type-definition vs instance discriminator); type_id MUST be a GTS Type Identifier or null — no longer falls back to JSON Schema dialect URL; rename endpoints /validate-schema → /validate-type, /schemas → /types; rename OP#12 'Schema vs Schema Validation' → 'Type Derivation Validation'; rename OpenAPI components ValidateSchemaRequest → ValidateTypeRequest, SchemaRegister → TypeRegister; rename example directories examples/**/schemas/ → examples/**/types/ (file extensions .schema.json retained); add Terminology section |
| 0.11 | Introduce term GTS Type Schema as the canonical definition of a GTS Type; remove the standalone Schema term from Terminology; rewrite GTS Type entry to name the abstract registered entity; rename GTS Type Registry → GTS Registry (registry now scopes both Type Schemas and well-known Instances). Conformance tests for reference implementations also updated: rename API endpoints /validate-type → /validate-type-schema and /types → /type-schemas; rename OpenAPI components TypeRegister → TypeSchemaRegister, ValidateTypeRequest → ValidateTypeSchemaRequest; rename request field TypeSchemaRegister.schema → TypeSchemaRegister.type_schema; rename helper validate_type → validate_type_schema. |
| 0.12 | BREAKING: reframe GTS Type Schemas as a dialect-agnostic JSON Schema extension; the prior $defs MUST NOT and post-Draft-07-keyword restrictions are dropped; derivation compatibility and the finality guard use the chained $id alone, allOf+$ref recommended but not required (ADR-0001). x-gts-traits-schema becomes a JSON Schema subschema (object/true/false); the registry chain-aggregates declarations along the $id chain via allOf (ADR-0002). Trait completeness is keyed on x-gts-abstract and enforced on non-abstract types against the materialized effective traits object (ADR-0003). Trait-value merge follows JSON Merge Patch (RFC 7396); cross-descendant locking moves to standard JSON Schema const in x-gts-traits-schema (ADR-0004). The four document-level keywords (x-gts-final, x-gts-abstract, x-gts-traits-schema, x-gts-traits) MUST appear at the schema top level and are rejected (fail fast) when nested in a subschema (§9.7.1, §9.11). |
This specification uses the following terms with precise meanings:
-
GTS Type: a type entity identified by a GTS Type Identifier and defined by a GTS Type Schema. A GTS Type may exist as a standalone document (e.g., a
*.schema.jsonfile), be exchanged between systems, or be stored in a GTS Registry. -
GTS Type Identifier: a canonical GTS identifier ending with
~that identifies a GTS Type. -
GTS Type Schema: the canonical definition of a GTS Type — a JSON Schema document annotated with the GTS-specific keywords (
x-gts-*), describing the type's instance shape, traits, and derivation.Implementations MAY accept alternative source forms (e.g., TypeSpec, YAML) provided they deterministically map to a canonical GTS Type Schema. The canonical form, used for interchange, validation, and registration, is the JSON Schema document.
-
GTS Registry: a registry that stores and resolves GTS entities — Type Schemas and well-known Instances — by GTS Identifier.
-
GTS Instance: a concrete object, value, or document that conforms to a GTS Type.
-
GTS Instance Identifier: a GTS identifier without the trailing
~, used to identify a well-known instance.
The proliferation of distributed systems, microservices, and event-driven architectures has created a significant challenge in maintaining data integrity, system interoperability, and type governance across organizational boundaries and technology stacks.
Existing identification methods—such as opaque UUIDs, simple URLs (e.g. JSON Schema URLs), or proprietary naming conventions—fail to address the full spectrum of modern data management requirements. The Global Type System (GTS) is designed to solve these systemic issues by providing a simple, structured, and self-describing mechanism for identifying and referencing GTS Types and GTS Instances.
The primary value of GTS is to provide a single, universal identifier that is immediately useful for:
Human- and Machine-Readable: GTS identifiers are semantically meaningful, incorporating vendor, package, namespace, and version information directly into the ID. This makes them instantly comprehensible to developers, architects, and automated systems for logging, tracing, and debugging.
Vendor and Domain Agnostic: By supporting explicit vendor registration, GTS facilitates safe, cross-vendor data exchange (e.g., in event buses or plugin systems) while preventing naming collisions and ensuring the origin of a definition is clear.
Explicit Type/Instance Distinction: The GTS naming format clearly separates a GTS Type from a concrete GTS Instance, enabling unambiguous type resolution and validation.
Inheritance and Conformance Lineage: The chained identifier system provides a robust, first-class mechanism for expressing type derivation and instance conformance. This is critical for ecosystems where third-parties must safely extend core types while guaranteeing compatibility with the base schema.
Built-in Version Compatibility: By adopting a constrained Semantic Versioning model, GTS inherently supports automated compatibility checking across minor versions. This simplifies data casting (upcast/downcast), allowing consumers to safely process data from newer schema versions without breaking.
Granular Access Control: The structured nature of the identifier enables the creation of coarse-grained access control policies using wildcard matching (e.g., granting a service permission to process all events from a specific vendor/package: gts.myvendor.accounting.*).
Deterministic Opaque IDs: GTS supports the deterministic derivation of UUIDs (v5), providing a stable, fixed-length key for database indexing and external system APIs where human-readability is not required, while maintaining a clear, auditable link back to the source type.
Specification-First: As a language- and format-agnostic specification (though prioritizing JSON/JSON Schema), GTS provides a stable foundation upon which robust, interchangeable validation and parsing tools can be built across any ecosystem.
GTS identifiers name either a GTS Type or a GTS Instance. A single GTS identifier may also chain multiple identifiers to express inheritance/compatibility and an instance’s conformance lineage.
The GTS identifier is a string with total length of 1024 characters maximum.
- A single GTS Type Identifier:
gts.<vendor>.<package>.<namespace>.<type>.v<MAJOR>[.<MINOR>]~- Note the trailing
~to denote a GTS Type Identifier.
- A single GTS Instance Identifier (object of given type):
- Well-known instance:
gts.<vendor>.<package>.<namespace>.<type>.v<MAJOR>[.<MINOR>]~<vendor>.<package>.<namespace>.<type>.v<MAJOR>[.<MINOR>] - Combined anonymous instance:
gts.<vendor>.<package>.<namespace>.<type>.v<MAJOR>[.<MINOR>]~<UUID> - Well-known and combined anonymous instance identifiers MUST include a left-hand type segment in a chain (see 2.2 and 3.7).
- Combined anonymous instance identifiers MUST include a UUID tail.
- Note: no trailing
~for instances.
- Well-known instance:
The <vendor> refers to a string code that indicates the origin of a given schema or instance definition. This can be valuable in systems that support cross-vendor data exchange, such as events or configuration files, especially in environments with deployable applications or plugins.
The <package> notation defines a module, plugin, or application provided by the vendor that contains the specified GTS definition.
The <namespace> specifies a category of GTS definitions within the package, and finally, the <type> defines the specific object type.
Segments must be lowercase ASCII letters, digits, and underscores; they must start with a letter or underscore: [a-z_][a-z0-9_]*. The single underscore _ is reserved as a placeholder and may only be used for the <namespace> segment.
The <vendor>, <package>, <namespace>, and <type> segment tokens must not start with a digit.
Versioning uses semantic versioning constrained to major and optional minor: v<MAJOR>[.<MINOR>] where <MAJOR> and <MINOR> are non-negative integers, for example:
gts.x.core.events.type.v1~- defines a base event type in the systemgts.x.core.events.type.v1.2~- defines a specific edition v1.2 of the base event type
The <UUID> is a 128-bit identifier (e.g., a UUID v5) that is used to identify a specific anonymous instance of a type. It is generated using a deterministic algorithm based on the type identifier and the instance data.
Examples - The GTS identifier can be used for instance or type identifiers:
gts.x.idp.users.user.v1.0~ # defines ID of a schema of the user objects provided by vendor 'x' in scope of the package 'idp'
gts.x.mq.events.topic.v1~ # defines ID of a schema of the MQ topic stream provided by vendor 'x' in scope of the 'mq' (message queue) package
Multiple GTS identifiers can be chained with ~ to express derivation and conformance. The chain follows left-to-right inheritance semantics:
- Pattern:
gts.<segment1>~<segment2>~<segment3> - Where is a single GTS identifier segment:
<vendor>.<package>.<namespace>.<type>.v<MAJOR>[.<MINOR>]<segment1>is a base type (GTS Type Identifier ending with~)<segment2>is a derived/refined type (GTS Type Identifier ending with~) that extends<segment1>with additional constraints or implementation-specific details. It MUST be compatible with<segment1>.<segment3>is a GTS Instance Identifier (no trailing~) that conforms to<segment2>. By transitivity, it also conforms to<segment1>.
Important: Each type in the chain inherits from its immediate predecessor (left neighbor) and MUST maintain compatibility.
Chaining rules:
- All elements except the rightmost MUST be type identifiers (conceptually ending with
~). - The rightmost element determines the identifier's nature:
- Ends with
~→ the whole identifier represents a GTS Type. - No trailing
~→ the whole identifier represents a GTS Instance.
- Ends with
- The
gts.prefix appears only once at the very beginning of the identifier string. - Segments after the first are considered relative identifiers and do not repeat the
gts.prefix. (e.g.,gts.x.some.base.type.v1~vendor.app.some.derived.v1~). - Use
_as a placeholder when the namespace is not applicable
Examples with explanations:
# Base type only (standalone schema)
gts.x.core.events.type.v1~
# Type `ven.app._.custom_event.v1` derives from base type `gts.x.core.events.type.v1`. Both are schemas (trailing `~`).
gts.x.core.events.type.v1~ven.app._.custom_event.v1~
# Instance `ven.app._.custom_event_topic.v1.2` (no trailing `~`) conforms to type `gts.x.core.events.topic.v1`. The identifier shows the full inheritance chain.
gts.x.core.events.topic.v1~ven.app._.custom_event_topic.v1.2The complete GTS identifier syntax in Extended Backus-Naur Form (EBNF):
(* Top-level identifier *)
gts-identifier = "gts." , gts-segment , ( chain-suffix-type | chain-suffix-instance | chain-suffix-anon-instance ) ;
(* Chained type ID ends with ~ *)
chain-suffix-type = { "~" , gts-segment } , "~" ;
(* Chained instance ID MUST have at least one tilde separator and NO trailing tilde *)
chain-suffix-instance = "~" , gts-segment , { "~" , gts-segment } ;
(* Combined anonymous instance ID ends with a UUID tail and NO trailing tilde *)
chain-suffix-anon-instance = { "~" , gts-segment } , "~" , uuid ;
(* Single GTS ID segment *)
gts-segment = vendor , "." , package , "." , namespace , "." , type , "." , version ;
vendor = segment ;
package = segment ;
namespace = segment ;
type = segment ;
(* Segment: lowercase letters, digits, underscores; starts with letter or underscore *)
segment = ( letter | "_" ) , { letter | digit | "_" } ;
(* Version: major.minor or major only *)
version = "v" , major , [ "." , minor ] ;
major = "0" | positive-integer ;
minor = "0" | positive-integer ;
(* Primitives *)
positive-integer = non-zero-digit , { digit } ;
letter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j"
| "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t"
| "u" | "v" | "w" | "x" | "y" | "z" ;
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
non-zero-digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
(* UUID tail for combined anonymous instance identifiers *)
hex-digit = digit | "a" | "b" | "c" | "d" | "e" | "f" ;
uuid = 8hex , "-" , 4hex , "-" , 4hex , "-" , 4hex , "-" , 12hex ;
8hex = hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit ;
4hex = hex-digit , hex-digit , hex-digit , hex-digit ;
12hex = hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit , hex-digit ;Grammar notes:
-
Type vs Instance distinction: A GTS identifier ending with
~(final-tilde present) denotes a GTS Type. Without the trailing~, it denotes a GTS Instance. -
Chain interpretation: In a chained identifier
gts.<gts-segment1>~<gts-segment2>~<gts-segment3>, each~acts as a separator. All segments before the final segment MUST be types (conceptually ending with~). The final segment determines whether the entire identifier is a type or instance. -
Combined anonymous instance: In a chained identifier of the form
gts.<gts-segment1>~...~<uuid>, the UUID is the instance identifier tail. All segments before the UUID MUST be types (conceptually ending with~). -
Placeholder rule: Use
_(underscore) as a segment value when the namespace is not applicable. It is recommended to use the placeholder only for the<namespace>segment. -
Normalization: GTS identifiers must be lowercase. Leading/trailing whitespace is not permitted. Canonical form has no optional spacing.
-
Reserved prefix: The
gts.prefix is mandatory and reserved. Future versions may introduce alternative prefixes but will maintain backward compatibility.
GTS identifiers enable the following operations and use cases:
-
Global Identification: Uniquely identify data types (JSON Schemas) and data instances (JSON objects) in a human-readable format across systems and vendors.
-
Schema Resolution and Validation:
- For a type identifier (ending with
~): resolve to the JSON Schema definition - For an instance identifier: extract the rightmost type from the chain and validate the object against that schema
- Chain validation: optionally verify that each type in the chain is compatible with its predecessor
- For a type identifier (ending with
GTS identifiers may be chained (e.g. gts.A~B~C). Validation MUST respect the left-to-right inheritance model and MUST preserve the compatibility guarantee (section 3.2).
-
Instance → schema validation (validate an object instance):
- The system MUST resolve the rightmost type in the identifier chain and validate the instance payload against that JSON Schema.
- If the instance identifier includes a type chain (
A~B~<instance>), validating against the rightmost typeBMUST also imply conformance to all base types in the chain (by transitivity). - Implementations MAY additionally validate that each adjacent type pair in the chain is compatible (schema→schema validation), but the primary runtime instance validation target is always the rightmost type.
-
Schema → schema validation (validate a derived schema against its predecessor schema):
- Given a derived type identifier chain (e.g.
A~B~orA~B~C~), the system MUST validate that each derived schema is compatible with its immediate predecessor in the chain. - The compatibility rule is: every valid instance of the derived schema MUST also be a valid instance of the base schema.
- The derived schema MUST be written such that it does not invalidate this compatibility guarantee, regardless of how the parent's constraints are expressed: via
allOfwith a$refto the parent (recommended, to avoid duplication of parent fields) or by re-declaring the parent's fields directly in the derived schema. See §11.0 for how GTS extends JSON Schema andadr/0001-derivation-form.mdfor the full discussion.
- Given a derived type identifier chain (e.g.
-
additionalPropertiesand adding new properties:- If a base schema (or any schema in the inheritance chain) defines an object with
additionalProperties: false, then derived schemas MUST NOT introduce new properties at that object level that would be rejected by the base schema. - Derived schemas MAY still tighten constraints of existing properties (e.g. reduce
maxLength, narrowenum, increaseminimum) and MAY further specify previously-open nested objects (e.g. basepayload: {"type":"object"}and derived definespayload.properties).
- If a base schema (or any schema in the inheritance chain) defines an object with
-
Version Compatibility Checking: Automatically determine if schemas with different MINOR versions are compatible (see section 4).
-
Access Control Policies: Build fine-grained or coarse-grained authorization rules using:
- Exact identifier matching
- Wildcard patterns (e.g.,
gts.vendor.package.*) - Chain-based isolation (e.g., restrict access to specific vendor's extensions)
- See also ABAC use-cases in sections 3.3 and 3.4 below
-
Extensible Type Systems: Enable platforms where:
- Base system types are defined by a core vendor
- Third-party vendors extend base types with additional constraints
- All validation guarantees are preserved through the inheritance chain
- Type evolution is tracked explicitly through versioning
GTS chained identifiers express type derivation through left-to-right inheritance. In a chain like gts.A~B~C:
- Type
Bextends typeAby adding constraints or refining field definitions - Type
Cfurther extends typeBin the same manner - Each derived type MUST be fully compatible with its predecessor (see section 4.3)
Compatibility guarantee: Every valid instance of a derived type is also a valid instance of all its base types in the chain. This means:
- An instance conforming to
Calso conforms toBandA - Validation against the rightmost type automatically ensures conformance to all base types
- Derived types can only add optional fields (in open models), tighten constraints, or provide more specific definitions—never break base type contracts
This inheritance model enables safe extensibility: third-party vendors can extend platform base types while maintaining full compatibility with the core system.
Schema modifiers: Schemas may optionally declare "x-gts-final": true to prohibit further derivation, or "x-gts-abstract": true to require that instances use a concrete derived type rather than the base type directly. See section 9.11 for full semantics.
Implementation pattern: Hybrid storage for extensible schemas
GTS types inheritance enables a powerful database design pattern that combines structured storage for base type fields with flexible JSON storage for derived type extensions. This approach provides:
- Query performance: Index and query base type fields using native database types
- Extensibility: Store vendor-specific extensions as JSON/JSONB without schema migrations
- Type safety: Validate all data against registered GTS schemas before storage
- Multi-tenancy: Support multiple vendors extending the same base type in a single table
Example: Event Management Platform
A platform defines a base event schema with common fields:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "gts://gts.x.core.events.type.v1~",
"type": "object",
"properties": {
"id": { "type": "string" },
"typeId": { "type": "string" },
"occurredAt": { "type": "string", "format": "date-time" },
"payload": { "type": "object", "additionalProperties": true }
},
"required": ["id", "typeId", "occurredAt", "payload"]
}A third-party vendor (ABC) registers a derived event type for order placement:
The platform stores all events in a single table using hybrid storage:
CREATE TABLE events (
id VARCHAR(255) PRIMARY KEY, -- Indexed for fast event fetch by ID
type_id VARCHAR(255) NOT NULL, -- Indexed for filtering by event type
occurred_at TIMESTAMP NOT NULL, -- Indexed for time-range queries
payload JSONB NOT NULL, -- Vendor-specific extensions stored as JSON
INDEX idx_type_occurred (type_id, occurred_at)
);Benefits of this approach:
- No schema migrations: New event types are registered via GTS schemas, not database DDL
- Efficient queries: Filter by
type_idoroccurred_atusing indexes, then parsepayloadonly for matching rows - Vendor isolation: Use
type_idpatterns (e.g.,gts.x.core.events.type.v1~abc.*) for access control (see 3.5) - Full validation: All events are validated against their registered GTS schema before insertion, ensuring data quality despite flexible storage
GTS Query Language is a compact predicate syntax, inspired by XPath/JSONPath, that lets you constrain results by attributes. Attach a square-bracketed clause to a GTS identifier containing name="value" pairs separated by commas. Example form: [ attr="value", other="value2" ].
Scope note: The query language and attribute selector (section 3.4) are runtime conveniences for filtering and accessing data in GTS-aware applications. They are not part of the core GTS identifier specification and should not be embedded in stored identifiers or schema definitions. Use them only in runtime queries, policy evaluation, and data access operations.
Predicates can reference plain literals or GTS-formatted values, e.g.:
# filter all events that are published to the topic "some_topic" by the vendor "z"
gts.x.core.events.type.v1~[type_id="gts.x.core.events.topic.v1~z.app._.some_topic.v1~"]
# filter all user settings that were defined for users if type is z-vendor app_admin
gts.x.core.acm.user_setting.v1~[user_type="gts.x.core.acm.user.v1~z.app._.app_admin.v1~"]Multiple parameters are combined with logical AND to further restrict the result set:
gts.x.y.z.type.v1~[foo="bar", id="ef275d2b-9f21-4856-8c3b-5b5445dba17d"]GTS includes a lightweight attribute accessor, akin to JSONPath dot notation, to read a single value from a bound instance. Append @ to the identifier and provide a property path, e.g., @..
The selector always resolves from the instance root and returns one attribute per query. For example:
# refer to the value of the message identifier
gts.x.y.z.message.v1@idNested attributes also can be accessed within the instance's structure. For example:
# refer to the value of the 'bar' item property from the 'foo' field
gts.x.y.z.message.v1.0@foo.barWildcards (*) enable policy scopes that cover families of identifiers (e.g., entire vendor/package trees) rather than single, exact instance or schema IDs. This is useful in RBAC/ABAC style engines and relationship-based systems (e.g., Zanzibar-like models) where permissions are expressed over sets of resources.
# grants access to all the audit events category defined by the vendor 'xyz'
gts.x.core.events.type.v1~x.core._.audit_event.v1~xyz.*
# grants access to all the menu items referring screens of the vendor 'abc'
gts.x.ui.left_menu.menu_item.v1[screen_type="gts.x.ui.core_ui.screens.v1~abc.*"]Scope disclaimer: GTS-based access control implementation is outside the scope of this specification and will vary across systems. GTS provides the syntax to express authorization rules; however, different policy engines may apply different evaluation strategies or may not support attribute-based or wildcard-based access control at all.
The following guidance is provided for implementers building GTS-aware policy engines:
Policy management domain model:
- Principal: Users, services, or groups (outside the scope of GTS) mapped to roles.
- Resource: GTS identifiers or patterns (with wildcards and, optionally, attribute predicates) that denote types or instances.
- Action: Verbs such as
read,write,emit,subscribe,admindefined by the platform.
Example policy shapes:
- RBAC-style allow: Role
xyz_auditor→ allowreadongts.x.core.events.type.v1~x.core._.audit_event.v1~xyz.* - ABAC refinement: Attach predicate filters like
[screen_type="..."]to restrict by referenced type. - Derived-type envelopes: Grant access at the base type (e.g.,
gts.x.core.events.type.v1~) so that derived schemas remain covered if they conform by chain rules.
Matching semantics options:
-
Implicit derived-type coverage (recommended): Granting access to a base GTS Type Identifier without an explicit wildcard (e.g.,
gts.a.b.c.d.v1~) SHOULD be treated as an implicit grant to all derived types and instances under that base type (equivalent in intent togts.a.b.c.d.v1~*).Example candidate:
gts.a.b.c.d.v1~w.x.y.z.v1This candidate SHOULD match all of:
gts.a.b.c.d.v1~gts.a.b.c.d.v1~*gts.a.b.c.d.v1~w.*
But it SHOULD NOT match:
gts.a.b.c.d.v1~x.*
-
Segment-wise prefixing: The
*wildcard can match any valid content of the target segment and its suffix hierarchy, enabling vendor/package/namespace/type grouping. -
Chain awareness: Patterns may target the base segment, derived segments, or instance tail; evaluation should consider the entire chain when present.
-
Attribute filters: Optional
[name="value", ...]predicates further constrain matches (e.g., only instances referencing a specificname). -
Minor version semantics: Patterns without minor versions (e.g.,
gts.vendor.pkg.ns.type.v1~*) match candidates with any minor version of that major version (e.g.,v1.0~,v1.1~), since the minor version is optional and omitting it means "any minor version". See section 10 for detailed examples.
Evaluation guidelines:
- Deny-over-allow (recommended): If your engine supports explicit denies, process them before allows to prevent privilege escalation.
- Most-specific wins: Prefer the most specific matching rule (longest concrete prefix, fewest wildcards, most predicates).
- Version safety: Consider pinning MAJOR and, optionally, MINOR versions in sensitive paths; otherwise rely on minor-version compatibility guarantees (see section 4).
- Tenant isolation: Use vendor/package scoping to isolate tenants and applications; avoid cross-vendor wildcards unless explicitly required.
Performance guidelines:
- Indexing: Normalize and index rules by canonical GTS prefix to avoid expensive pattern-matching scans.
- Caching: Cache resolution results for common patterns and predicate evaluations; invalidate caches on schema or policy changes.
- Auditing: Log the concrete identifier and the matched rule (pattern + predicates) for traceability and compliance.
In GTS, a GTS Type is always named: it has a stable GTS Type Identifier (ends with ~) and can be referenced from a JSON Schema $id.
However, a GTS Instance may be represented in two common ways:
- Well-known instance (named): used for unique, globally-defined objects that benefit from a stable human-readable name (catalog entries, topics/streams, modules, capabilities, etc.).
- Mandatory: well-known GTS Instance Identifiers MUST be expressed as a chain where the left segment is the type and the rightmost segment is the instance name. Single-segment instance identifiers (without a left-hand type segment) are prohibited.
- Example (well-known topic/stream instance):
gts.x.core.events.topic.v1~x.commerce._.orders.v1.0
- Field naming: typically
id(alternatives:gtsId,gts_id).
Example:
{
"id": "gts.x.core.events.topic.v1~x.commerce._.orders.v1.0",
"name": "orders",
"description": "Order lifecycle events topic"
}-
Anonymous instance: used for runtime-created objects where a globally meaningful name is not required (events/messages, DB rows, audit records, etc.).
- Recommended: use an opaque identifier as
id(typically a UUID) and store the associated GTS Type Identifier separately (e.g., in atypefield). - Example (anonymous event instance):
id: "7a1d2f34-5678-49ab-9012-abcdef123456",type: "gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~"
- Field naming:
type(alternatives:gtsType,gts_type).
Some services may also support a combined anonymous instance representation:
id: "gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~7a1d2f34-5678-49ab-9012-abcdef123456"- In this case, the explicit
typefield MAY be omitted, since the GTS Type Identifier can be derived from theidprefix up to the final~.
- Recommended: use an opaque identifier as
Note: A type marked with "x-gts-abstract": true cannot have direct instances (well-known or anonymous). Instances must reference a concrete (non-abstract) derived type as the rightmost type in the chain. See section 9.11.
This split is common in event systems: topics/streams are often well-known instances, while individual events are anonymous. See ./examples/events and the field-level recommendations in section 9.1.
Example:
{
"id": "7a1d2f34-5678-49ab-9012-abcdef123456",
"type": "gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~",
"occurredAt": "2025-09-20T18:35:00Z"
}GTS uses semantic versioning with MAJOR and optional MINOR components. This section covers two distinct compatibility concepts:
1. Type Derivation Compatibility (via chaining): A derived type like gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~ must be always fully compatible with its base type gts.x.core.events.type.v1~. Derived types refine base types by adding constraints or specifying fields left open (e.g., payload as object with additionalProperties: true).
2. Minor Version Compatibility (same type, different versions): When evolving a single type across minor versions (e.g., v1.0 → v1.1 → v1.2), compatibility depends on the chosen strategy:
- MAJOR version increments (v1 → v2): Always indicate breaking changes
- MINOR version increments (v1.0 → v1.1): Must maintain compatibility according to one of three strategies: backward, forward, or full compatibility
The compatibility mode for minor version evolution is implementation-defined and can vary depending on system component implementing the API or DB storage, namespace or use case. For example:
- Event schemas might use forward compatibility (old consumers can read new events)
- API request payloads might use backward compatibility (new servers can process old requests)
- Configuration schemas might require full compatibility (any version can read any other)
Before we dig deeper into the GTS versions compatibility, let's first define the different compatibility modes:
Backward Compatibility: A consumer with the new schema can process data produced with an old schema.
- Use case: Consumers are updated after producers (e.g., API clients updated before servers).
- Guarantee: New code can read old data.
Forward Compatibility: A consumer with the old schema can process data produced with a new schema.
- Use case: Producers are updated before consumers, or to support rollback scenarios.
- Guarantee: Old code can read new data.
Full Compatibility: Changes are both backward and forward compatible.
- Use case: Producers and consumers can be deployed in any order.
- Guarantee: Maximum safety but most restrictive evolution path.
Implementation note: The exact compatibility mode is implementation-defined and outside the scope of this specification. Systems may enforce different modes for different identifier namespaces
Understanding additionalProperties is critical for compatibility:
- Open content model:
additionalPropertiesistrueor not specified. The schema accepts fields not explicitly defined. - Closed content model:
additionalPropertiesisfalse. The schema rejects any fields not explicitly defined.
These models affect which changes are safe:
- Adding a field to a closed model is backward compatible (old data has no extra fields; new consumers handle absence).
- Adding/removing optional fields in an open model is fully compatible (open consumers accept any fields; optional fields can be absent).
The table below shows which schema changes between minor versions of the same type are safe for each compatibility mode.
NOTE: The table below illustrates the compatibility rules for GTS Type Schemas of the same type, but different versions. The derived types are always fully compatible with the base type.
| Change | Backward | Forward | Full | Notes |
|---|---|---|---|---|
| Adding optional property (open model) | ✅ Yes | ✅ Yes | ✅ Yes | Old consumers ignore new fields (open model accepts any fields). New consumers handle absence of optional field. |
| Removing optional property (open model) | ✅ Yes | ✅ Yes | ✅ Yes | New consumers ignore the removed field in old data (open model accepts any fields). Old consumers handle absence of optional field. |
| Updating description/examples | ✅ Yes | ✅ Yes | ✅ Yes | Documentation changes don't affect validation. |
| Updating minor version of referenced GTS types | ✅ Yes | ✅ Yes | ✅ Yes | Assumes referenced types follow same compatibility rules. |
| Adding optional property (closed model) | ✅ Yes | ❌ No | ❌ No | Old data lacks the field; new consumers handle absence. Old consumers reject new data with extra fields. |
| Changing required property to optional | ✅ Yes | ❌ No | ❌ No | New consumers handle absence. Old data always provides it. |
| Removing enum value | ✅ Yes | ❌ No | ❌ No | New consumers handle remaining values. Old data may use removed value. |
| Widening numeric type (int → number) | ✅ Yes | ❌ No | ❌ No | Old data (integers) is subset of new type. Old consumers may not handle floats. |
| Relaxing constraints (e.g., increasing max) | ✅ Yes | ❌ No | ❌ No | Old data satisfies looser constraints. Old consumers reject values outside old limits. |
| Removing optional property (closed model) | ❌ No | ✅ Yes | ❌ No | Old consumers expect the field may be absent. New data won't have it. |
| Changing optional property to required | ❌ No | ✅ Yes | ❌ No | Old consumers don't expect it to be required. New data always provides it. |
| Adding new enum value | ❌ No | ✅ Yes | ❌ No | Old data uses existing values. New consumers handle new values. Old consumers reject unknown values. |
| Narrowing numeric type (number → int) | ❌ No | ✅ Yes | ❌ No | New consumers accept integers only. Old data may contain floats. |
| Tightening constraints (e.g., decreasing max) | ❌ No | ✅ Yes | ❌ No | New consumers enforce stricter rules. Old data may violate new constraints. |
| Adding new required property | ❌ No | ❌ No | ❌ No | Breaking change: old data lacks the field, new consumers require it. |
| Removing required property | ❌ No | ❌ No | ❌ No | Breaking change: old data has the field, new consumers don't expect it. |
| Renaming property | ❌ No | ❌ No | ❌ No | Breaking change: equivalent to remove + add. |
| Changing property type (incompatible) | ❌ No | ❌ No | ❌ No | Breaking change unless using union types. |
This section demonstrates how different types of schema changes affect compatibility between minor versions of the same GTS type. We take as example Event Management system and typical events structure, however it can be used for any other data schemas in the system
Use case: Configuration schemas where old systems must tolerate new config options.
Schema v1.0 (gts.x.core.db.connection_config.v1.0~):
{
"$id": "gts://gts.x.core.db.connection_config.v1.0~",
"type": "object",
"required": ["host", "port", "database"],
"properties": {
"host": { "type": "string" },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
"database": { "type": "string" },
"timeout": { "type": "integer", "minimum": 1, "default": 30 }
},
"additionalProperties": false
}Schema v1.1 (adds required field):
{
"$id": "gts://gts.x.core.db.connection_config.v1.1~",
"type": "object",
"required": ["host", "port", "database", "timeout"],
"properties": {
"host": { "type": "string" },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
"database": { "type": "string" },
"timeout": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
}Compatibility analysis:
- ✅ Forward: v1.0 consumer can read v1.1 data (
timeoutis optional in v1.0 with default value) - ❌ Backward: v1.1 consumer rejects v1.0 data (missing required
timeout) - ❌ Full: Not fully compatible
Deployment strategy: Update config producers to always include timeout, then update consumers to v1.1.
Config examples:
// v1.0 config (rejected by v1.1 because timeout is now required)
{"host": "db.example.com", "port": 5432, "database": "mydb"}
// v1.1 config (valid for both schemas)
{"host": "db.example.com", "port": 5432, "database": "mydb", "timeout": 60}Use case: Event schemas where producers and consumers can be deployed independently.
Base event schema v1 (gts.x.core.events.type.v1~)
{
"$id": "gts://gts.x.core.events.type.v1~",
"type": "object",
"required": ["id", "type", "timestamp"],
"properties": {
"id": { "type": "string" },
"type": { "type": "string" },
"timestamp": { "type": "integer" },
"payload": { "type": "object", "additionalProperties": true }
},
"additionalProperties": false
}Schema v1.0 (gts.x.core.events.type.v1~x.api.users.create_request.v1.0~):
{
"$id": "gts://gts.x.core.events.type.v1~x.api.users.create_request.v1.0~",
"type": "object",
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~" },
{
"properties": {
"payload": {
"type": "object",
"required": ["email", "name"],
"properties": {
"email": { "type": "string", "format": "email" },
"name": { "type": "string" }
},
"additionalProperties": false
}
}
}
]
}Schema v1.1 (adds optional field):
{
"$id": "gts://gts.x.core.events.type.v1~x.api.users.create_request.v1.1~",
"type": "object",
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~" },
{
"properties": {
"payload": {
"type": "object",
"required": ["email", "name"],
"properties": {
"email": { "type": "string", "format": "email" },
"name": { "type": "string" },
"phoneNumber": { "type": "string" }
},
"additionalProperties": false
}
}
}
]
}Compatibility analysis:
- ❌ Forward: v1.0 server rejects v1.1 requests (closed model with
additionalProperties: falserejects unknownphoneNumber) - ✅ Backward: v1.1 server can process v1.0 requests (missing
phoneNumberis optional) - ❌ Full: Not fully compatible
Deployment strategy: Update servers to v1.1 first, then gradually update clients.
Request payload examples:
// v1.0 request payload (valid for both schemas)
{"email": "user@example.com", "name": "John Doe"}
// v1.1 request payload (rejected by v1.0 server due to additionalProperties: false)
{"email": "user@example.com", "name": "John Doe", "phoneNumber": "+1234567890"}Schema v1.0 (gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~):
{
"$id": "gts://gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~",
"type": "object",
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~" },
{
"properties": {
"payload": {
"type": "object",
"required": ["orderId", "customerId", "totalAmount"],
"properties": {
"orderId": { "type": "string" },
"customerId": { "type": "string" },
"totalAmount": { "type": "number" }
},
"additionalProperties": true
}
}
}
]
}Schema v1.1 (adds optional field):
{
"$id": "gts://gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.1~",
"type": "object",
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~" },
{
"properties": {
"payload": {
"type": "object",
"required": ["orderId", "customerId", "totalAmount"],
"properties": {
"orderId": { "type": "string" },
"customerId": { "type": "string" },
"totalAmount": { "type": "number" },
"currency": { "type": "string", "default": "USD" }
},
"additionalProperties": true
}
}
}
]
}Compatibility analysis:
- ✅ Backward: v1.1 consumers can read v1.0 data (missing
currencyfield is handled via default) - ✅ Forward: v1.0 consumers can read v1.1 data (open model ignores unknown
currencyfield) - ✅ Full: Fully compatible—deploy in any order
Event payload examples:
// v1.0 data (valid for both schemas)
{"orderId": "123", "customerId": "456", "totalAmount": 99.99}
// v1.1 data (valid for v1.1, readable by v1.0 due to open model)
{"orderId": "123", "customerId": "456", "totalAmount": 99.99, "currency": "EUR"}Note: Changes to referenced GTS identifier values do not affect full compatibility. For example, the following two schemas are treated as fully compatible even though they reference different const values:
{
"$id": (
"gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.1~"
),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.core.events.type.v1~"},
{
"type": "object",
"required": ["type", "payload"],
"properties": {
"type": {
"const": (
"gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.1~"
)
},
}
}
]
}{
"$id": (
"gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.2~"
),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.core.events.type.v1~"},
{
"type": "object",
"required": ["type", "payload"],
"properties": {
"type": {
"const": (
"gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.2~" // GTS ID changed to v1.2
)
},
}
}
]
}Important distinction: Type derivation (chaining) is different from version evolution:
// Base type (always compatible with derived types)
"$id": "gts://gts.x.core.events.type.v1~"
// Derived type v1.0 (refines base type)
"$id": "gts://gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~"
// Derived type v1.1 (minor version of the derived type)
"$id": "gts://gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.1~"Compatibility rules:
order_placed.v1.0~is always fully compatible with basetype.v1~(derivation)order_placed.v1.1~is always fully compatible with basetype.v1~(derivation)order_placed.v1.1~compatibility withorder_placed.v1.0~depends on the changes made (version evolution—see examples above)
See the examples folder for complete schema definitions demonstrating these patterns.
To maximize compatibility and minimize breaking changes between the minor versions of the same GTS type, follow these recommendations:
-
Make new properties optional with defaults: This is the safest way to add fields. Use
defaultkeyword in JSON Schema. -
Never remove or rename required properties: Always a breaking change. Increment MAJOR version instead.
-
Deprecate instead of removing: Mark fields as deprecated in documentation. Keep them in the schema for at least one MAJOR version.
-
Avoid changing field types: Type changes are almost always breaking. To evolve a type, use union types:
"type": ["string", "number"]. -
Use a GTS Registry: Centralize GTS Type management and enforce compatibility checks before allowing new versions to be published.
Besides being a universal identifier, GTS provides concrete, production-ready capabilities that solve common architectural challenges for platform vendors and service providers integrating multiple third-party services under single control plane:
- Automated compatibility checking: Validate schema changes against backward/forward/full compatibility rules before deployment (see section 4.3)
- Safe schema evolution: Add optional fields to open models without breaking existing consumers or requiring coordinated deployments
- Version casting: Automatically upcast/downcast data between minor versions (e.g., process v1.2 data with v1.0 consumer)
- Breaking change detection: Prevent accidental breaking changes through automated validation in CI/CD pipelines
- Plugin architectures: Allow third-party vendors to extend platform base types while maintaining compatibility guarantees (see section 3.2)
- Hybrid storage: Store common fields in indexed columns, vendor-specific extensions in JSONB—no schema migrations needed (see section 3.2 implementation pattern)
- Vendor isolation: Use GTS chains to track data provenance and enforce vendors' data boundaries in shared databases
- Zero-downtime extensions: Register new derived types without altering existing tables or restarting services
- Wildcard-based policies: Grant object access permissions using patterns like
gts.vendor.package.*instead of maintaining explicit lists (see section 3.5) - Attribute-based filtering: Combine GTS identifiers with predicates for fine-grained access control (see section 3.3)
- Chain-aware authorization: Restrict access to specific vendor extensions while allowing base type access
- Audit trails: Log GTS identifiers for complete traceability of data access and schema usage
- Human-readable identifiers: Debug issues by reading event types, config schemas, or API payloads directly from logs
- Self-documenting APIs: GTS identifiers encode vendor, package, namespace, and version—no external documentation lookup needed
- GTS Type Registries: Build centralized catalogs where GTS Types are indexed by GTS Type Identifiers for discovery and validation
- Deterministic UUIDs: Generate stable UUID v5 from GTS identifiers for external systems requiring opaque IDs. The UUID5 namespace is ns:URL + 'gts':
import uuid
GTS_NS = uuid.uuid5(uuid.NAMESPACE_URL, "gts")
print(uuid.uuid5(GTS_NS, "gts.x.core.events.type.v1~"))
print(uuid.uuid5(GTS_NS, "gts.x.core.events.type.v1~abc.app._.custom_event.v1.2"))Practical Scenario:
Consider a vendor, X, who operates a multi-tenant event management platform. This platform acts as a broker, receiving events from various producers (e.g., third-party applications) and routing them to the correct handlers. According to the platform's specification, every event must contain a gtsId field that references a registered event schema. This allows the platform to validate, authorize, and route events of different kinds, such as general-purpose events, audit logs, and custom messages.
Now, imagine a second vendor, ABC, develops an application named APP that runs on vendor X's platform. When a customer makes an online purchase within APP, the application needs to emit a store.purchase_audit_event to the event manager.
In this scenario, vendor X's platform must be able to:
- Authorize the incoming event, ensuring that vendor
ABCis permitted to emit it. - Validate the event's structure against the correct schema.
- Ensure data isolation, making the event visible only to authorized parties (e.g., vendor
ABC'sAPPadministrators) and not to other tenants.
Let's define the schemas required to implement this.
First, let's define the base event schema for vendor X event manager:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "gts://gts.x.core.events.type.v1~",
"title": "Base Event",
"type": "object",
"properties": {
"id": { "type": "string", "$comment": "This field serves as the unique identifier for the event instance" },
"type": { "type": "string", "$comment": "This field serves as the unique identifier for the event schema" },
"timestamp": { "type": "integer", "$comment": "timestamp in seconds since epoch" },
"payload": { "type": "object", "additionalProperties": true, "$comment": "Event payload... can be anything" }
},
"required": ["id", "type", "timestamp", "payload"],
"additionalProperties": false
}Now, let's define the audit event schema for vendor X event manager:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "gts://gts.x.core.events.type.v1~x.core.audit.event.v1~",
"title": "Audit Event, derived from Base Event",
"type": "object",
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~" },
{
"type": "object",
"properties": {
"payload": {
"type": "object",
"properties": {
"user_id": { "type": "string", "$comment": "User ID" },
"user_agent": { "type": "string", "$comment": "User agent" },
"ip_address": { "type": "string", "$comment": "IP address" },
"data": { "type": "object", "additionalProperties": true, "$comment": "Audit event custom data... can be anything" }
},
"required": ["user_id", "user_agent", "ip_address", "data"],
"additionalProperties": false
}
},
"required": ["payload"]
}
]
}Then, let's define the schema of specific audit event registered by vendor ABC application APP:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "gts://gts.x.core.events.type.v1~x.core.audit.event.v1~abc.app.store.purchase_audit_event.v1.2~",
"title": "Vendor ABC Custom Purchase Audit Event from app APP",
"type": "object",
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~x.core.audit.event.v1~" },
{
"type": "object",
"properties": {
"payload": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"purchase_id": { "type": "string" },
"amount": { "type": "number" },
"currency": { "type": "string" },
"price": { "type": "number" }
},
"required": ["purchase_id", "amount", "currency", "price"],
"additionalProperties": false
}
}
}
},
"required": ["payload"]
}
]
}Finally, when the producer (the application APP of vendor ABC) emits the event, it uses the type to identify the event schema and provide required payload:
{
"id": "e81307e5-5ee8-4c0a-8d1f-bd98a65c517e",
"type": "gts.x.core.events.type.v1~x.core.audit.event.v1~abc.app.store.purchase_audit_event.v1.2~",
"timestamp": 1743466200000000000,
"payload": {
"user_id": "9c905ae1-f0f3-4cfb-aa07-5d9a86219abe",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"ip_address": "127.0.0.1",
"data": {
"purchase_id": "cats_drinking_bowl_42",
"amount": 2,
"currency": "USD",
"price": 19.95
}
}
}When the event manager receives the event it processes it as follows:
-
Schema Resolution: Parse the
typeto identify the full chain. The event manager can see that this instance conforms togts.x.core.events.type.v1~,...~x.core.audit.event.v1~, and finally...~abc.app.store.purchase_audit_event.v1.2~. -
Validation: Load the most specific JSON Schema (
...~abc.app.store.purchase_audit_event.v1.2~) and validate the event object against it. It would automatically mean the event body is validated against any other schema in the chain (e.g., the base event and the base audit event). -
Authorization: Check if the producer is authorized to emit events matching the pattern
gts.x.core.events.type.v1~x.core.audit.event.v1~abc.app.*or a broader pattern likegts.x.core.events.type.v1~x.core.audit.event.v1~abc.*. -
Routing & Auditing: Use the chain to route events to appropriate handlers or storage if needed.
Note: use the GTS Kit for visualization of the entities relationship and validation
See additional GTS examples in the examples folder:
- Event Examples - Event-driven architecture with topic and event type definitions
- Module Examples - Modular system capabilities and plugins
- MCP Examples - AI/LLM tool definitions using Model Context Protocol
- TypeSpec VM Examples - Virtual machine types across different platforms (VMWare, Nutanix, Virtuozzo) defined in TypeSpec format
- YAML UI Examples - User interface component definitions (menus, grids) in YAML format
Critical implementation requirement: The architectural guarantees of GTS—particularly type safety across inheritance chains and safe minor version evolution—depend entirely on a stateful GTS Registry component. Production systems MUST implement or integrate a registry capable of:
- Storing and indexing all registered GTS Type Schemas by their GTS Type Identifiers
- Validating compatibility of new GTS Type versions against existing versions using the precise rules defined in section 4.3 before publication
- Enforcing inheritance constraints to ensure derived types remain compatible with their base types
- Rejecting incompatible changes that violate the declared compatibility mode (backward/forward/full)
- Providing GTS Type resolution for validation, casting, and relationship resolution operations
Without a registry performing rigorous type compatibility validation (including schema diffing where applicable), the type safety guarantees of GTS cannot be maintained. Implementations should treat the registry as a critical infrastructure component, similar to a database or message broker.
This specification intentionally does not enforce lifecycle, operational or governance choices. It is up to the implementation vendor to define policies and behavior for:
- Whether a defined type is exported (published) and available for cross-vendor use via APIs or an event bus.
- Whether a given JSON/JSON Schema definition is mutable or immutable (e.g., handling an incompatible change without changing the minor or major version).
- How to implement access policies and access checks based on the GTS query and attribute access languages.
- When to introduce a new minor version versus a new major version.
- GTS identifiers renaming and aliasing
- Exact GTS identifier minor version compatibility rules enforcement (backward, forward, full)
Non-goals reminder: GTS is not an eventing framework, transport, or workflow. It standardizes identifiers and basic validation/casting semantics around JSON and JSON Schema.
- JSONSchema $schema url: While JSONSchema provides a robust framework for defining the structure of JSON data, GTS extends this by offering clear vendor, package and namespace notation and chaining making it easier to track and validate data instances across different systems and versions.
- UUID: Opaque and globally unique. GTS is meaningful to humans and machines; UUIDs can be derived from GTS deterministically when opaque IDs are required.
- Apple UTI: Human-readable, reverse-DNS-like. GTS is similar in readability but adds explicit versioning, vendors/apps support, chaining, and schema/instance distinction suitable for JSON Schema-based validation.
- Amazon ARN: Global and structured, but cloud-service-specific. GTS is vendor-neutral and domain-agnostic, focused on data schemas and instances.
Single-chain variant:
^gts\.([a-z_][a-z0-9_]*)\.([a-z_][a-z0-9_]*)\.([a-z_][a-z0-9_]*)\.([a-z_][a-z0-9_]*)\.v(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?~?$Verbose, named groups (Python re.VERBOSE):
^\s*
gts\.
(?P<vendor>[a-z_][a-z0-9_]*)\.
(?P<package>[a-z_][a-z0-9_]*)\.
(?P<namespace>[a-z_][a-z0-9_]*)\.
(?P<type>[a-z_][a-z0-9_]*)
\.v(?P<major>0|[1-9]\d*)
(?:\.(?P<minor>0|[1-9]\d*))?
(?P<is_type>~)?
\s*$is_type captures the optional trailing ~ (present for type IDs, absent for instance IDs).
For chained identifiers, the pattern enforces that all segments except the final instance designator are type IDs (with ~ separators):
^\s*gts\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.v(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:~[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*\.v(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?)*(?:~(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?)?\s*$Pattern explanation:
- Starts with a single absolute segment (
gts.prefix) - Followed by zero or more relative segments, each prefixed by
~ - The identifier may end with:
~(type)- a segment end (instance)
~<uuid>(combined anonymous instance)
Validation rules:
- Standalone type identifier: MUST end with
~ - In a chain, all elements before the final instance designator MUST be types (end with
~in the original string).- For well-known instances, the final designator is the last
<vendor>.<package>.<namespace>.<type>.v...segment (no trailing~). - For combined anonymous instances, the final designator is the UUID tail after the last
~.
- For well-known instances, the final designator is the last
- Only the first segment uses the
gts.prefix; chained segments are relative (nogts.)
Parsing strategy:
- Split on
~to get raw segments; the first is absolute, the rest are relative - If the final raw segment is a UUID, treat it as the combined-anonymous instance tail; otherwise treat the final raw segment as the well-known instance segment (or absent, if the identifier ends with
~) - Parse the non-UUID segments as GTS segments (first absolute, the rest relative)
- Validate that all segments except the final instance designator are types
GTS specification provides recommendations for reference implementation of the core operations for working with GTS identifiers. These recommendations are not mandatory but are provided to help implementers understand the expected behavior of appropriate reference implementations in different programming languages.
For existing reference implementations, refer to the official libraries:
This section provides recommended conventions for embedding GTS identifiers into JSON objects (instances) and JSON Schemas (types). Implementations may support additional conventions, but these provide an interoperable default.
JSON Schema ($id)
It is recommended to put the GTS type identifier into the JSON Schema $id field, using a URI-like form by prepending the gts:// prefix:
Reserved prefix note: Do not place the canonical
gts.string directly in$id. Always wrap the identifier with thegts://scheme (for example:"$id": "gts://gts.x.core.events.type.v1~"). The rawgts.prefix is reserved for canonical identifiers inside the scheme, and mixing it directly into$idleads to ambiguity and upload failures.
{
"$id": "gts://gts.x.core.events.type.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Event Envelope (Common Fields)"
}Implementation note: GTS itself defines the canonical identifier string starting with gts.. When $id is expressed as gts://..., implementations should trim the gts:// prefix and treat the remainder as the canonical GTS identifier for validation, comparison, and registry keys. The gts:// prefix exists only to make $id URI-compatible.
When $id starts with gts://, the remainder must be a valid, wildcard-free GTS identifier (see OP#1 rules). Asterisks and other wildcard tokens are not permitted in GTS Type Identifiers.
JSON Schema ($ref)
It is recommended to make GTS Type references in JSON Schema $ref URI-compatible the same way as $id, by prepending the gts:// prefix when $ref points at a GTS Type Identifier:
Note: Just like
$id, do not embed rawgts.prefixes in$ref. Use the URI form (gts://...) and ensure the referenced identifier is a valid GTS ID with no wildcard characters.
{
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~" }
]
}Note: local JSON Pointer references (e.g. "$ref": "#/definitions/Foo" under Draft-07, or "$ref": "#/$defs/Foo" under Draft 2019-09+) remain valid. The gts:// recommendation applies only when $ref targets a GTS Type Identifier. The canonical container for reusable subschemas follows the dialect declared by $schema: definitions for Draft-07, $defs for Draft 2019-09 and later; both are admissible in GTS Type Schemas.
Implementation note: When $ref is expressed as gts://..., implementations should trim the gts:// prefix and treat the remainder as the canonical GTS identifier for resolution, validation, comparison, and registry keys. The gts:// prefix exists only to make $ref URI-compatible.
The post-gts:// content must therefore parse as a valid GTS identifier with no wildcards; otherwise the schema upload should be rejected.
JSON instances (well-known vs anonymous)
- Well-known instances (named): recommended to use a GTS identifier in the
idfield (alternatives:gtsId,gts_id). Prefer a chained identifier so the left segment(s) define the GTS Type automatically, and the rightmost segment is the instance name.- Example (well-known topic/stream instance):
gts.x.core.events.topic.v1~x.commerce._.orders.v1.0
- Example (well-known topic/stream instance):
- Anonymous instances: typically use the
idfield to store the object UUID, and store the GTS Type Identifier separately in atypefield (alternatives:gtsType,gts_type).- Example (anonymous event instance):
id: "7a1d2f34-5678-49ab-9012-abcdef123456",type: "gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~"
- Example (anonymous event instance):
See working examples under ./examples/events:
- Well-known topics:
./examples/events/instances/gts.x.core.events.topic.v1~x.commerce.orders.orders.v1.0.json - Anonymous events:
./examples/events/instances/gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1~.examples.json
Implement and expose all operations OP#1–OP#13 listed above and add appropriate unit tests.
- OP#1 - ID Validation: Verify identifier syntax
- OP#2 - ID Extraction: Extract identifiers from JSON objects or JSON Schema documents
- OP#3 - ID Parsing: Decompose identifiers into constituent parts (vendor, package, namespace, type, version, etc.)
- OP#4 - ID Pattern Matching: Match identifiers against patterns containing wildcards
- OP#5 - ID to UUID Mapping: Generate deterministic UUIDs from GTS identifiers
- OP#6 - Schema Validation: Validate object instances against their corresponding schemas. When validating instances, if the rightmost type in the chain is marked
x-gts-abstract: true, validation MUST fail (see section 9.11) - OP#7 - Relationship Resolution: Load schemas and instances, resolve inter-dependencies, and detect broken references
- OP#8 - Compatibility Checking: Verify that schemas with different MINOR versions are compatible
- OP#9 - Version Casting: Transform instances between compatible MINOR versions
- OP#10 - Query Execution: Filter identifier collections using the GTS query language
- OP#11 - Attribute Access: Retrieve property values and metadata using the attribute selector (
@) - OP#12 - Type Derivation Validation: Validate that a derived type correctly extends its base chain. Today this includes JSON Schema-level constraint compatibility (every derived schema MUST conform to all constraints defined in its parent schemas throughout the inheritance hierarchy —
additionalProperties, narrowing/widening, etc. — regardless of whether the derived schema references the parent viaallOf+$refor re-declares parent fields directly) and trait inheritance from OP#13. This ensures type safety in extension and prevents constraint violations in multi-level type hierarchies. When validating derived types, if any base in the chain is markedx-gts-final: true, validation MUST fail (see section 9.11) - OP#13 - Schema Traits Validation: Validate schema traits (
x-gts-traits-schema/x-gts-traits). See section 9.7 for full semantics and validation rules.
Implement simple GTS instances in-memory registry with optional GTS entities validation on registration. If "validation" parameter enabled, the entity registration action must ensure that all the GTS references are valid - identitfiers must match GTS pattern, refererred entities must be registered, the x-gts-ref references must be valid (see below)
Provide a CLI wrapping OPs for local use and CI: e.g., gts validate, gts parse, gts match, gts uuid, gts compat, gts cast, gts query, gts get. Use non-zero exit codes on validation/compatibility failures for pipeline integration.
Implement an HTTP server that conforms to tests/openapi.json so it can be tested from this specification directory.
# 1. Start appropriate server, normally as 'gts server'
# 2. Test it's conformance to required openapi.json by running specification tests:
pytest ./tests
Use x-gts-ref in GTS schemas (JSON schemas) to declare that a string field is a GTS entity reference, not an arbitrary string; validators must enforce this.
Allowed values:
"x-gts-ref": "gts.*"— field must be a valid GTS identifier (see OP#1); optionally resolve against a registry if available."x-gts-ref": "/$id"— relative self-reference; field value must equal the current schema’s$idwithout thegts://prefix ("/" refers to the JSON Schema document root,$idis its identifier). The referred field must be a GTS string or anotherx-gts-reffield.
See examples in ./examples/modules for typical patterns.
Implementation notes:
- Treating
x-gts-reflike JSON Schema string constraints:- When the value is a literal starting with
gts.(e.g.,gts.x.core.modules.capability.v1~), it can be enforced similarly to astartsWith(...)check by validating the instance value against the provided GTS prefix (sections 8.1/8.2). Implementations must also validate the GTS ID. - When the value is a relative path like
./$idor./description, resolve it as a JSON Pointer relative to the schema root. If the pointer doesn't resolve to a GTS string or anotherx-gts-reffield, an error must be reported. - For nested paths (e.g.,
./properties/id), resolve the pointer accordinly to the field path in the JSON Schema document.
- When the value is a literal starting with
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).
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:
- Retention rules — how long instances of this type are kept (e.g., object TTL)
- Processing directives — how attributes should be handled (e.g., PII masking, indexing hints)
- Association links — linking schemas to related entities (e.g., associating an event type with its topic/stream)
Two JSON Schema annotation keywords are used together:
| Keyword | JSON type | Purpose | Typical location |
|---|---|---|---|
x-gts-traits-schema |
JSON Schema (object | boolean) | Defines the shape of the trait — property names, types, constraints, and default values |
Base / ancestor schemas |
x-gts-traits |
Plain JSON object | Provides concrete values for the trait properties | Derived (leaf) schemas; may also appear alongside x-gts-traits-schema in the same schema |
Schema-only keywords: Both x-gts-traits-schema and x-gts-traits are schema annotation keywords and MUST only appear in JSON Schema documents (documents with $schema). They MUST NOT appear in instance documents. Implementations MUST reject instances that contain these keywords.
Keyword placement: Both x-gts-traits-schema and x-gts-traits are type-level keywords and MUST appear at the top level of the GTS Type Schema document, adjacent to $id and $schema — NOT nested inside an allOf entry or any other subschema. A misplaced occurrence MUST be rejected (fail fast). This governs only the position of the keyword itself, not the contents of x-gts-traits-schema (which is an ordinary JSON Schema subschema and may freely use $ref, allOf, etc.). The same rule applies to the modifiers x-gts-final / x-gts-abstract (§9.11).
A single schema MAY contain both keywords. This is explicitly allowed and useful when a mid-level schema defines new trait properties (x-gts-traits-schema) while also resolving traits inherited from its parent (x-gts-traits).
x-gts-traits-schema is a JSON Schema subschema. By the JSON Schema definition, its value MAY therefore be:
- an object subschema — declares the trait shape in the usual way (
properties,required, etc.); true— admits any trait values (the trivially-satisfied schema; traits remain permitted but unconstrained at this layer);false— prohibits traits entirely on this host, and on any descendant whose chain includes this layer (falseis unsatisfiable, so the chain-aggregated effective trait-schema becomes unsatisfiable andx-gts-traitsis rejected).
When x-gts-traits-schema is an object subschema, the effective trait-schema (after chain aggregation per §9.7.5) MUST constrain trait values to JSON objects.
Because x-gts-traits-schema is an ordinary JSON Schema subschema, all standard JSON Schema constructs apply inside it with their normal semantics; implementations MUST NOT invent a custom reference mechanism for $ref.
The trait shape MAY be declared inline, referenced from a standalone trait-schema registered as an ordinary GTS Type via $ref, or composed via allOf of inline parts and references. The choice is an authoring decision — inline keeps the trait surface private to the host and inheriting the host's ACL; the $ref-to-registered-type form is appropriate when the trait surface should be a separately governed artifact.
Inheritance along the host-type derivation chain happens at the registry level, not at the author level. A descendant host type does NOT need to repeat the ancestor's x-gts-traits-schema inside its own — the registry composes all x-gts-traits-schema declarations encountered along the host's $id chain via JSON Schema allOf (see §9.7.5). A descendant MAY write an explicit allOf that includes a $ref to an ancestor's x-gts-traits-schema; doing so is redundant under chain aggregation but not invalid (consistent with the JSON Schema extension framing — see adr/0001-derivation-form.md).
See adr/0002-x-gts-traits-schema.md for the rationale behind the subschema framing and the chain-aggregation rule.
x-gts-traits is a plain JSON object of concrete values. Constraint keywords like const belong in x-gts-traits-schema (the trait schema), not in x-gts-traits (the trait values).
A type schema declares the trait shape — property names, types, constraints, and default values. Any type in the $id chain (base or descendant) MAY contribute its own x-gts-traits-schema; the registry composes all such declarations along the chain via JSON Schema allOf into a single effective trait-schema (see §9.7.5).
The same derivation compatibility principle that governs host body schemas (§3.1) applies to x-gts-traits-schema: every value valid against the descendant's effective trait-schema MUST also be valid against each ancestor's trait-schema. This is enforced naturally by the allOf composition — contradictions across the chain (e.g., conflicting types, narrowed constraints that don't overlap) produce an unsatisfiable effective trait-schema and fail registration. Typically a base declares the initial trait shape and descendants narrow existing trait properties (tighten constraints, const, narrower enums). Descendants MAY also extend the trait surface by introducing new top-level properties — but only if no ancestor's x-gts-traits-schema declares additionalProperties: false (or another restriction that would reject the new property); otherwise the new property is treated as "additional" against that ancestor's branch in the allOf composition and validation fails, by the same mechanic as §3.1 governs for host bodies. default values are JSON Schema annotations and do not participate in narrowing: descendants MAY freely redeclare a property's default in their own x-gts-traits-schema. A publisher who wants a trait value to be fixed across descendants SHOULD declare "const": <value> (a real narrowing of the validation surface), not rely on a default.
Inline definition:
{
"$id": "gts://gts.x.core.events.type.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"properties": {
"topicRef": {
"description": "GTS ID of the topic/stream where events of this type are published.",
"type": "string",
"x-gts-ref": "gts.x.core.events.topic.v1~",
"default": "gts.x.core.events.topic.v1~x.core._.default.v1"
},
"retention": {
"description": "ISO 8601 duration for event retention.",
"type": "string",
"default": "P30D"
}
}
},
"properties": { "..." : {} }
}$ref to reusable trait schemas:
A platform MAY publish standalone, reusable trait schemas (e.g., RetentionTrait, TopicTrait, PIITrait). Base schemas reference them via standard $ref:
{
"$id": "gts://gts.x.core.events.type.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"allOf": [
{ "$ref": "gts://gts.x.core.traits.retention.v1~" },
{ "$ref": "gts://gts.x.core.traits.topic.v1~" }
]
},
"properties": { "..." : {} }
}Where each referenced trait schema is a standalone JSON Schema registered as a GTS entity:
{
"$id": "gts://gts.x.core.traits.retention.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"retention": {
"description": "ISO 8601 duration for data retention.",
"type": "string",
"default": "P30D"
}
}
}Derived schemas resolve (configure) trait values by providing a plain JSON object via x-gts-traits. Trait values MUST be valid against the effective trait schema derived from the inheritance chain as defined below.
x-gts-traits is a top-level member of the document (§9.7.1), a sibling of $id / $schema / allOf — not nested inside an allOf entry:
{
"$id": "gts://gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~",
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~" }
],
"x-gts-traits": {
"topicRef": "gts.x.core.events.topic.v1~x.commerce._.orders.v1",
"retention": "P90D"
}
}A mid-level schema MAY extend the trait schema while also providing values for inherited traits:
Both keywords sit at the top level alongside $id (§9.7.1); the allOf carries only the body composition:
{
"$id": "gts://gts.x.core.events.type.v1~x.core.audit.event.v1~",
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~" }
],
"x-gts-traits-schema": {
"type": "object",
"properties": {
"auditRetention": {
"description": "Retention override for audit compliance.",
"type": "string",
"default": "P365D"
}
}
},
"x-gts-traits": {
"topicRef": "gts.x.core.events.topic.v1~x.core._.audit.v1"
}
}Traits MUST follow standard JSON Schema practices. The key rule is that the registry MUST treat trait schemas as normal JSON Schemas and MUST rely on standard JSON Schema composition and $ref semantics (especially allOf) rather than inventing a bespoke merge algorithm.
Given an inheritance chain S₀ → S₁ → … → Sₙ:
-
Trait schema merge
- The registry MUST build an effective trait schema by composing all encountered
x-gts-traits-schemavalues using JSON SchemaallOf. - Any
$refappearing insidex-gts-traits-schemaMUST be resolved using standard JSON Schema$refresolution rules (base URI resolution + JSON Pointer fragments). - Derived schemas MAY further constrain (narrow) traits by adding additional schema constraints in their
x-gts-traits-schema(this is naturally enforced byallOf).
- The registry MUST build an effective trait schema by composing all encountered
-
Trait value merge
- The registry MUST build an effective traits object by walking the type's
$idchain root → leaf and applying each layer'sx-gts-traitsas a JSON Merge Patch (RFC 7396) against the chain-merged object so far. Top-level scalar / array /nullleaves 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. - 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.
nullat 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'sdefaultre-apply via the materialization step described in the Completeness check below — that is, a descendant writes"<key>": nullto "fall back to the schema default" without picking a specific value. If the deleted key isrequiredand has nodefault, the completeness check (OP#13) fails 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 wantnullas an intended trait value cannot express it via this merge and must use a sentinel value documented as part of the trait shape.- Defaults declared in the effective trait-schema MUST be materialized into the effective traits object before the Completeness check runs (per ADR-0003): for every property declared in the effective trait-schema with a
defaultand not present in the chain-merged object, the registry MUST substitute the default value. The Completeness check below (OP#13) operates on the resulting materialized effective traits object. - A publisher who wants a trait value to be locked across all descendants of a base type SHOULD declare
"const": <value>for that property inx-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 —constis the mechanism. - A descendant MAY redeclare a trait value with the same value the ancestor already declared (idempotent restatement).
- See
adr/0004-x-gts-traits-merge-strategy.mdfor the rationale.
- The registry MUST build an effective traits object by walking the type's
-
Validation
- Completeness check (OP#13, type-level): For types whose
x-gts-abstractis nottrue, 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 arequiredproperty of the effective trait-schema has no chain-assigned value and no default — the type fails OP#13 validation. Completeness is a property of the type itself, not of any instance: it is always enforced on the explicit validation endpoints (/validate-type-schema,/validate-entity), and is additionally enforced at registration when validation is enabled (?validate=true), per the common pattern described in §9.11.5. For types withx-gts-abstract: true, this completeness check is skipped; descendants are expected to close any unresolved required traits. Seeadr/0003-x-gts-traits-completeness.mdfor the rationale. - If the effective trait schema cannot be satisfied (e.g., contradictory constraints introduced across the chain), schema validation MUST fail.
- Completeness check (OP#13, type-level): For types whose
Example — descendant override and const lock:
Consider a 3-level chain: base → audit_event → most_derived_event.
base.x-gts-traits-schema.properties.indexed.const = true— the publisher locksindexed.audit_event.x-gts-traitssetstopicRef = gts.x.core.events.topic.v1~x.core._.audit.v1.most_derived_event.x-gts-traitssetstopicRef = gts.x.core.events.topic.v1~x.core._.notification.v1.
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.
These rules are intentionally aligned with existing JSON Schema composition semantics and GTS schema chaining practices.
See ./examples/events/types/ for complete examples demonstrating trait definition and resolution.
Accept and emit both JSON and YAML (.json, .yaml, .yml) for schemas and instances.
Ensure conversions are lossless; preserve $id, gtsId, and custom extensions like x-gts-ref.
Support generating JSON Schema and OpenAPI from TypeSpec while preserving GTS semantics.
Ensure generated schemas use GTS identifiers as $id for types and keep any x-gts-* extensions intact.
Support UUIDs (format: uuid) for instance id fields.
A schema modifier is a boolean annotation on a GTS Type Schema that restricts how the type participates in the GTS type system. Modifiers can be used to control inheritance and instantiation behavior. There are two keywords for this purpose: x-gts-final and x-gts-abstract.
| Keyword | JSON type | Purpose | Typical location |
|---|---|---|---|
x-gts-final |
boolean |
Marks the type as not inheritable — no derived schemas may reference it as a base | Leaf schemas; enum-like types with a fixed set of well-known instances |
x-gts-abstract |
boolean |
Marks the type as not directly instantiable — instances must conform to a concrete derived type | Base/ancestor schemas that serve purely as templates |
Schema-only keywords: Both x-gts-final and x-gts-abstract are schema annotation keywords and MUST only appear in JSON Schema documents (documents with $schema). They MUST NOT appear in instance documents. Implementations MUST reject instances that contain these keywords.
Allowed values: The only meaningful value is true. If the keyword is absent or set to false, it has no effect (the schema behaves normally — both inheritable and instantiable). Implementations MUST reject non-boolean values.
Mutual exclusion: A schema MUST NOT declare both "x-gts-final": true and "x-gts-abstract": true. This combination is semantically meaningless (a type that can be neither inherited from nor instantiated serves no purpose) and MUST be rejected during schema registration or validation.
| Modifier combination | Inheritance allowed? | Direct instances allowed? |
|---|---|---|
| (default / neither) | Yes | Yes |
x-gts-abstract: true |
Yes | No |
x-gts-final: true |
No | Yes |
Both true |
INVALID — MUST be rejected | — |
When a schema declares "x-gts-final": true:
-
Registration guard: When a new schema is registered whose
$idchain references a final type as a base, the registry MUST reject the registration (when validation is enabled). Specifically, if the derived schema's$idis of the formgts://gts.A~B~and schemaA~has"x-gts-final": true, then registeringA~B~MUST fail. This is determined from the chained$idalone — it does not depend on whether the derived schema body usesallOfto reference the parent. -
Validation via
/validate-type-schema(OP#12): When validating a derived schema against its base chain, if any base schema in the chain is markedx-gts-final, validation MUST fail with an error indicating that the base type is final and cannot be extended. -
Instances are unaffected: A final type MAY have well-known instances and anonymous instances.
x-gts-finalrestricts only schema derivation, not instantiation. -
No propagation:
x-gts-finalapplies only to the schema that declares it. It does NOT propagate to base types in the chain. For a chainA~ → B~ → C~, ifB~is final, thenC~is invalid. ButA~can still be inherited by types other thanB~'s descendants. -
Keyword placement: The keyword MUST appear at the top level of the JSON Schema document, adjacent to
$idand$schema— NOT nested inside anallOfentry or any other subschema. A misplaced occurrence MUST be rejected (fail fast). The same applies tox-gts-abstract(§9.11.3 item 6) and the trait keywords (§9.7.1).
{
"$id": "gts://gts.x.infra.compute.vm_state.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"x-gts-final": true,
"type": "object",
"properties": { "..." : {} }
}For derived schemas using allOf, the keyword MUST appear at the top level, NOT inside the allOf entries:
{
"$id": "gts://gts.x.core.events.type.v1~x.vendor._.order_event.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{ "$ref": "gts://gts.x.core.events.type.v1~" },
{ "..." : {} }
],
"x-gts-final": true
}When a schema declares "x-gts-abstract": true:
-
Instance registration guard: When a new instance is registered whose rightmost type in the chain is an abstract type, the registry MUST reject the registration (when validation is enabled). For example, if
gts.x.core.events.type.v1~is abstract, registering a well-known instancegts.x.core.events.type.v1~x.vendor._.some_event.v1MUST fail (the rightmost type is abstract). However, if a concrete derived schemagts.x.core.events.type.v1~x.vendor._.order_event.v1~exists and is not abstract, then registering instancegts.x.core.events.type.v1~x.vendor._.order_event.v1~x.vendor._.order_placed.v1succeeds. -
Validation via
/validate-instanceand/validate-entity(OP#6): When validating an instance (through either the instance-specific or unified entity endpoint), the system resolves the rightmost type in the chain. If that type is abstract, validation MUST fail. -
Schema derivation is unaffected: An abstract type is explicitly intended to be inherited from. Registering derived schemas from an abstract type always succeeds (subject to other validation rules).
-
No propagation: A derived type is concrete by default. If
A~is abstract andB~derives fromA~,B~is concrete unless it also declares"x-gts-abstract": true. -
Anonymous instances: For combined anonymous instance IDs like
gts.A~<UUID>, the system resolves the type from the prefix. If that type is abstract, the instance MUST be rejected. -
Keyword placement: Like
x-gts-final(§9.11.2 item 5),x-gts-abstractis a type-level modifier and MUST appear at the top level of the JSON Schema document, adjacent to$idand$schema— NOT inside anallOfentry (or any other subschema). A subschema is not "the type"; placing the modifier there is a misplacement and MUST be rejected during schema registration or validation.
-
Completeness keyed on
x-gts-abstract: A type whosex-gts-abstractis nottrueMUST satisfy trait completeness at registration (see §9.7.5). A type withx-gts-abstract: trueis exempt — abstract types may have unresolved required traits; descendants are expected to close them. Seeadr/0003-x-gts-traits-completeness.md. -
Final types follow the non-abstract rule: A type with
x-gts-final: trueis non-abstract by definition (abstract+final is rejected per §9.11.1) and therefore subject to the completeness check. Because no further descendants are permitted, completeness must be satisfied by the final type itself — by chain-inherited values, locally declaredx-gts-traits, ordefaults in the effective trait-schema. -
Abstract types may declare
x-gts-traits-schema: Doing so contributes to the effective trait-schema of descendants; the abstract type itself is not required to provide values.
Enforcement follows the same pattern as existing ?validate=true behavior: checks are performed when validation is enabled on registration, and always enforced on explicit validation endpoints (/validate-type-schema, /validate-instance, /validate-entity). This is consistent with existing patterns (e.g., x-gts-ref checks in section 9.6).
See ./examples/typespec/vms/types/states/gts.x.infra.compute.vm_state.v1~.schema.json for an example of a final type, ./examples/modules/types/gts.x.core.modules.capability.v1~.schema.json for another final type, and ./examples/events/types/gts.x.core.events.type.v1~.schema.json for an example of an abstract base type.
Important: An identifier containing a wildcard (*) is a pattern for matching and may not serve as a canonical identifier for a type or instance.
A single wildcard (*) character can be used to find all identifiers matching a given prefix. The wildcard is a greedy operator that matches any sequence of characters after it, including the ~ chain separator.
Rules for using wildcards:
- The wildcard (
*) must be used only once. - The wildcard must appear at the end of the pattern.
- The wildcard must not be used in combination with an attribute selector (
@) or query ([]). - The pattern must start at the beginning of a valid segment. For example,
gts.x.llm.chat.msg*is invalid ifmsgis not a complete segment.gts.x.llm.chat.message.v*is valid becausevis the start of the version segment. - Minor version semantics: When a pattern specifies only a major version (e.g.,
v0.*), it matches candidates with any minor version of that major version (e.g.,v0.1~,v0.2~, etc.). This is because the minor version is optional, and omitting it semantically means "any minor version".
Valid Examples:
Given the following identifiers:
gts.x.llm.chat.message.v1.0~
gts.x.llm.chat.message.v1.0~x.llm.system_message.v1.0~
gts.x.llm.chat.message.v1.1~
gts.x.llm.chat.message.v1.1~x.llm.user_message.v1.1~
-
Pattern:
gts.x.llm.chat.message.*- Find all base schemas versions and their derived schemas- Result: All four identifiers listed above.
-
Pattern:
gts.x.llm.chat.message.v1.*- Find all base and deriver types from v1 (any minor version)- Result: All four identifiers (matches both
v1.0~andv1.1~because pattern without minor version matches any minor version)
- Result: All four identifiers (matches both
-
Pattern:
gts.x.llm.chat.message.v1~*- Find all derived types from v1 (any minor version)- Result: two derived entities:
gts.x.llm.chat.message.v1.0~x.llm.system_message.v1.0~,gts.x.llm.chat.message.v1.1~x.llm.user_message.v1.1~
- Result: two derived entities:
-
Pattern:
gts.x.llm.chat.message.v1.0~*- Find all derived types (schemas) down the chain- Result: Only one matching entity:
gts.x.llm.chat.message.v1.0~x.llm.system_message.v1.0~
- Result: Only one matching entity:
Minor Version Matching Examples:
The following examples demonstrate the special case where patterns without minor versions match candidates with any minor version:
Pattern: gts.vendor.pkg.ns.type.v0~*
Candidate: gts.vendor.pkg.ns.type.v0.1~
Result: ✅ MATCH (pattern v0~ matches any v0.x)
Pattern: gts.vendor.pkg.ns.type.v0~*
Candidate: gts.vendor.pkg.ns.type.v0~
Result: ✅ MATCH (exact match)
Pattern: gts.vendor.pkg.ns.type.v0.1~*
Candidate: gts.vendor.pkg.ns.type.v0.1~
Result: ✅ MATCH (exact match with minor version)
Pattern: gts.vendor.pkg.ns.type.v0~abc.*
Candidate: gts.vendor.pkg.ns.type.v0.1~
Result: ❌ NO MATCH (pattern v0~abc.* does not match any v0.x)
Pattern: gts.vendor.pkg.ns.type.v0.1~*
Candidate: gts.vendor.pkg.ns.type.v0.2~
Result: ❌ NO MATCH (different minor versions)
Pattern: gts.vendor.pkg.ns.type.v0~*
Candidate: gts.vendor.pkg.ns.type.v1.0~
Result: ❌ NO MATCH (different major versions)
Invalid Pattern Examples:
gts.x.llm.chat.msg*- Invalid ifmsgis not a complete segment.gts.x.llm.chat.message.v*~*- Multiple wildcards are used.
GTS Type Schemas extend JSON Schema with a vendor keyword set (x-gts-*) and a set of registry-enforced semantic rules (see §3.2 derivation, §9.11 modifiers, OP#12 derivation compatibility, OP#13 trait validation). GTS does not impose additional syntactic restrictions on the standard JSON Schema body: any syntactically valid JSON Schema body that carries a valid GTS $id is a syntactically valid GTS Type Schema. The constraints GTS does enforce on document structure concern only its own x-gts-* keywords — these are type-level annotations that MUST appear at the document top level and are rejected when misplaced, and that MUST NOT appear in instance documents (§9.7.1, §9.11). Implementations MUST treat the GTS keywords described in this specification as layered on top of the underlying JSON Schema dialect's semantics, alongside the standard JSON Schema keywords ($id, $ref, allOf, const, …) used here.
Dialect-agnostic. GTS does not pin Type Schemas to a single JSON Schema draft. The dialect of any concrete GTS Type Schema is set by its $schema URI, and implementations MUST honour that dialect when validating or interpreting the schema body. The reference examples in this specification declare $schema: http://json-schema.org/draft-07/schema# because Draft-07 has the broadest tooling support and is the safest baseline for cross-vendor interoperability; however, Type Schemas that declare a later dialect — Draft 2019-09 (https://json-schema.org/draft/2019-09/schema) or Draft 2020-12 (https://json-schema.org/draft/2020-12/schema) — are equally valid GTS Type Schemas. Authors who wish to use post-Draft-07 keywords ($defs, prefixItems, unevaluatedProperties, unevaluatedItems, $dynamicRef/$dynamicAnchor, dependentRequired, dependentSchemas, …) MAY do so, provided the dialect declared in $schema admits those keywords and the GTS-specific rules (derivation compatibility per OP#12, trait validation per OP#13, modifiers per §9.11) are satisfied.
This specification does not publish a dedicated GTS meta-schema or $schema URI; x-gts-* keywords are vendor extensions layered over whichever JSON Schema dialect a Type Schema declares. GTS Type Schemas are therefore not a JSON Schema Dialect in the formal sense — all GTS-specific constraints are enforced at the registry, not by a meta-schema. Whether a future revision will eventually publish a dedicated $schema URI and meta-schema (and thereby make GTS a Dialect formally) is an open question; this specification does not commit to that path.
JSON Schema has no native concept of derivation or inheritance — its closest primitive, allOf, is a logical AND over subschemas at instance-validation time. In GTS, derivation is expressed by the chained $id (e.g., gts://A~B~); the schema body MAY use allOf with a $ref to the parent — which is convenient for avoiding duplication of the parent's fields and constraints in the derived schema — but is not strictly required. A derived schema that re-declares the parent's fields directly without allOf is admissible, provided it satisfies derivation compatibility (OP#12). See adr/0001-derivation-form.md for the full discussion.
- Reusable subschemas inside a GTS Type Schema SHOULD be placed under the canonical container for the dialect declared by
$schema:definitionsfor Draft-07,$defsfor Draft 2019-09 and later. Local JSON Pointer references such as"$ref": "#/definitions/Foo"(Draft-07) or"$ref": "#/$defs/Foo"(Draft 2019-09+) are the recommended form.
This section defines recommendations for how GTS-aware systems interpret JSON documents. The rules describe the concepts; the exact field names used for instance IDs and instance types are implementation-defined and may be configuration-driven (different systems may look for identifiers in different fields).
A JSON document is a schema if and only if it contains a top-level $schema field.
- If
$schemais present → the document MUST be treated as a schema. - If
$schemais absent → the document MUST be treated as an instance.
This discriminator MUST be applied before any ID parsing heuristics.
For GTS schemas (documents with $schema), it is recommended that $id is URI-compatible by using:
$id: "gts://<canonical-gts-id>"
Implementations MUST normalize this by stripping the gts:// prefix when extracting/returning the canonical GTS identifier. The gts:// prefix exists only to make $id URI-compatible.
Implementations MUST clearly distinguish the following five categories of JSON documents:
- GTS Type Schemas
- Have
$schema - Have
$idstarting withgts://and the remainder is a valid GTS Type Identifier (ends with~) - This is the canonical JSON representation of a GTS Type Schema. Files named
*.schema.jsoncarry such documents. - Example:
- Have
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "gts://gts.x.core.events.type.v1~",
"type": "object"
}- Non‑GTS schemas
- Have
$schema - Do not have a valid GTS
$id - Handling is implementation-defined (ignore vs error depending on API context)
- Example:
- Have
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/schemas/order.json",
"type": "object"
}- Instances of unknown / non‑GTS schemas
- No
$schema - GTS Type cannot be determined (no acceptable GTS Type reference field found, or the field value is not a valid GTS ID)
- Handling is implementation-defined (ignore vs error depending on API context)
- Example:
- No
{
"id": "123",
"payload": { "foo": "bar" }
}- Well-known GTS instances (named)
- No
$schema - Instance is identified by a GTS Instance Identifier (often a chain) stored in an implementation-chosen instance-ID field
- The GTS Type is derived from the left segment(s) of the chain
- Example (well-known topic/stream instance):
- No
{
"id": "gts.x.core.events.topic.v1~x.commerce._.orders.v1.0",
"name": "orders"
}NOTE: In this specification, a GTS Instance Identifier is a GTS identifier without the trailing
~(i.e., it does not name a GTS Type). Some systems may still accept anidfield or its equivalent that contains a GTS Type Identifier (ending with~) and treat it as a GTS Type reference rather than a GTS Instance Identifier. This behavior is not defined by the GTS spec and is entirely implementation-specific / configuration-driven.
- Anonymous GTS instances
- No
$schema - Instance
idis opaque (typically UUID) - GTS Type is provided separately via an implementation-chosen GTS Type Identifier field (e.g.,
type,gtsType,gts_type) - Example (anonymous event instance):
- No
{
"id": "7a1d2f34-5678-49ab-9012-abcdef123456",
"type": "gts.x.core.events.type.v1~x.commerce.orders.order_placed.v1.0~",
"occurredAt": "2025-09-20T18:35:00Z"
}NOTE: In this specification, a GTS Type Identifier is a GTS identifier with the trailing
~. Some systems may still accept atypefield or its equivalent that contains a GTS Instance Identifier (not ending with~). This behavior is not defined by the GTS spec and is entirely implementation-specific / configuration-driven.
For instances (documents without $schema), implementations typically apply heuristics in this order:
- Try instance ID fields (commonly
id, then aliases likegtsId,gts_id):- If the value is a valid GTS identifier, treat it as a well-known instance and derive the
type_id(the GTS Type Identifier) from the chain (everything up to and including the last~). - Otherwise treat it as an anonymous instance ID value.
- If the value is a valid GTS identifier, treat it as a well-known instance and derive the
- For anonymous instances, determine the GTS Type from a separate field (commonly
type, or aliases likegtsType,gts_type;schemaMAY be supported as a legacy alias but is discouraged for new instances).
Important: When determining instance type, a chained GTS ID in the instance ID field ALWAYS takes priority over any explicit type field. The type is derived from the chain's type segments, not from a separate type property.
Different systems may choose different field names and priority orders via configuration. The examples below (and the ./examples/* folders) use the common defaults: id for instance ID and type for instance type.
The type_id returned by extraction/registration APIs (e.g. /extract-id) MUST be either a valid GTS Type Identifier (ending with ~) or null. It MUST NOT contain any non-GTS value, including JSON Schema dialect URLs (such as http://json-schema.org/draft-07/schema#).
Specifically:
- Derived GTS schema (chained
$id):type_idis the parent GTS Type Identifier — the chain's left segments up to and including the last~of the base. - Base GTS schema (single-segment
$idwith no chain):type_idisnull— a base GTS schema has no GTS parent type. - Non-GTS schema (
$schemapresent, no GTS$id):type_idisnull— the document is not a GTS entity. - Well-known GTS instance (chained GTS ID in instance ID field):
type_idis the chain's left segments up to and including the last~. - Anonymous GTS instance (UUID
id+ separatetypefield, or combined-anonymous form):type_idis the GTS Type Identifier referenced by thetypefield (or derived from the chain prefix in the combined form). - Non-GTS instance (no GTS identifier and no GTS Type reference):
type_idisnull.
Implementations MAY expose the JSON Schema dialect URL ($schema) separately if needed (e.g., as a distinct meta_schema field), but MUST NOT conflate it with type_id.
It is advisable to include instance identifiers in a top-level field such as id. However, the choice of the specific field name is left to the discretion of the implementation and can vary from service to service.
Example #1: instance definition of an object instance (event topic) that has an id field that encodes the object type (gts.x.core.events.topic.v1~) and identifies the object itself (x.core.idp.events.v1). In the example below it makes no sense to add an additional type field referring to the object schema because the id is already unique and there are no other event topics with the given id in the system:
{
"id": "gts.x.core.events.topic.v1~x.core.idp.events.v1",
"description": "User-related events (creation, profile changes, etc.)",
"retention": "P30D",
"ordering": "by-partition-key",
}Example #2: instance definition of an object that has a gtsId field that encodes the object type, but also its own integer identifier of the object:
[{
"id": "123",
"type": "gts.x.core.events.type.v1~x.core.idp.events.v1~",
"payload": { "foo": "123", "bar": 42 }
},
{
"id": "125",
"type": "gts.x.core.events.type.v1~x.core.idp.events.v1~",
"payload": { "foo": "xyz", "bar": 123 }
}]Example #3: schema definition of an event type with the $id field equal to the type identifier (ending with ~):
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "gts://gts.x.core.events.type.v1~",
"type": "object",
"properties": {
"id": { "type": "string" },
"timestamp": { "type": "string", "format": "date-time" },
"payload": { "type": "object" }
},
"required": ["id", "payload"]
}- Prefer chains where the base system type is first, followed by vendor-specific refinements, and finally the instance.
- Favor additive changes in MINOR versions. Use a new MAJOR for breaking changes.
- Keep types small and cohesive; use
namespaceto group related types within a package.
See tests/README.md
The GTS specification does not require vendors to publish their types publicly, but we encourage them to submit their vendor codes to prevent future conflicts.
Currently registered vendors:
| Vendor | Description |
|---|---|
| x | example vendor |
{ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "gts://gts.x.core.events.type.v1~abc.events.order_placed.v1~", // define a new event type derived from the base event type "type": "object", "allOf": [ { "$ref": "gts://gts.x.core.events.type.v1~" }, // inherit base event schema { "properties": { "typeId": { "const": "gts.x.core.events.type.v1~abc.orders.order_placed.v1~" }, "payload": { "type": "object", "properties": { // define the payload structure specific for this new event type "orderId": { "type": "string" }, "customerId": { "type": "string" }, "totalAmount": { "type": "number" }, "items": { "type": "array", "items": { "type": "object" } } }, "required": ["orderId", "customerId", "totalAmount", "items"], "additionalProperties": false } } } ] }