Keys and References

Indexes allow validators to enforce uniqueness and to create references between different parts of a configuration document.

Indexes are defined using the reserved section list vr_key and can appear either at the document root or inside a section definition. Once defined, the values collected by an index can be referenced using the key constraint (see Key).

This mechanism enables referential integrity within a configuration, for example by ensuring that a reference in one section points to an identifier defined elsewhere.

*[vr_key]*
name: "filter"
key: "filter.identifier"

[filter]
type: "SectionList"

[filter.vr_entry.identifier]
type: "text"

[app.start_filter]
type: "text"
key: "filter"
*[filter]*
identifier: "first"

*[filter]*
identifier: "second"

[app]
start_filter: "first"

Rules for Indexes

  1. Index Creation: An index is created by defining a section list named vr_key.

    *[vr_key]*
    name: "filter"
    key: "filter.identifier"
    
  2. Uniqueness: All key values collected in an index must be unique. Validators must report an error if a duplicate key is encountered.

    *[filter]*
    identifier: "one"
    
    *[filter]*
    identifier: "one"  # ERROR: Identifier must be unique
    
  3. Placement: A vr_key section list may appear either:

    • at the document root, or

    • inside a node-rules definition for a section.

    [app.server]
    type: "SectionList"
    
    *[app.server.vr_entry.vr_key]*
    name: "connection_id"
    key: "connection.id"
    
  4. Scope and Visibility: An index is visible only within the subtree in which it is defined. References outside this scope are invalid.

    *[app.server.vr_entry.vr_key]*
    name: "connection_id"
    key: "connection.id"
    
    [app.connection]
    type: "text"
    key: "connection_id"  # ERROR: No such index in this scope
    
  5. Key Field: Each vr_key entry must contain a key field that specifies one or more values to be indexed.

    *[vr_key]*
    key: "filter.identifier"
    
  6. Optional Name: A vr_key entry may define a name field, which assigns an identifier to the index for use in key constraints.

    *[vr_key]*
    name: "filter"
    key: "filter.identifier"
    

Design Rationale

Indexes are intentionally scoped to the subtree in which they are defined. This prevents name collisions between unrelated parts of a configuration and keeps validation rules modular and predictable.

A global index space would introduce two problems:

  • Accidental collisions between unrelated rules using the same index name.

  • Unnecessary coupling between distant parts of the configuration.

If global or cross-document references are required, they should be modeled explicitly at a higher level instead of overloading vr_key.

Rules for Keys

  1. Text Name-Path Required: Each key value must be a text string containing a valid name-path.

    *[vr_key]*
    name: "filter"
    key: "filter.identifier"
    
  2. Allowed Value Types: A referenced key must point to either a text or an integer value.

    *[vr_key]*
    key: "blog.created"  # ERROR: Must reference text or integer
    
    [blog]
    type: "SectionList"
    
    [blog.vr_entry.created]
    type: "DateTime"
    
  3. Section List + Value Requirement: Each name-path must resolve to:

    • a section list, and

    • a value inside each entry of that section list.

    *[vr_key]*
    name: "filter"
    key: "app.filter.meta.id"
    
    [app.filter]
    type: "SectionList"
    
    [app.filter.vr_entry.meta.id]
    type: "text"
    

    In this example:

    • app.filter identifies the section list

    • meta.id identifies the value within each entry

  4. Composite Keys: If multiple keys are specified, their combination must be unique across all entries.

    *[vr_key]*
    key: "server.service", "server.protocol"
    
    [server]
    type: "SectionList"
    
    [server.vr_entry.service]
    type: "text"
    in: "api", "management"
    
    [server.vr_entry.protocol]
    type: "text"
    in: "https", "json"
    

Rules for Index Names

  1. Optional Name: An index may define a name entry, which is used by key constraints to reference the index.

  2. Naming Rules: Index names must follow the ELCL name rules.

    *[vr_key]*
    name: "%my-name%"  # ERROR: Invalid ELCL name
    
  3. Normalization and Comparison: Index names are normalized and compared according to the ELCL name rules.

    Underscores and spaces are equivalent, and comparisons are case-insensitive.

    *[vr_key]*
    name: "filter_index"
    key: "filter.identifier"
    
    [app.start_filter]
    type: "text"
    key: "Filter Index"  # VALID after normalization
    

Rules for Multi-Key Indexes

  1. Key Combination Representation: Multi-key indexes combine their key values into a single entry, separated by commas (,).

    *[vr_key]*
    key: "server.service", "server.protocol"
    
    [server]   # Creates the composite key "api,https"
    service: "api"
    protocol: "https"
    

    Design Rationale

    While commas could theoretically appear in key values, such cases can be avoided by disallowing commas in fields used as keys. This keeps index representation simple and efficient.

  2. Referencing Parts of a Multi-Key: A multi-key index can be referenced either as a whole or by individual parts using the key[index] syntax, where the index is zero-based.

    *[vr_key]*
    name: "server"
    key: "server.service", "server.protocol"
    
    [server.ports]
    type: "SectionList"
    
    [server.ports.vr_entry.protocol]
    type: "text"
    key: "server[1]"  # References only the protocol part
    key_error: "No server with this protocol was configured"