glossarist 0.4.33 → 0.4.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glossarist",
3
- "version": "0.4.33",
3
+ "version": "0.4.35",
4
4
  "description": "JavaScript SDK for Glossarist GCR packages — read, write, validate, and manage terminology concepts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -2,7 +2,7 @@ import * as yaml from 'js-yaml';
2
2
  import { Concept } from './models/concept.js';
3
3
  import { RelatedConcept } from './models/related-concept.js';
4
4
  import { HyperedgeRegistry } from './models/hyperedge-registry.js';
5
- import { migrateHyperedgeToRelation } from './migration/partitive-relation-migrator.js';
5
+ import { migrateHyperedgeToRelation } from './migration/hyperedge-migrator.js';
6
6
  import { InvalidInputError, YamlParseError } from './errors.js';
7
7
 
8
8
  // Structural keys are reserved at the concept level and excluded
@@ -101,7 +101,7 @@ export class ConceptLevelDiff extends GlossaristModel {
101
101
  this._sources = wrapListDiff(data.sources);
102
102
  this._dates = wrapListDiff(data.dates);
103
103
  this._relatedConcepts = wrapListDiff(data.relatedConcepts ?? data.related_concepts);
104
- // Unified n-ary relations diff. Accepts:
104
+ // Unified hyperedge diff. Accepts:
105
105
  // - data.relations (new unified shape)
106
106
  // - data.partitiveRelations /
107
107
  // data.partitive_relations /
@@ -127,7 +127,7 @@ export class ConceptLevelDiff extends GlossaristModel {
127
127
  get sources() { return this._sources; }
128
128
  get dates() { return this._dates; }
129
129
  get relatedConcepts() { return this._relatedConcepts; }
130
- /** Unified n-ary relations diff (PartitiveHyperedge + GenericHyperedge). */
130
+ /** Unified hyperedge diff (PartitiveHyperedge + GenericHyperedge). */
131
131
  get relations() { return this._relations; }
132
132
  /**
133
133
  * Filtered view of `relations` containing only PartitiveHyperedge
@@ -465,7 +465,7 @@ function diffConceptLevel(oldConcept, newConcept) {
465
465
  sources: diffSources(oldConcept.sources ?? [], newConcept.sources ?? []),
466
466
  dates: diffDates(oldConcept.dates ?? [], newConcept.dates ?? []),
467
467
  relatedConcepts: diffRelatedConcepts(oldConcept.relatedConcepts ?? [], newConcept.relatedConcepts ?? []),
468
- relations: diffNaryRelations(
468
+ relations: diffHyperedges(
469
469
  oldConcept.relations ?? [],
470
470
  newConcept.relations ?? [],
471
471
  ),
@@ -565,13 +565,13 @@ function diffRelatedConcepts(oldRC, newRC) {
565
565
  });
566
566
  }
567
567
 
568
- // Diff two unified n-ary relations arrays. Identity is polymorphic —
568
+ // Diff two unified hyperedge arrays. Identity is polymorphic —
569
569
  // dispatches via value.constructor.identityOf so PartitiveHyperedge
570
- // and GenericHyperedge (and any future n-ary subclass) coexist in
570
+ // and GenericHyperedge (and any future hyperedge subclass) coexist in
571
571
  // the same list. Cross-type entries with matching identity are still
572
572
  // treated as different (because their constructor differs and they
573
573
  // live under different wire keys).
574
- function diffNaryRelations(oldR, newR) {
574
+ function diffHyperedges(oldR, newR) {
575
575
  return diffSet(oldR, newR, {
576
576
  identityKey: relationIdentityOf,
577
577
  textKey: relationText,
@@ -125,10 +125,10 @@ function renderConceptLevel(diff, colors) {
125
125
  lines.push(...renderListDiff('Sources', diff.sources, colors, itemLabel));
126
126
  lines.push(...renderListDiff('Dates', diff.dates, colors, itemLabel));
127
127
  lines.push(...renderListDiff('Related', diff.relatedConcepts, colors, itemLabel));
128
- // Unified n-ary relations section. Replaces the per-type sections
128
+ // Unified hyperedge section. Replaces the per-type sections
129
129
  // (Partitive relations, Generic relations) — the underlying diff
130
130
  // is a single ListDiff over the unified .relations array.
131
- lines.push(...renderListDiff('N-ary relations', diff.relations, colors, relationLabel));
131
+ lines.push(...renderListDiff('Hyperedges', diff.relations, colors, relationLabel));
132
132
  lines.push(...renderListDiff('Groups', diff.groups, colors, itemLabel));
133
133
  lines.push(...renderListDiff('Sections', diff.sections, colors, itemLabel));
134
134
  lines.push(...renderListDiff('Tags', diff.tags, colors, itemLabel));
@@ -214,8 +214,16 @@ function relationLabel(r) {
214
214
  const ref = m?.ref ?? m ?? {};
215
215
  let tail = '';
216
216
  const mult = resolveMultiplicity(m);
217
- if (mult && mult !== 'compulsory') tail = ` (${mult})`;
217
+ if (mult && mult !== 'compulsory') tail += ` (${mult})`;
218
+ // PartitiveMember uses is_delimiting (boolean flag).
218
219
  if (m?.is_delimiting === true) tail += ' ⊘';
220
+ // GenericMember uses delimitingCharacteristic (string value per
221
+ // ISO 704:2022 §5.5.4.2.1) — show the delimiting characteristic
222
+ // text so reviewers can see the intension difference.
223
+ if (m?.delimitingCharacteristic) {
224
+ const c = Object.values(m.delimitingCharacteristic)[0];
225
+ if (typeof c === 'string' && c.length > 0) tail += ` ⟨${c}⟩`;
226
+ }
219
227
  return `${ref.id ?? ref.source ?? ref.text ?? '?'}${tail}`;
220
228
  }).join(', ')
221
229
  : '';
@@ -1,4 +1,4 @@
1
- // AbstractHyperedge — shared base for n-ary concept-system
1
+ // AbstractHyperedge — shared base for directed 1→N hyperedges
2
2
  // relations (PartitiveHyperedge, GenericHyperedge, future
3
3
  // SequentialRelation, AssociativeRelation).
4
4
  //
@@ -10,7 +10,7 @@
10
10
  // - sources / notes / status: administrative metadata
11
11
  //
12
12
  // Concrete leaves add type-specific fields if needed (currently none).
13
- // See docs/design/abstract-nary-relation.md (concept-model repo).
13
+ // See docs/design/abstract-hyperedge.md (concept-model repo).
14
14
 
15
15
  import { GlossaristModel } from './base.js';
16
16
  import { ConceptRef } from './concept-ref.js';
@@ -0,0 +1,26 @@
1
+ // ConceptSystemType — ISO 12620 A.7.1 concept system type enum.
2
+ //
3
+ // Permissible instances per ISO 12620:
4
+ // generic — species are part of the extension of the genus
5
+ // partitive — based on whole-part relations
6
+ // sequential — based on spatial and temporal relations
7
+ // associative — based on thematic or pragmatic relations
8
+ // mixed — two or more of the above (the realistic case)
9
+
10
+ export const CONCEPT_SYSTEM_TYPE = Object.freeze({
11
+ GENERIC: 'generic',
12
+ PARTITIVE: 'partitive',
13
+ SEQUENTIAL: 'sequential',
14
+ ASSOCIATIVE: 'associative',
15
+ MIXED: 'mixed',
16
+ });
17
+
18
+ export const CONCEPT_SYSTEM_TYPE_VALUES = Object.freeze(
19
+ Object.values(CONCEPT_SYSTEM_TYPE),
20
+ );
21
+
22
+ export function isValidConceptSystemType(value) {
23
+ return CONCEPT_SYSTEM_TYPE_VALUES.includes(value);
24
+ }
25
+
26
+ export default CONCEPT_SYSTEM_TYPE;
@@ -0,0 +1,117 @@
1
+ // ConceptSystem — first-class concept system entity per ISO 12620 A.7.
2
+ //
3
+ // Promotes the previously-implicit concept system (domains + tags +
4
+ // relation graph) to an explicit first-class entity. Per-file at
5
+ // `concept-systems/<id>.yaml`.
6
+ //
7
+ // See docs/design/concept-systems.md (concept-model repo).
8
+
9
+ import { GlossaristModel } from './base.js';
10
+ import { ConceptRef } from './concept-ref.js';
11
+ import {
12
+ CONCEPT_SYSTEM_TYPE,
13
+ CONCEPT_SYSTEM_TYPE_VALUES,
14
+ isValidConceptSystemType,
15
+ } from './concept-system-type.js';
16
+
17
+ export class ConceptSystem extends GlossaristModel {
18
+ constructor(data = {}) {
19
+ super();
20
+ this.id = _ensureNonEmptyString(data.id, 'id');
21
+ this.name = _ensureName(data.name);
22
+ this.type = _resolveType(data.type);
23
+ this.domain = data.domain ?? null;
24
+ this.purpose = data.purpose ?? null;
25
+ this.members = _ensureMembers(data.members);
26
+ this.hyperedges = Array.isArray(data.hyperedges) ? [...data.hyperedges] : [];
27
+ this.rootConcepts = Array.isArray(data.rootConcepts)
28
+ ? data.rootConcepts.map(c => c instanceof ConceptRef ? c : new ConceptRef(c))
29
+ : [];
30
+ this.sources = Array.isArray(data.sources) ? [...data.sources] : [];
31
+ this.status = data.status ?? null;
32
+ }
33
+
34
+ get isGeneric() { return this.type === CONCEPT_SYSTEM_TYPE.GENERIC; }
35
+ get isPartitive() { return this.type === CONCEPT_SYSTEM_TYPE.PARTITIVE; }
36
+ get isSequential() { return this.type === CONCEPT_SYSTEM_TYPE.SEQUENTIAL; }
37
+ get isAssociative() { return this.type === CONCEPT_SYSTEM_TYPE.ASSOCIATIVE; }
38
+ get isMixed() { return this.type === CONCEPT_SYSTEM_TYPE.MIXED; }
39
+
40
+ hasHyperedge(edgeId) {
41
+ return this.hyperedges.includes(edgeId);
42
+ }
43
+
44
+ hasMember(refOrUri) {
45
+ const uri = typeof refOrUri === 'string' ? refOrUri : _refUri(refOrUri);
46
+ return this.members.some(m => _refUri(m) === uri);
47
+ }
48
+
49
+ identity() {
50
+ return this.id;
51
+ }
52
+
53
+ toJSON() {
54
+ const obj = {
55
+ id: this.id,
56
+ name: { ...this.name },
57
+ type: this.type,
58
+ members: this.members.map(m => m.toJSON()),
59
+ };
60
+ if (this.domain != null) obj.domain = this.domain;
61
+ if (this.purpose != null) obj.purpose = { ...this.purpose };
62
+ if (this.hyperedges.length > 0) obj.hyperedges = [...this.hyperedges];
63
+ if (this.rootConcepts.length > 0) obj.rootConcepts = this.rootConcepts.map(c => c.toJSON());
64
+ if (this.sources.length > 0) obj.sources = [...this.sources];
65
+ if (this.status != null) obj.status = this.status;
66
+ return obj;
67
+ }
68
+
69
+ static fromJSON(data) {
70
+ return new ConceptSystem(data);
71
+ }
72
+ }
73
+
74
+ function _ensureNonEmptyString(value, fieldName) {
75
+ if (typeof value !== 'string' || value.length === 0) {
76
+ throw new Error(`ConceptSystem#${fieldName} must be a non-empty string`);
77
+ }
78
+ return value;
79
+ }
80
+
81
+ function _ensureName(value) {
82
+ if (value == null) return {};
83
+ if (typeof value !== 'object' || Array.isArray(value)) {
84
+ throw new Error('ConceptSystem#name must be a localized object (e.g. { eng: "..." })');
85
+ }
86
+ return { ...value };
87
+ }
88
+
89
+ function _resolveType(value) {
90
+ if (value == null) {
91
+ throw new Error(
92
+ 'ConceptSystem#type is required; one of ' +
93
+ CONCEPT_SYSTEM_TYPE_VALUES.join(', '),
94
+ );
95
+ }
96
+ if (!isValidConceptSystemType(value)) {
97
+ throw new Error(
98
+ `ConceptSystem#type ${JSON.stringify(value)} invalid; ` +
99
+ `must be one of: ${CONCEPT_SYSTEM_TYPE_VALUES.join(', ')}`,
100
+ );
101
+ }
102
+ return value;
103
+ }
104
+
105
+ function _ensureMembers(values) {
106
+ if (!Array.isArray(values) || values.length === 0) {
107
+ throw new Error('ConceptSystem#members must be a non-empty array');
108
+ }
109
+ return values.map(v => v instanceof ConceptRef ? v : new ConceptRef(v));
110
+ }
111
+
112
+ function _refUri(ref) {
113
+ const r = ref?.ref ?? ref ?? {};
114
+ return `${r.source ?? ''}:${r.id ?? ''}`;
115
+ }
116
+
117
+ export default ConceptSystem;
@@ -0,0 +1,25 @@
1
+ // ConceptType — ISO 704:2022 §5.2-5.3 general vs individual concept.
2
+ //
3
+ // general — concept corresponds to a category of objects
4
+ // (designated by terms or symbols)
5
+ // individual — concept corresponds to a unique object
6
+ // (designated by proper names / appellations)
7
+ //
8
+ // Individual concepts occupy the bottom rung of a concept ladder
9
+ // and cannot be subdivided into more specific concepts, but CAN be
10
+ // subdivided into parts via a partitive relation.
11
+
12
+ export const CONCEPT_TYPE = Object.freeze({
13
+ GENERAL: 'general',
14
+ INDIVIDUAL: 'individual',
15
+ });
16
+
17
+ export const CONCEPT_TYPE_VALUES = Object.freeze(Object.values(CONCEPT_TYPE));
18
+
19
+ export const DEFAULT_CONCEPT_TYPE = CONCEPT_TYPE.GENERAL;
20
+
21
+ export function isValidConceptType(value) {
22
+ return CONCEPT_TYPE_VALUES.includes(value);
23
+ }
24
+
25
+ export default CONCEPT_TYPE;
@@ -4,7 +4,7 @@ import { RelatedConcept } from './related-concept.js';
4
4
  import { PartitiveHyperedge } from './partitive-hyperedge.js';
5
5
  import { AbstractHyperedge } from './abstract-hyperedge.js';
6
6
  import { HyperedgeRegistry, groupHyperedgesByWireKey } from './hyperedge-registry.js';
7
- import { migrateHyperedgeToRelation } from '../migration/partitive-relation-migrator.js';
7
+ import { migrateHyperedgeToRelation } from '../migration/hyperedge-migrator.js';
8
8
  import { ConceptReference } from './concept-reference.js';
9
9
  import { ConceptDate } from './concept-date.js';
10
10
  import { ConceptSource } from './concept-source.js';
@@ -36,7 +36,7 @@ export class Concept extends GlossaristModel {
36
36
 
37
37
  this.relatedConcepts = _mapInstances(data.relatedConcepts ?? data.related ?? data.related_concepts ?? [], RelatedConcept);
38
38
 
39
- // Unified hyperedge array. Every n-ary relation (PartitiveHyperedge,
39
+ // Unified hyperedge array. Every hyperedge (PartitiveHyperedge,
40
40
  // GenericHyperedge, future TemporalHyperedge / AssociativeHyperedge /
41
41
  // SequentialHyperedge) lives here. The single public API is
42
42
  // `.relations`; external systems that need to filter by type use
@@ -0,0 +1,25 @@
1
+ // EquivalenceDegree — ISO 704:2022 §7.7.3 cross-language equivalence.
2
+ //
3
+ // full — equivalent intension across languages (default)
4
+ // partial — overlapping but not identical intension
5
+ // none — no corresponding concept in this language
6
+ // directional — monodirectional equivalence (see equivalence_note)
7
+
8
+ export const EQUIVALENCE_DEGREE = Object.freeze({
9
+ FULL: 'full',
10
+ PARTIAL: 'partial',
11
+ NONE: 'none',
12
+ DIRECTIONAL: 'directional',
13
+ });
14
+
15
+ export const EQUIVALENCE_DEGREE_VALUES = Object.freeze(
16
+ Object.values(EQUIVALENCE_DEGREE),
17
+ );
18
+
19
+ export const DEFAULT_EQUIVALENCE_DEGREE = EQUIVALENCE_DEGREE.FULL;
20
+
21
+ export function isValidEquivalenceDegree(value) {
22
+ return EQUIVALENCE_DEGREE_VALUES.includes(value);
23
+ }
24
+
25
+ export default EQUIVALENCE_DEGREE;
@@ -1,15 +1,82 @@
1
- // GenericMember — one member of a GenericHyperedge. Inherits the
2
- // ISO 704:2022 MECE shape from HyperedgeMember. Declared as a
3
- // distinct leaf class for type safety and to leave room for
4
- // generic-specific extensions.
1
+ // GenericMember — one member of a GenericHyperedge.
5
2
  //
6
- // See docs/design/abstract-nary-relation.md and
7
- // docs/design/generic-relation.md (concept-model repo).
3
+ // Per ISO 704:2022 §5.5.4.2.1 (Generic relations and general concepts):
4
+ //
5
+ // "In a generic relation, the intension of the subordinate concept
6
+ // includes the intension of the superordinate concept plus at least
7
+ // one additional delimiting characteristic."
8
+ //
9
+ // In hyperedge terms: when a GenericHyperedge has comprehensive=computer mouse
10
+ // and criterion=means of movement detection, each member (mechanical mouse,
11
+ // optomechanical mouse, optical mouse) carries a delimitingCharacteristic —
12
+ // the value of the criterion for that specific concept:
13
+ //
14
+ // mechanical mouse → 'detecting movement by means of rollers'
15
+ // optomechanical mouse → 'detecting movement by means of rollers and light sensors'
16
+ // optical mouse → 'detecting movement by means of light sensors'
17
+ //
18
+ // Multidimensionality (§5.6.3): the same comprehensive can be the
19
+ // '1' side of multiple generic hyperedges simultaneously, each by a
20
+ // different criterion. computer mouse is also comprehensive of
21
+ // {wired mouse, wireless mouse} under criterion=computer connection.
22
+ // That second hyperedge's members carry their own delimitingCharacteristic
23
+ // values ('using a corded electrical connection', 'using a cordless
24
+ // light or sound connection').
25
+ //
26
+ // delimitingCharacteristic is REQUIRED on GenericMember. Without it,
27
+ // the member is just a coordinate concept with no semantic content
28
+ // tying it to the criterion — ISO 704 says the relation is meaningless
29
+ // without the delimiting characteristic.
30
+ //
31
+ // LocalizedString shape (mirrors criterion on the parent hyperedge):
32
+ // { eng: 'detecting movement by means of light sensors' }
33
+ // { eng: '...', fra: '...' } — multiple languages supported
8
34
 
9
35
  import { HyperedgeMember } from './hyperedge-member.js';
10
36
 
11
37
  export class GenericMember extends HyperedgeMember {
12
- // Inherits all attributes and validations from HyperedgeMember.
38
+ constructor(data = {}) {
39
+ super(data);
40
+ this.delimitingCharacteristic = _resolveDelimitingCharacteristic(data.delimitingCharacteristic);
41
+ }
42
+
43
+ toJSON() {
44
+ const base = super.toJSON();
45
+ base.delimitingCharacteristic = { ...this.delimitingCharacteristic };
46
+ return base;
47
+ }
48
+ }
49
+
50
+ function _resolveDelimitingCharacteristic(value) {
51
+ if (value == null) {
52
+ throw new Error(
53
+ 'GenericMember.delimitingCharacteristic is required per ISO 704:2022 §5.5.4.2.1 ' +
54
+ '(the intension of a subordinate concept must include at least one additional ' +
55
+ 'delimiting characteristic beyond the superordinate).',
56
+ );
57
+ }
58
+ if (typeof value === 'string') {
59
+ if (value.length === 0) {
60
+ throw new Error(
61
+ 'GenericMember.delimitingCharacteristic cannot be empty (ISO 704:2022 §5.5.4.2.1)',
62
+ );
63
+ }
64
+ return { default: value };
65
+ }
66
+ if (typeof value === 'object') {
67
+ const entries = Object.entries(value).filter(([, v]) => typeof v === 'string' && v.length > 0);
68
+ if (entries.length === 0) {
69
+ throw new Error(
70
+ 'GenericMember.delimitingCharacteristic must have at least one non-empty string ' +
71
+ 'value (ISO 704:2022 §5.5.4.2.1)',
72
+ );
73
+ }
74
+ return Object.fromEntries(entries);
75
+ }
76
+ throw new Error(
77
+ `GenericMember.delimitingCharacteristic must be a string or a language-keyed object; ` +
78
+ `got ${typeof value}`,
79
+ );
13
80
  }
14
81
 
15
82
  export default GenericMember;
@@ -1,13 +1,21 @@
1
- // HyperedgeMember — abstract base shape for members of any
2
- // n-ary concept-system relation (PartitiveMember, GenericMember,
3
- // future AssociativeMember, SequentialMember).
1
+ // HyperedgeMember — abstract base shape for the members of any directed
2
+ // 1→N hyperedge (PartitiveMember, GenericMember, future AssociativeMember,
3
+ // SequentialMember).
4
4
  //
5
- // Carries the shared ISO 704:2022 MECE dimensions: presence × count,
6
- // plus the orthogonal is_delimiting flag.
5
+ // Carries ONLY the dimensions shared by every hyperedge member type:
6
+ // ref — ConceptRef [1] (the member concept)
7
+ // presence — 'required' | 'optional' (default: required)
8
+ // count — 'exactly_one' | 'at_least_one' | 'multiple' (default: exactly_one)
7
9
  //
8
- // Concrete leaf classes extend this with type-specific fields if
9
- // needed (currently none). See docs/design/abstract-nary-relation.md
10
- // (concept-model repo).
10
+ // These three form the ISO 704:2022 MECE pair (presence × count). The
11
+ // pair is the shared invariant across all hyperedge types — that's
12
+ // why it lives on the base.
13
+ //
14
+ // Type-specific extensions live on the leaves:
15
+ // PartitiveMember — is_delimiting (rake-diagram semantics, ISO 704 §5.5.4.2.2)
16
+ // GenericMember — delimitingCharacteristic (intension difference, ISO 704 §5.5.4.2.1)
17
+ //
18
+ // See docs/design/abstract-hyperedge.md (concept-model repo).
11
19
 
12
20
  import { GlossaristModel } from './base.js';
13
21
  import { ConceptRef } from './concept-ref.js';
@@ -30,14 +38,12 @@ export class HyperedgeMember extends GlossaristModel {
30
38
  this.ref = _ensureRef(data.ref);
31
39
  this.presence = _resolvePresence(data.presence);
32
40
  this.count = _resolveCount(data.count);
33
- this.is_delimiting = _resolveDelimiting(data.is_delimiting);
34
41
 
35
42
  _assertValidPair(this.presence, this.count);
36
43
  }
37
44
 
38
45
  get isRequired() { return this.presence === PARTITIVE_PRESENCE.REQUIRED; }
39
46
  get isOptional() { return this.presence === PARTITIVE_PRESENCE.OPTIONAL; }
40
- get isDelimiting() { return this.is_delimiting === true; }
41
47
 
42
48
  identity() {
43
49
  return HyperedgeMember.identityOf(this);
@@ -47,7 +53,6 @@ export class HyperedgeMember extends GlossaristModel {
47
53
  const obj = { ref: this.ref.toJSON() };
48
54
  if (this.presence !== DEFAULT_PRESENCE) obj.presence = this.presence;
49
55
  if (this.count !== DEFAULT_COUNT) obj.count = this.count;
50
- if (this.is_delimiting) obj.is_delimiting = true;
51
56
  return obj;
52
57
  }
53
58
 
@@ -95,20 +100,10 @@ function _resolveCount(value) {
95
100
  return c;
96
101
  }
97
102
 
98
- function _resolveDelimiting(value) {
99
- if (value == null) return false;
100
- if (typeof value !== 'boolean') {
101
- throw new Error(
102
- `HyperedgeMember.is_delimiting must be boolean; got ${typeof value}`,
103
- );
104
- }
105
- return value;
106
- }
107
-
108
103
  function _assertValidPair(presence, count) {
109
- // Delegate to the multiplicity SSOT (Phase 8 of TODO.abstract-hyperedge).
110
- // The canonical error string lives in one place: multiplicity.js.
111
- // Calling multiplicityFromPair throws on the invalid combination;
112
- // we don't need to duplicate the error message here.
104
+ // Delegate to the multiplicity SSOT. The canonical error string
105
+ // lives in one place: multiplicity.js. Calling multiplicityFromPair
106
+ // throws on the invalid combination; we don't need to duplicate the
107
+ // error message here.
113
108
  multiplicityFromPair(presence, count);
114
109
  }
@@ -32,9 +32,9 @@ export {
32
32
  isValidPluralityMarker,
33
33
  } from './plurality-marker.js';
34
34
 
35
- // v2 PartitiveHyperedge models (TODO.partitive-relation-v2). These
36
- // supersede the v1 hyperedge models above; the v1 models are kept for
37
- // backward-compat input. New code should use v2 exclusively.
35
+ // PartitiveHyperedge models. The wire format (partitive_relations)
36
+ // is unchanged from earlier versions; the v1 wire key (partitive_hyperedges)
37
+ // is parsed on input and migrated to v2 shape via hyperedge-migrator.js.
38
38
  export { PartitiveHyperedge } from './partitive-hyperedge.js';
39
39
  export { PartitiveMember } from './partitive-member.js';
40
40
 
@@ -89,7 +89,7 @@ export {
89
89
  export {
90
90
  migrateHyperedgeToRelation,
91
91
  downgradeRelationToHyperedge,
92
- } from '../migration/partitive-relation-migrator.js';
92
+ } from '../migration/hyperedge-migrator.js';
93
93
 
94
94
  export { makeEnum } from './enum.js';
95
95
  export { resolveHyperedgeColor } from './hyperedge-colors.js';
@@ -1,9 +1,18 @@
1
- // PartitiveMember — one member of a PartitiveHyperedge. Inherits the
2
- // ISO 704:2022 MECE shape from HyperedgeMember; the `comprehensive`
3
- // of its parent PartitiveHyperedge denotes the whole concept.
1
+ // PartitiveMember — one member of a PartitiveHyperedge.
4
2
  //
5
- // Distinct leaf class for type safety and to leave room for
6
- // Partitive-specific extensions.
3
+ // Adds to HyperedgeMember the partitive-specific `is_delimiting` flag
4
+ // from ISO 704:2022 §5.5.4.2.2 (rake diagram semantics):
5
+ //
6
+ // "Delimiting" parts (bold/3x-width line in source diagrams) behave
7
+ // like delimiting characteristics — they distinguish the comprehensive
8
+ // from coordinate concepts. Whether a part is delimiting depends on
9
+ // the concept system, the coordinate concepts, the inheritance
10
+ // principle, and the criterion of subdivision used.
11
+ //
12
+ // `is_delimiting` lives here on PartitiveMember, not on the base
13
+ // HyperedgeMember, because it is partitive-specific. GenericMember
14
+ // carries `delimitingCharacteristic` instead (the intension-difference
15
+ // value per §5.5.4.2.1, which always applies to every generic member).
7
16
  //
8
17
  // Matches concept-model/models/concepts/PartitiveMember.lutaml exactly:
9
18
  // +ref — ConceptRef [1]
@@ -13,14 +22,34 @@
13
22
  //
14
23
  // The combination (optional, at_least_one) is invalid: if a part is
15
24
  // optional, the at-least-one constraint is vacuous and collapses to
16
- // (optional, multiple). Rejected at construction.
25
+ // (optional, multiple). Rejected at construction by HyperedgeMember's
26
+ // delegate to multiplicityFromPair.
17
27
 
18
28
  import { HyperedgeMember } from './hyperedge-member.js';
19
29
 
20
30
  export class PartitiveMember extends HyperedgeMember {
21
- // Inherits all attributes and validations from HyperedgeMember.
22
- // Declared as a distinct class for type safety and to leave room
23
- // for future partitive-specific extensions.
31
+ constructor(data = {}) {
32
+ super(data);
33
+ this.is_delimiting = _resolveDelimiting(data.is_delimiting);
34
+ }
35
+
36
+ get isDelimiting() { return this.is_delimiting === true; }
37
+
38
+ toJSON() {
39
+ const base = super.toJSON();
40
+ if (this.is_delimiting) base.is_delimiting = true;
41
+ return base;
42
+ }
43
+ }
44
+
45
+ function _resolveDelimiting(value) {
46
+ if (value == null) return false;
47
+ if (typeof value !== 'boolean') {
48
+ throw new Error(
49
+ `PartitiveMember.is_delimiting must be boolean; got ${typeof value}`,
50
+ );
51
+ }
52
+ return value;
24
53
  }
25
54
 
26
55
  export default PartitiveMember;
@@ -0,0 +1,56 @@
1
+ // SequentialHyperedge — an ISO 12620 A.6.3 / ISO 704:2022 §5.5.5
2
+ // sequential relation connecting a comprehensive concept to two or
3
+ // more subordinate concepts in an ORDERED sequence (temporal,
4
+ // spatial, causal, developmental).
5
+ //
6
+ // Mirror of PartitiveHyperedge and GenericHyperedge. Member array
7
+ // order IS significant — reversing the array reverses the sequence.
8
+ // Use cases: manufacturing process steps, biological life cycles,
9
+ // software release lifecycles, supply chain stages, geological eras.
10
+ //
11
+ // See docs/design/abstract-nary-relation.md (concept-model repo).
12
+
13
+ import { AbstractHyperedge } from './abstract-hyperedge.js';
14
+ import { SequentialMember } from './sequential-member.js';
15
+ import { HyperedgeRegistry } from './hyperedge-registry.js';
16
+
17
+ export class SequentialHyperedge extends AbstractHyperedge {
18
+ // Per-class metadata block — the OCP contract.
19
+ static wireKey = 'sequential_relations';
20
+ static typeTag = 'sequential_relation';
21
+ static rdfType = 'gloss:SequentialRelation';
22
+ static memberClass = SequentialMember;
23
+ static v1WireKeys = [];
24
+ static kindLabel = 'SEQ';
25
+
26
+ constructor(data = {}) {
27
+ super(data);
28
+ this.members = data?.members == null
29
+ ? this.members
30
+ : (Array.isArray(data.members)
31
+ ? data.members.map(m =>
32
+ m instanceof SequentialMember ? m : new SequentialMember(m))
33
+ : this.members);
34
+ }
35
+
36
+ static identityOf(value) {
37
+ const v = value ?? {};
38
+ const c = v.comprehensive ?? {};
39
+ // For sequential, member order IS significant — do NOT sort.
40
+ const memberKeys = Array.isArray(v.members)
41
+ ? v.members.map(m => {
42
+ const r = m?.ref ?? m ?? {};
43
+ return `${r.source ?? ''}:${r.id ?? ''}`;
44
+ })
45
+ : [];
46
+ return `${c.source ?? ''}:${c.id ?? ''}|${memberKeys.join('|')}`;
47
+ }
48
+
49
+ static fromJSON(data) {
50
+ return new SequentialHyperedge(data);
51
+ }
52
+ }
53
+
54
+ export default SequentialHyperedge;
55
+
56
+ HyperedgeRegistry.register(SequentialHyperedge);
@@ -0,0 +1,13 @@
1
+ // SequentialMember — one member of a SequentialHyperedge. Inherits
2
+ // the MECE shape from HyperedgeMember. Distinct leaf class for type
3
+ // safety. Member array order IS significant for sequential hyperedges.
4
+ //
5
+ // See docs/design/abstract-nary-relation.md (concept-model repo).
6
+
7
+ import { HyperedgeMember } from './hyperedge-member.js';
8
+
9
+ export class SequentialMember extends HyperedgeMember {
10
+ // Inherits all attributes and validations from HyperedgeMember.
11
+ }
12
+
13
+ export default SequentialMember;
@@ -14,7 +14,7 @@
14
14
  //
15
15
  // The carrying concept links via `gloss:hasPartitiveRelation`.
16
16
  //
17
- // Replaces gloss-hyperedge.js (the v1 emitter) per
17
+ // Per
18
18
  // TODO.partitive-relation-v2 item 01.
19
19
  // Cross-repo alignment with glossarist-ruby's Rdf::GlossPartitiveRelation.
20
20
  //
@@ -1,7 +1,7 @@
1
1
  // Shared identity-key helpers for cross-relation validators.
2
2
  //
3
- // Extracted from PartitiveRelationCoherenceRule and
4
- // BinaryHasPartRedundancyRule per TODO.partitive-relation-v3/06.
3
+ // Extracted from HyperedgeCoherenceRule and
4
+ // BinaryHasPartRedundancyRule.
5
5
  // Eliminates drift: both rules now use the same notion of "same ref"
6
6
  // and "same criterion" instead of each defining its own _refKey and
7
7
  // _criterionKey with subtly different behaviors.