Annex E - External Imports and Versioning (Normative)

Version: 1.7.0 Date: May 2026 Status: Draft

License: This annex is part of the Open Okyline Language Specification and is subject to the same license terms (CC BY-SA 4.0). See the Core Specification for full license details.

This annex extends Annex D to enable cross-schema composition in enterprise environments. It defines schema identity ($id, $version), versioned dependencies ($deps), and external imports ($import). Organizations can publish schemas to a registry and import definitions from other contracts while maintaining strict version control and compatibility guarantees.


Relation to the Core Specification

This annex extends Annex D by adding support for:

Annex D defines the core mechanisms for structural composition ($ref, $defs, $override, $remove). This annex enables those same mechanisms to operate across schema boundaries through versioned imports.

Implementations MAY support Annex D without Annex E, providing internal composition only. Full Okyline 1.2.0 conformance requires both annexes.


E.1 Overview

In enterprise environments, schemas often need to:

Okyline addresses these needs through a two-layer architecture:

  1. Annex D (internal) - $defs + $ref for composition within a single document
  2. Annex E (external) - $import + $deps for importing definitions from other schemas

External definitions, once imported via $import, behave exactly like internal definitions in $defs. The composition mechanisms ($ref, $override, $remove) work identically regardless of origin.


E.2 Schema Identity - $id, $version, and $state

E.2.1 Contract Identity

Each publishable Okyline schema is identified by a pair ($id, $version) that defines a unique contract in the registry.

The format and validation rules for $id and $version are defined in the Core Specification [§7.3 Optional Metadata Keys]. These fields can be used as simple metadata for traceability even without this annex.

Example:

{
  "$id": "sales.orders",
  "$version": "1.3.4",
  "$oky": { }
}

E.2.2 When $id and $version Become Required

While $id and $version are optional in the Core specification, they become required when using external imports:

Field Required when
$id Schema is published to a registry, or referenced by other schemas via $import
$version Schema is published to a registry, or uses external dependencies via $deps

E.2.3 Pre-release Version Resolution

When resolving dependencies, pre-release versions follow semver ordering:

2.0.0-alpha < 2.0.0-alpha.1 < 2.0.0-beta < 2.0.0-rc.1 < 2.0.0

Registry behavior:

E.2.4 Lifecycle State - $state

The optional $state field declares the lifecycle state of the contract. It carries the publisher’s intent regarding whether the schema is still being shaped or has been frozen for consumption.

Allowed values:

Value Semantics
"DRAFT" The schema is work-in-progress and MAY evolve without prior notice, even at the same $version. Consumers MUST consider its content unstable.
"FINAL" The schema is frozen. Once a (id, version) pair is published as FINAL, its content MUST NOT change. Any modification requires a new $version.

Default:

If $state is absent, the schema MUST be treated as "DRAFT". Implementations MAY enforce explicit $state declaration at publication boundaries (e.g. in a registry workflow), but the language parser itself MUST accept the absence and apply the default.

Validation:

Any value other than "DRAFT" or "FINAL" MUST cause schema loading to fail.

Example:

{
  "$id": "sales.orders",
  "$version": "1.0.0",
  "$state": "FINAL",
  "$oky": { }
}

Note: The lifecycle state has no effect on schema validation itself; it informs registry, packaging, and dependency-binding tooling. The registry-level consequences of DRAFT and FINAL (immutability guarantees, transitive constraints) are out of scope for this annex.

E.2.5 Validation Entry Points - $entries

The optional $entries block declares additional validation entry points within a single schema. Each entry maps a public name to a local type definition; consumers can request validation against a specific entry rather than only against the root $oky payload.

Syntax:

{
  "$entries": {
    "AnimalCreate": "&Animal",
    "OrderCreate":  "&Order"
  },
  "$defs": {
    "Animal": { "name|@ {2,50}": "Rex", "age|@ (0..50)": 3 },
    "Order":  { "id|@ ~^ORD-[0-9]+$~": "ORD-1" }
  },
  "$oky": { "label|@ {1,100}": "Pet store API" }
}

Normative Rules:

Validating a JSON payload against "AnimalCreate" enforces the Animal contract; validating without an entry name targets $oky.

Note: $entries is a runtime concern; it does not affect what other schemas can import. See §E.4.5 Visibility for export control.


E.3 Dependency Declaration - $deps

The $deps block declares external dependencies with version constraints.

E.3.1 Syntax

{
  "$id": "orders",
  "$version": "2.1.0",

  "$oky": { },

  "$deps": {
    "common": "~1.3.0",
    "crm": "^2.0.0",
    "billing": "1.5.0"
  }
}

E.3.2 Version Constraint Syntax

Okyline supports three constraint modes, following semver conventions:

Syntax Name Matches Example
1.3.4 Exact Only this version 1.3.4 only
~1.3.0 Tilde (patches) Same MAJOR.MINOR, any PATCH 1.3.0, 1.3.1, 1.3.17… but not 1.4.0
^1.3.0 Caret (compatible) Same MAJOR, any MINOR/PATCH 1.3.0, 1.4.0, 1.9.5… but not 2.0.0

The choice of constraint mode is a governance decision left to each organization. The specification does not mandate or recommend a particular strategy.

E.3.3 Normative Rules

Dependency Presence Rule (Normative)

$deps MUST be present whenever the schema contains at least one non-empty $import import block, whether inside $defs, $nomenclature, $format, or $compute. A schema that declares $import imports without a corresponding $deps section MUST be rejected at load time.

If no $import import block is present (or all are empty), $deps SHOULD be omitted, but a $deps entry that is not referenced by any $import import is tolerated. Hygiene of unused dependency declarations is a governance concern that implementations MAY surface through separate linting tooling, not through schema rejection. This aligns with the convention adopted by mainstream package managers (npm, Maven, Cargo). —

E.4 External Definition Imports - $import

The $import block creates local aliases for definitions from external schemas.

E.4.1 Syntax

{
  "$id": "orders",
  "$version": "2.1.0",

  "$oky": {
    "order": {
      "shippingAddress | $ref @": "&Address",
      "customerEmail | $ref @": "&Email"
    }
  },

  "$defs": {
    "$import": {
      "Address": "&common.Address",
      "Email": "&common.Email"
    }
  },

  "$deps": {
    "common": "~1.3.0"
  }
}

E.4.2 Semantics

Once imported, aliases in $import behave exactly like entries in $defs:

E.4.3 Normative Rules

Import Kind Restriction (Normative)

$import MUST be declared inside one of the four sub-blocks; each sub-block restricts what kinds of definitions can be imported through it:

Any $import entry that attempts to import an element of a different kind than its enclosing block MUST cause schema loading to fail.

A $import block declared at the root of the schema document (not inside one of the four sub-blocks) MUST cause schema loading to fail.

Implementation Dependency (Normative)

Support for external imports via $import requires support for $ref resolution as defined in Annex D.

An implementation that does not implement $ref MUST reject any schema that contains at least one non-empty $import import block.

E.4.4 Aliasing for Disambiguation

When multiple external schemas define a definition with the same name, $import aliases resolve the ambiguity:

{
  "$id": "shipping",
  "$version": "1.0.0",

  "$oky": {
    "Shipment": {
      "destination | $ref @": "&Address",
      "legacyOrigin | $ref ?": "&LegacyAddress"
    }
  },

  "$defs": {
    "$import": {
      "Address": "&common.Address",
      "LegacyAddress": "&legacy.Address"
    }
  },

  "$deps": {
    "common": "~1.0.0",
    "legacy": "~2.0.0"
  }
}

Both common and legacy define Address, but the local aliases Address and LegacyAddress are unambiguous within this schema.

E.4.5 Visibility - $public and $private

Each sub-block ($defs, $nomenclature, $format, $compute) MAY declare a visibility rule that controls which of its entries are exposable to external imports via $import. The two directives are mutually exclusive within a single sub-block.

Syntax:

{
  "$defs": {
    "Animal":  { "name|@": "Rex" },
    "Helper":  { "x|@": "v" },
    "$private": ["Helper"]
  },
  "$nomenclature": {
    "STATUS":  "DRAFT,FINAL",
    "$public": ["STATUS"]
  },
  "$format": {
    "OrderId": "^ORD-[0-9]+$",
    "$private": []
  }
}

Semantics:

Directive present in a sub-block Effect
Neither $public nor $private All entries are exposed (default - backward-compatible).
$public declared Strict allow-list: only listed entries are exposed. Adding a new entry leaves it private by construction.
$private declared Deny-list: every entry is exposed except the listed ones.
Both declared Error - mutually exclusive.

The listed names are bare (no sigil); the enclosing sub-block determines the kind unambiguously.

Normative Rules:

Effect on imports:

An external $import import that references a hidden name (entry present in the source sub-block but not exposed) MUST fail with the same error as if the name did not exist. The source schema MUST NOT disclose whether the name is genuinely absent or merely hidden.

Example:

{
  "$id": "common",
  "$version": "1.0.0",
  "$defs": {
    "Address":  { "city|@": "Paris" },
    "Internal": { "x|@": "v" },
    "$private": ["Internal"]
  },
  "$oky": { "f|@": "v" }
}

A consumer schema MAY import &common.Address but MUST NOT import &common.Internal; attempting the latter is treated as an unknown name.

Note: Visibility is a barrier to import, not a runtime gate. A type that is exposed but uses a hidden nomenclature/format/compute/type internally continues to resolve those internal references at validation time - the source’s own scope is preserved (§E.8.5).

E.4.6 Re-export through Alias Chains

A $import alias is itself a valid import target. An external import &schemaId.Name succeeds whether Name is a local entry of the target sub-block OR an exposed $import alias of that target. This applies symmetrically to all four kinds (types, nomenclatures, formats, computes).

Re-export is subject to the same visibility rules as direct exposure (§E.4.5). Cycles in alias chains MUST cause schema loading to fail.

Example: an intermediate lib region re-exports common.Address as Addr; a consumer app imports &region.Addr and resolves transparently to common.Address.


E.5 External Reference Syntax

E.5.1 In $import Only

External references use the syntax:

&schemaId.DefinitionName

Examples:

"$defs": {
  "$import": {
    "Address": "&common.Address",
    "Customer": "&crm.Customer",
    "Invoice": "&fr.billing.Invoice"
  }
}

Structure:

E.5.2 Restriction

External reference syntax (&schemaId.Name) is only valid in $import.

Inside $oky and $defs, all references MUST use local names:

//  Correct - local alias
"address | $ref": "&Address"

//  Invalid - external syntax not allowed in $oky
"address | $ref": "&common.Address"

This restriction ensures that:


E.6 Registry Resolution

E.6.1 Registry Role

The reference implementation maintains a schema registry that maps ($id, $version) to Okyline schema documents.

E.6.2 Resolution Algorithm

Constraint matching follows strict semver ordering. A pre-release version is, by semver, strictly less than its stable counterpart at the same {major, minor, patch} (e.g. 1.3.0-rc.1 < 1.3.0). Consequently, ~, ^, and exact constraints do not match pre-releases of the base {major, minor, patch} unless the constraint itself names a pre-release (see E.6.4 for the explicit opt-in form). This aligns Okyline with the mainstream package-manager convention (npm, Cargo, Maven).

When resolving a dependency, the resolver performs a two-pass selection:

  1. Pass 1 - Stable preferred. Among matching versions, selects the highest stable version (no pre-release suffix). If found, this is the resolved version regardless of consumer stability.
  2. Pass 2 - Pre-release fallback (gated). Only if no stable version matches AND the consumer is itself a pre-release (the F.5 stability contagion rule, §E.6.3) OR the constraint explicitly names a pre-release, the resolver selects the highest pre-release version matching the constraint.

If neither pass yields a candidate, the resolution fails and the schema MUST be rejected.

E.6.3 Pre-release Fallback (F.5 Stability Contagion)

A schema whose own $version carries a pre-release suffix is itself unstable. To support development workflows where consumer and dependency schemas evolve in parallel pre-release cycles, an unstable consumer is allowed to fall back to a matching pre-release dependency (Pass 2 above). A stable consumer is not so allowed: it sees only stable matches, unless the constraint itself opts in to pre-releases.

The resolution table below illustrates both consumer modes:

Constraint Available in registry Stable consumer Pre-release consumer
"2.0.0" 2.0.0 2.0.0 2.0.0
"~1.3.0" 1.3.0, 1.3.1, 1.3.2-beta 1.3.1 1.3.1
"~1.3.0" 1.3.0, 1.3.5-rc.1 1.3.0 1.3.0
"~1.3.0" 1.3.5-rc.1, 1.3.5-rc.2 no match 1.3.5-rc.2
"~1.3.0" 1.3.0-rc.1, 1.3.0-rc.2 no match no match
"^2.0.0" 2.0.0, 2.1.0, 2.2.0-rc.1 2.1.0 2.1.0

Note the last two rows: pre-releases of the constraint base patch (e.g. 1.3.0-rc.1 for ~1.3.0) are not matched even by an unstable consumer, because they rank below the base in strict semver order. To allow them, the consumer must opt in explicitly via the constraint syntax described in §E.6.4.

Relation to Mainstream Package Managers

Okyline’s resolution combines two regimes:

This combination is intentional: it preserves predictable resolution for released schemas (a stable consumer never accidentally pulls in a pre-release dep), while keeping in-flight development workflows ergonomic (an unstable consumer can iterate against the latest pre-release patches without continuously rewriting its $deps).

E.6.4 Explicit Pre-release Opt-in

To target a base patch that has not yet released as stable, the consumer writes the constraint with a pre-release suffix:

"$deps": { "lib": "~1.3.0-rc.0" }

Such a constraint matches:

This makes the intent explicit (the consumer knows it is targeting an unreleased version) and removes the ambiguity that would otherwise come with implicit pre-release fallback for stable-base constraints.

E.6.5 Resolution Guarantees


E.7 Interaction with Annex D

E.7.1 Unified Namespace

Once imported via $import, external definitions join the same namespace as $defs entries:

Available definitions = $defs ∪ $import

All Annex D mechanisms work identically on both:

Mechanism Internal ($defs) External ($import)
$ref (property-level)
$ref (object-level)
$override
$remove

E.7.2 Name Collision Rule

A definition name MUST NOT appear as both a local entry of $defs and an alias inside $defs.$import:

//  Invalid - collision
{
  "$oky": { },
  "$defs": {
    "Address": { "city|@": "Paris" },
    "$import": {
      "Address": "&common.Address"
    }
  }
}

If a collision occurs, the schema MUST be rejected at load time.

E.7.3 Transitive Dependencies

External schemas may themselves have dependencies. The registry is responsible for resolving the full dependency graph.

Rules:


E.8 Resolution Scope Rules (All Root Blocks)

This section defines the global resolution scope rules for Okyline.
These rules apply uniformly to all reusable elements defined or imported by a schema:

The goal is to guarantee deterministic behavior, strict version isolation,
and predictable inheritance semantics, even in the presence of external imports
and object-level inclusion.


E.8.1 Owner Scope of a Definition

Every reusable element in Okyline belongs to exactly one owner contract scope:

(scope) = ($id, $version)

This rule applies uniformly to all element kinds:

The owner scope of an element never changes implicitly.

External owner scopes are introduced only through explicit $import imports ($import inside $defs, $nomenclature, $format, or $compute). $deps declarations do not create visibility by themselves.


E.8.2 Global Namespace Rules for Type Definitions

Type and object definitions participate in a single global namespace per contract.

Accordingly:

Available types = $defs (local entries) ∪ $defs.$import (imported aliases)

Any $import declared inside $nomenclature, $format, or $compute:


E.8.3 Reference Resolution Principle

Any reference to a reusable name:

is always resolved within the owner scope of the element that contains the reference.

References are:


E.8.4 Resolution in Local Definitions

For any element declared in the current contract:

"$nomenclature": {
  "COUNTRY": "FR,DE,ES"
}

or:

"$defs": {
  "Address": {
    "country|@ ($COUNTRY)": "FR"
  }
}

All references inside the element are resolved in the
current contract’s owner scope.


E.8.5 Resolution in Imported Definitions ($import)

When a definition is imported via $defs.$import:

"$deps": { "crm": "2.0.1" },
"$defs": { "$import": { "Customer": "&crm.Customer" } }

the imported definition:

The consuming contract does not alter this behavior.


E.8.6 Object-Level Inclusion ($ref) and Field Injection

When object-level inclusion is used:

"Order": {
  "$ref": "&Customer",
  "orderId|@": "ORD-1"
}

the implementation performs field injection.

Normative Rule

Injected fields retain their original owner scope.

Consequences:

This guarantees that importing a type preserves the exact behavior defined and tested in its original contract version.


E.8.7 Overrides and Scope Rebinding

$override is the only mechanism that changes resolution scope.

Example:

"country | $override @ ($COUNTRY)": "FR"

Semantics


E.8.8 Local Additions

Any field added locally (not inherited) is:


E.8.9 Imported Nomenclatures, Formats and Computes

When a contract imports shared resources explicitly:

"$deps": { "geo": "1.4.0" },
"$nomenclature": {
  "$import": { "COUNTRY": "&geo.COUNTRY" }
}

the imported name:

Thus:

"country|@ ($COUNTRY)": "FR"

resolves against geo.COUNTRY, not a local definition.

The same rule applies to $format.$import and $compute.$import.

Numeric precision of imported computes. When a compute is imported via $compute.$import (or inherited through a field of an imported type), the validator evaluates it with the source contract’s $decimalScale - not the consumer’s. The library author chose that precision for a domain reason (often financial); silently inheriting the consumer’s scale would cause incorrect rounding.

Opt-in. This scale switch happens only when the source contract has explicitly declared $decimalScale. A contract that does not declare one is treated as “no opinion” and inherits the active scale of the validation.

Example. A tax library at $decimalScale: 6 defines round(amount * 0.1965, 6). When imported by a consumer at $decimalScale: 2, the multiplication still runs at scale 6, preserving the 4 fractional digits needed for accurate fiscal rounding before final cent-level display.


E.8.10 No Implicit Cross-Scope Rebinding

The following behaviors are explicitly forbidden:

Any such behavior would break determinism and testability.


E.8.11 Summary of Scope Rules

Situation Resolution Scope
Local definition Current contract
Imported type definition ($defs.$import) Source contract
Injected field via $ref Source contract
Overridden field ($override) Current contract
Local field addition Current contract
Imported nomenclature / format / compute Source contract
$decimalScale for imported compute Source contract if defined

E.9 Summary

Feature Description
$id Schema identifier for registry publication and external references
$version Schema version following semver format
$state Lifecycle state of the contract ("DRAFT" or "FINAL", default "DRAFT")
$entries Additional validation entry points (public name → local type reference)
$deps Declares external dependencies with version constraints (~, ^, exact)
$import Creates local aliases for external definitions
$public / $private Per-sub-block visibility - controls what is exposable to $import import
&schemaId.Name External reference syntax (only in $import)
&Name Local reference syntax (in $oky, $defs) - resolves in $defs$import
Re-export A $import alias is a valid import target - intermediate libs can re-export
Registry resolution Highest compatible version, with pre-release fallback
Transitive dependencies Resolved by registry; cycles forbidden; multiple versions of the same $id MAY coexist

Conformance Levels

Level Annexes Capabilities
Core D only Internal composition ($defs, $ref, $override, $remove)
Full D + E Core + external imports and versioning

E.10 Complete Example

E.10.1 Shared Schema: common v1.2.0

{
  "$id": "common",
  "$version": "1.2.0",
  "$title": "Common Definitions",

  "$oky": { },

  "$defs": {
    "Email|~$Email~ {5,100}": "user@example.com",

    "DateTime|~$DateTime~": "2025-01-01T00:00:00Z",

    "Address": {
      "street|@ {5,100}": "123 Main Street",
      "city|@ {2,50}": "Paris",
      "postalCode|@ {5,10}": "75001",
      "country|@ {2}": "FR"
    },

    "Auditable": {
      "createdAt|@ $ref": "&DateTime",
      "updatedAt|@ $ref": "&DateTime"
    }
  }
}

E.10.2 Consumer Schema: ecommerce v1.0.0

{
  "$id": "ecommerce",
  "$version": "1.0.0",
  "$title": "E-Commerce Order Schema",

  "$oky": {
    "order": {
      "$ref": "&Auditable",
      "orderId|@ # ~$OrderId~": "ORD-12345678",
      "customerEmail | $ref @": "&Email",
      "status|@ ($ORDER_STATUS)": "PENDING",
      "items | $ref @ [1,100]": ["&OrderItem"],
      "shippingAddress | $ref @": "&Address",
      "billingAddress | $ref": "&Address",
      "total|@ (%OrderTotal)": 100.00,

      "$requiredIf status('SHIPPED','DELIVERED')": ["trackingNumber"],
      "trackingNumber|{10,50}": "TRACK123456"
    }
  },

  "$defs": {
    "$import": {
      "Email": "&common.Email",
      "Address": "&common.Address",
      "Auditable": "&common.Auditable"
    },
    "OrderItem": {
      "sku|@ # {5,20}": "SKU-12345",
      "name|@ {2,200}": "Product Name",
      "quantity|@ (1..1000)": 1,
      "unitPrice|@ (>0)": 10.00
    }
  },

  "$format": {
    "OrderId": "^ORD-[0-9]{8}$"
  },

  "$compute": {
    "ItemTotal": "unitPrice * quantity",
    "OrderTotal": "sum(items, %ItemTotal)"
  },

  "$nomenclature": {
    "ORDER_STATUS": "PENDING,CONFIRMED,SHIPPED,DELIVERED,CANCELLED"
  },

  "$deps": {
    "common": "~1.2.0"
  }
}

E.10.3 Resolution Flow

  1. Registry resolves common: ~1.2.0 → finds common@1.2.3 (highest patch)
  2. $import aliases are bound:
  3. Schema body uses &Email, &Address, &Auditable as if they were local
  4. All Annex D composition rules apply normally

End of Annex E - External Imports and Versioning (Normative)