GlossaristGlossarist
Blog

Hyperedges, per-file storage, and definition types

Glossarist v3.3 ships GenericHyperedge (the genus/species rake parallel to PartitiveHyperedge), promotes n-ary relations to first-class files at relations/<id>/<slug>.yaml, and adds ISO 704:2022 §5.3 definition types. The clean break drops backward compatibility for bundled partitive_relations arrays.

Today we’re shipping three changes that finish the ISO 704:2022 rake story for Glossarist:

  1. GenericHyperedge — the genus/species n-ary rake, structural mirror of PartitiveHyperedge
  2. Per-file n-ary relation storage — every Partitive / Generic hyperedge now lives at relations/<comprehensive-id>/<criterion-slug>.yaml
  3. ISO 704:2022 §5.3 definition typesintensional, extensional, partitive, translated on DetailedDefinition

This is a clean break. We’ve dropped backward compatibility for the bundled partitive_relations: arrays on concept files. The wire format is per-file only. A migration script handles existing data.

What a hyperedge looks like

ISO 704:2022 rake diagrams encode member semantics in the line notation itself. A solid line means a part is required; a dashed line means optional; a bold 3x-width line means delimiting. The horizontal backline indicates whether the decomposition is complete (joined) or partial (continues off-page).

Here’s a PartitiveHyperedge — the VIM measurement result (2.9) rake:

Partitive hyperedge diagram showing measurement result with three parts
Partitive hyperedge — VIM measurement result rake showing three MECE member dimensions.

Three teeth drop from a joined backline: one solid (required part), one bold 3x (required + delimiting — distinguishes from coordinate concepts), and one dashed (optional). All three share the comprehensive AND the criterion of subdivision, making them coordinate concepts per ISO 12620.

1. GenericHyperedge

The ISO 704:2022 MECE model — presence × count as orthogonal dimensions, plus the bold-line delimiting flag — applies to both partitive and generalization (genus/species) rakes. PartitiveHyperedge ships the partitive side; GenericHyperedge ships the genus/species side.

The trigger for adding GenericHyperedge was concrete: OIML V 2-200:2010 contains ~8 generic hyperedges, several with 6 criterion-of-subdivision groups per comprehensive. Measurement standard (5.1) decomposes by realization medium, by calibration role, by governance level, by metrological hierarchy, by reference/working, and by travel — six distinct rakes on the same genus.

Here’s how that looks in source diagrams:

Generalization hyperedge with two criterion groups on measurement standard
Generalization hyperedge — OIML measurement standard with two criterion groups (multidimensionality).

Binary broader_generic edges can’t express this grouping coherently. You’d have six flat lists of narrower edges with no way to distinguish which species belong to which decomposition. GenericHyperedge groups species by criterion.

GenericHyperedge solves this with the same shape as PartitiveHyperedge:

GenericHyperedge extends AbstractHyperedge
  +comprehensive: ConceptRef [1]              # genus concept
  +members:       GenericMember [2..*]        # species concepts
  +completeness:  Completeness [0..1]         # complete | partial
  +criterion:     LocalizedString [0..1]      # subdivision principle
  +sources:       ConceptSource [0..*]        # bibliographic provenance
  +notes:         LocalizedString [0..1]      # optional commentary
  +status:        ConceptStatus [0..1]        # lifecycle (independent)

GenericMember extends HyperedgeMember
  +ref:           ConceptRef [1]
  +presence:      Presence [0..1]             # required | optional
  +count:         Count [0..1]                # exactly_one | at_least_one | multiple
  +is_delimiting: Boolean [0..1]              # ISO 704 bold 3x-width line

Two GenericHyperedges on the same comprehensive, distinguished by criterion, are distinct decompositions — not duplicates.

OCP-compliant type dispatch

Both leaves inherit from a shared AbstractHyperedge base, and members inherit from HyperedgeMember. A HyperedgeRegistry (JS) / autoload-backed constant (Ruby) maps the wire type discriminator (partitive_relation / generic_relation) to the correct class at load time.

Adding a fourth n-ary type later — e.g. SequentialHyperedge for ISO 12620 sequential concept systems — is one class addition plus a registry entry. No validator or schema refactor.

ExternalConcept as comprehensive

Two of OIML’s generalization rakes have parenthetical/external comprehensives — “(quantity expressed by a measurement unit)” and “(precision condition of measurement)”. These are ISO 704’s “parenthetic terms”: concepts taken as primitives, assumed understood.

Glossarist handles this: an ExternalConcept (status: external) can serve as the comprehensive of a GenericHyperedge. The hyperedge file lives in the ExternalConcept’s owning dataset; the ExternalConcept’s provided_by edge resolves the actual definition from elsewhere.

A new validator — check-external-as-comprehensive — warns when such an ExternalConcept has no provided_by edge, since the decomposition dangles otherwise.

2. Per-file relation storage

This is the architectural change with the biggest impact on dataset layout.

Until now, partitive_relations: arrays lived inside concept YAML files. That worked when the array was small. With OIML-scale data it breaks:

  • i18n bloat — a concept like measurement standard has 6 generic rakes, each carrying localized criterion + notes + sources’ modification text. ~60+ localized strings piled into the concept file alongside its own localized_concepts.
  • Editing conflicts — translator working on French definition vs terminologist restructuring the rake both touch the same YAML.
  • Provenance — every hyperedge should carry its own sources; that bulk doesn’t belong on the concept.
  • Lifecycle independence — a concept can be valid while one of its decompositions is draft. If they share a file, the file’s status is ambiguous.

The new layout

Per-file storage layout with directory tree and YAML preview
Per-file storage layout — concept files alongside relation files under relations/<id>/.

Each file holds exactly one hyperedge (one comprehensive + one criterion

  • its members). The directory IS the index — relations/5-1/ enumerates every decomposition of concept 5.1.

Why inline i18n (not per-language sub-files)

Hyperedge text is short — criterion is a phrase, notes are 1-2 sentences. Inline LocalizedString (object keyed by ISO 639 code) is the right granularity. Per-language sub-files would 4x the file count for marginal translator benefit.

This matches what PartitiveHyperedge.criterion already does — extend the pattern to notes and any other text fields.

What stays bundled

Binary related edges stay in the concept file. The argument for per-file doesn’t apply with the same force:

  • A binary edge has at most one content (localized text), usually short
  • Edges are structurally part of the concept’s neighborhood
  • Splitting would 10x the file count for concepts with many related entries

The dividing line is “n-ary with criterion.” Anything that crosses it becomes a file. Binary stays inline.

Member multiplicity — MECE decomposition

ISO 704:2022 encodes per-member multiplicity via two orthogonal diagram line dimensions. Glossarist decomposes this into a MECE pair: presence (solid vs dashed) × count (line count).

MECE member multiplicity table showing all 5 valid combinations plus the invalid combo
MECE member multiplicity — all five valid presence × count combinations plus the invalid one.

Five valid combinations, one invalid. The invalid (optional, at_least_one) combo is rejected: an optional part has vacuous at-least-one (it may not exist at all), so it collapses to (optional, multiple). Both the JSON Schema and the Ruby/JS validators reject it with a clear error message.

The third orthogonal flag — is_delimiting — encodes the bold 3x-width line. It applies regardless of presence/count.

3. Definition types (ISO 704:2022 §5.3)

ISO 704 distinguishes four definition strategies. We’ve added type to DetailedDefinition:

definition:
  - type: intensional
    content: "A measurement unit is a real scalar quantity..."
  - type: partitive
    content: "A measurement standard is composed of a reference
              material, a measuring system, or a material measure."

Values: intensional (default), extensional, partitive, translated.

Additive change. When type is omitted, the definition is treated as intensional — no migration needed for existing data.

The clean break

We are not preserving backward compatibility for the bundled partitive_relations: array format. The clean break simplified the implementation considerably:

  • No “two readers, one writer” plumbing in glossarist-ruby / glossarist-js
  • No long-tale support burden
  • Schema is simpler — concept.yaml has no relation-shaped $defs
  • Validators don’t need a “bundled or per-file?” dispatch

The only adopters today are internal (glossarist-ruby, glossarist-js, concept-browser, vocab datasets) — all are updated in lockstep with this release. External adopters, if any emerge, get the new format from day one.

Migration

The migrate-to-per-file-relations script in concept-model/exe/ handles existing data:

bundle exec exe/migrate-to-per-file-relations --backup path/to/concepts/

For each concept file with partitive_relations, the script:

  1. Extracts each hyperedge
  2. Derives a slug from the criterion (or “decomposition” if absent)
  3. Writes it to relations/<comprehensive-id>/<slug>.yaml
  4. Removes the partitive_relations array from the concept file

The --dry-run flag previews; the --backup flag preserves .bak files.

Lockstep rollout

  • concept-model — schemas, LUTAML, ontology, SHACL, JSON-LD, validators, migration script, examples
  • glossarist-rubyV3::GenericHyperedge, V3::GenericMember, V3::HyperedgeMember (abstract base), V3::AbstractHyperedge (abstract base), V3::RelationLoader, V3::DefinitionType. V3::PartitiveHyperedge refactored to inherit from AbstractHyperedge.
  • glossarist-js — mirror classes: GenericHyperedge, GenericMember, HyperedgeMember, RelationLoader, DefinitionType, plus a HyperedgeRegistry for OCP-compliant type dispatch
  • concept-browserGenericRelationList.vue (mirrors PartitiveRelationList.vue with type label swapped); adapter types extended
  • vocab datasets — migration script runs across VIM 2007/2010/2012 + OIML V 2-200:2010

What we deliberately did not do

Three architectural non-decisions worth flagging:

  1. No ConceptSystem entity. ISO 704 §5.4 describes concept systems as the substrate. We already model that via domains + tags + the relation graph — adding a fourth entity duplicates existing structure.
  2. No computed inheritance walk. ISO 704 §6.4’s inheritance principle is a reasoning pattern over correct intensional definitions, not a data structure. Adding one would duplicate data already in definitions.
  3. No AssociativeHyperedge n-ary. No evidence of associative rakes in our datasets yet. Binary related covers observed cases. Trigger to revisit: when a real dataset shows associative rakes with role-bearing members.

See docs/design/iso704-2022-mapping.md for the full reasoning.

The optomechanical mouse (ISO 704:2022 §5.5.4.2.2)

ISO 704:2022 uses optomechanical mouse as its canonical worked example for partitive decomposition. Here it is with all MECE dimensions exercised:

# relations/example-optomechanical-mouse/by-physical-structure.yaml
$id: example-optomechanical-mouse/by-physical-structure
type: partitive_relation
comprehensive: { source: EXAMPLE, id: "optomechanical-mouse" }
members:
  - ref: { source: EXAMPLE, id: "mouse-ball" }
    is_delimiting: true
  - ref: { source: EXAMPLE, id: "x-axis-roller" }
    is_delimiting: true
  - ref: { source: EXAMPLE, id: "y-axis-roller" }
    is_delimiting: true
  - ref: { source: EXAMPLE, id: "infrared-emitter" }
    is_delimiting: true
  - ref: { source: EXAMPLE, id: "infrared-sensor" }
    is_delimiting: true
  - ref: { source: EXAMPLE, id: "circuit-board" }
    # not delimiting — all computer mice have circuit boards
  - ref: { source: EXAMPLE, id: "mouse-button" }
    # not delimiting — all computer mice have buttons
  - ref: { source: EXAMPLE, id: "mouse-wheel" }
    presence: optional
    # not all optomechanical mice have a wheel
completeness: complete
criterion: { eng: physical structure }

The five delimiting parts distinguish optomechanical mouse from coordinate concepts like mechanical mouse (no infrared emitter/sensor) and optical mouse (no ball, no rollers). Mouse button and wheel are non-delimiting — they don’t distinguish the comprehensive from its coordinates.

Design history

This release consolidates four iterations of n-ary relation modeling, each driven by real dataset evidence:

DateIterationWhat changed
2026-07-22PartitiveHyperedge v1Initial model — enumeration: closed|open + opaque markers: [double, dashed]
2026-07-23PartitiveRelation v2ISO 704 / 1087-1 / 12620 terminology; markers promoted to structured data; per-member certainty; criterion field; ExternalConcept support
2026-07-29ISO 704:2022 MECEFlat multiplicity enum replaced by MECE pair presence × count; delimiting parts as third orthogonal dimension
2026-07-30Hyperedges + per-file + DefinitionType(this release) GenericHyperedge parallel to PartitiveHyperedge; both inherit AbstractHyperedge / HyperedgeMember; per-file storage; ISO 704 §5.3 definition types

Each iteration is preserved as a tag in the concept-model repository. The current wire format and class names are the ones described in this post; prior formats are reachable via git history if you need to understand the evolutionary pressure that shaped the current design.

What’s next

  • OIML V 2-200:2010 migration — the ~8 generic rakes move to per-file format with criterion-of-subdivision groups preserved.
  • VIM variants — VIM 2007/2010/2012 partitive relations already migrated; no additional work needed.
  • SequentialHyperedge — ISO 12620 sequential concept systems (spatial/ temporal rakes) modeled by analogy. Trigger: when a real dataset needs n-ary spatial/temporal grouping AND Partitive/Generic have been stable for one release cycle.

Further reading