glossarist 0.4.32 → 0.4.34

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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/src/concept-parser.js +67 -45
  3. package/src/concept-reader.js +59 -3
  4. package/src/concept-serializer.js +4 -4
  5. package/src/diff/concept-diff.js +13 -13
  6. package/src/diff/diff-patch.js +19 -27
  7. package/src/diff/diff-renderer.js +19 -6
  8. package/src/index.js +2 -1
  9. package/src/migration/{partitive-relation-migrator.js → hyperedge-migrator.js} +3 -3
  10. package/src/models/{abstract-nary-relation.js → abstract-hyperedge.js} +5 -5
  11. package/src/models/completeness.js +1 -1
  12. package/src/models/concept.js +145 -72
  13. package/src/models/{generic-relation.js → generic-hyperedge.js} +18 -6
  14. package/src/models/generic-member.js +76 -9
  15. package/src/models/hyperedge-index.js +87 -0
  16. package/src/models/{concept-system-member.js → hyperedge-member.js} +26 -43
  17. package/src/models/hyperedge-registry.js +115 -0
  18. package/src/models/index.d.ts +16 -16
  19. package/src/models/index.js +16 -13
  20. package/src/models/multiplicity.js +11 -6
  21. package/src/models/partitive-hyperedge.js +69 -153
  22. package/src/models/partitive-member.js +40 -11
  23. package/src/models/relation-loader.js +28 -22
  24. package/src/rdf/gloss-concept.js +36 -20
  25. package/src/rdf/gloss-generic-hyperedge.js +92 -0
  26. package/src/rdf/gloss-partitive-relation.js +4 -4
  27. package/src/rdf/index.d.ts +4 -4
  28. package/src/rdf/index.js +0 -1
  29. package/src/reference-resolver.js +14 -12
  30. package/src/validators/binary-has-part-redundancy-rule.js +6 -6
  31. package/src/validators/delimiting-coherence-rule.js +2 -2
  32. package/src/validators/hyperedge-coherence-rule.js +98 -0
  33. package/src/validators/index.js +3 -9
  34. package/src/validators/partitive-relation-coherence-rule.js +6 -79
  35. package/src/validators/ref-keys.js +2 -2
  36. package/src/models/partitive-relation.js +0 -76
  37. package/src/models/relation-type-registry.js +0 -25
  38. package/src/rdf/gloss-hyperedge.js +0 -90
  39. package/src/validators/partitive-drift-rule.js +0 -51
  40. package/src/validators/partitive-hyperedge-rule.js +0 -75
@@ -1,80 +1,7 @@
1
- // Validates PartitiveRelation coherence per ISO 704 / ISO 12620.
2
- //
3
- // Checks (per TODO.partitive-relation-v2 item 13):
4
- // 1. ERROR: each relation has ≥2 partitives (ISO 704 "two or more")
5
- // 2. ERROR: no two relations on the same concept share the same
6
- // comprehensive + same criterion (duplicate decomposition —
7
- // violates ISO 12620 coordinate-concept coherence)
8
- // 3. WARNING: a concept with multiple relations where any relation
9
- // lacks criterion (cannot verify distinctness from siblings)
10
- // 4. WARNING: a TypeSharedPlurality with is_uncertain=true but
11
- // is_shared=false (semantically odd — broken line qualifies
12
- // the close-set double line; without `double`, what's being
13
- // qualified?)
14
- //
15
- // Lives in the validator framework, not the model constructor, per
16
- // invariant I8 (TODO.hyperedges-v2/00). Constructors enforce type-shape;
17
- // validators enforce semantics.
1
+ // Deprecated. This rule has been generalized and renamed to
2
+ // HyperedgeCoherenceRule (see ./hyperedge-coherence-rule.js). The
3
+ // new rule is type-blind — it validates every AbstractHyperedge
4
+ // subclass, not just PartitiveHyperedge. This file remains as a
5
+ // re-export shim for existing imports.
18
6
 
19
- import { ValidationRule } from './validation-rule.js';
20
- import { refKey, criterionKey } from './ref-keys.js';
21
-
22
- export class PartitiveRelationCoherenceRule extends ValidationRule {
23
- constructor() { super('partitive-relation-coherence'); }
24
-
25
- validate(concept, path, result) {
26
- const relations = concept.partitiveRelations ?? concept.partitiveHyperedges ?? [];
27
- if (relations.length === 0) return;
28
-
29
- // Check 1: ≥2 partitives per relation. (The constructor also
30
- // rejects this, but a warning here surfaces it in the result
31
- // bundle for CI visibility.)
32
- for (let i = 0; i < relations.length; i++) {
33
- const partitiveCount = (relations[i]?.partitives ?? []).length;
34
- if (partitiveCount < 2) {
35
- this.addIssue(result,
36
- `${path}partitiveRelations[${i}].partitives`,
37
- `PartitiveRelation ${i + 1} has ${partitiveCount} partitive(s); ` +
38
- `ISO 704 requires ≥2 (use a binary has_part edge for pairwise facts)`,
39
- );
40
- }
41
- }
42
-
43
- // Check 2: no duplicate (comprehensive + criterion) on the same concept.
44
- const seen = new Map();
45
- for (let i = 0; i < relations.length; i++) {
46
- const rel = relations[i];
47
- const compKey = refKey(rel?.comprehensive);
48
- const critKey = criterionKey(rel?.criterion);
49
- if (compKey == null || critKey == null) continue;
50
- const key = `${compKey}|${critKey}`;
51
- if (seen.has(key)) {
52
- this.addIssue(result,
53
- `${path}partitiveRelations[${i}]`,
54
- `duplicate PartitiveRelation (same comprehensive + same criterion ` +
55
- `as relation ${seen.get(key) + 1}); violates ISO 12620 coordinate-concept coherence`,
56
- );
57
- } else {
58
- seen.set(key, i);
59
- }
60
- }
61
-
62
- // Check 3: criterion documentation (warning) when concept has multiple relations.
63
- if (relations.length > 1) {
64
- for (let i = 0; i < relations.length; i++) {
65
- if (!relations[i]?.criterion) {
66
- result.addWarning(
67
- `${path}partitiveRelations[${i}].criterion`,
68
- `concept has multiple PartitiveRelations but relation ${i + 1} ` +
69
- `has no criterion; cannot verify distinctness from siblings`,
70
- );
71
- }
72
- }
73
- }
74
-
75
- // (Check 4 — plurality coherence — removed. The prior plurality
76
- // block was based on a misreading of ISO 704; the corrected model
77
- // uses per-member multiplicity + is_delimiting, which are validated
78
- // at construction by PartitiveMember itself.)
79
- }
80
- }
7
+ export { HyperedgeCoherenceRule, PartitiveRelationCoherenceRule } from './hyperedge-coherence-rule.js';
@@ -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.
@@ -1,76 +0,0 @@
1
- // PartitiveRelation — an ISO 704:2022 / ISO 1087-1 / ISO 12620
2
- // partitive relation connecting a comprehensive concept
3
- // (superordinate concept partitive) to two or more partitive
4
- // concepts (subordinate concepts partitive) which fitted together
5
- // constitute the comprehensive.
6
- //
7
- // Extends AbstractNaryRelation; the leaf-specific concern is the
8
- // PartitiveMember type for `members`. The wire-level field name
9
- // stays `partitives` for v1/v2 backward compat.
10
- //
11
- // ISO 704:2022 rake diagram semantics:
12
- //
13
- // comprehensive concept
14
- // ─────────────────────
15
- // │ partitive (one or more) with per-member multiplicity + optional delimiting
16
- // ─────────────────────
17
- //
18
- // All partitives within one relation are coordinate concepts
19
- // (ISO 12620): they share the comprehensive AND share the
20
- // criterion of subdivision.
21
- //
22
- // "Delimiting" parts (bold/3x-width line in source diagrams) behave
23
- // like delimiting characteristics — they distinguish the comprehensive
24
- // from coordinate concepts. Whether a part is delimiting depends on
25
- // the concept system, the coordinate concepts, the inheritance
26
- // principle, and the criterion of subdivision used
27
- // (ISO 704:2022 §5.5.4.2.1).
28
- //
29
- // Distinguished from binary `has_part` / `is_part_of` edges, which
30
- // express pairwise part-of assertions without completeness,
31
- // multiplicity, or criterion claims. A single partitive should be
32
- // expressed as a binary edge, not a PartitiveRelation (ISO requires
33
- // "two or more").
34
-
35
- import { AbstractNaryRelation } from './abstract-nary-relation.js';
36
- import { PartitiveMember } from './partitive-member.js';
37
-
38
- export class PartitiveRelation extends AbstractNaryRelation {
39
- constructor(data = {}) {
40
- const members = data?.members ?? data?.partitives;
41
- super({ ...data, members });
42
- this.members = members == null
43
- ? this.members
44
- : (Array.isArray(members)
45
- ? members.map(m =>
46
- m instanceof PartitiveMember ? m : new PartitiveMember(m))
47
- : this.members);
48
- }
49
-
50
- get partitives() { return this.members; }
51
-
52
- toJSON() {
53
- const base = super.toJSON();
54
- const { members, ...rest } = base;
55
- return { ...rest, partitives: members };
56
- }
57
-
58
- static identityOf(value) {
59
- const v = value ?? {};
60
- const c = v.comprehensive ?? {};
61
- const partKeys = Array.isArray(v.partitives ?? v.members)
62
- ? (v.partitives ?? v.members).map(p => {
63
- const r = p?.ref ?? p ?? {};
64
- return `${r.source ?? ''}:${r.id ?? ''}`;
65
- }).sort()
66
- : [];
67
- return `${c.source ?? ''}:${c.id ?? ''}|${partKeys.join('|')}`;
68
- }
69
-
70
- static fromJSON(data) {
71
- const normalized = { ...(data ?? {}), members: data?.partitives ?? data?.members };
72
- return new PartitiveRelation(normalized);
73
- }
74
- }
75
-
76
- export default PartitiveRelation;
@@ -1,25 +0,0 @@
1
- // N-ary relation type registry. Single source of truth for the
2
- // `type` → class dispatch used by the parser, the per-file loader,
3
- // and Concept's unified relations resolver.
4
- //
5
- // Adding a new n-ary relation type (Sequential, Associative, …)
6
- // = one entry here plus one new class. Nothing else changes:
7
- // not Concept, not parser, not serializer, not diff, not patch,
8
- // not renderer.
9
- //
10
- // Lives in its own module (not relation-loader.js) because the loader
11
- // pulls in node:fs, which is off-limits on the browser-gcr path that
12
- // transitively imports this registry via Concept.
13
-
14
- import { PartitiveRelation } from './partitive-relation.js';
15
- import { GenericRelation } from './generic-relation.js';
16
-
17
- export const TYPE_TO_CLASS = Object.freeze({
18
- partitive_relation: PartitiveRelation,
19
- generic_relation: GenericRelation,
20
- });
21
-
22
- export function classForRelationType(type) {
23
- if (typeof type !== 'string') return null;
24
- return TYPE_TO_CLASS[type] ?? null;
25
- }
@@ -1,90 +0,0 @@
1
- // PartitiveHyperedge → RDF quads.
2
- //
3
- // Shape (per concept-model ontology, v3.1.0):
4
- // <base>/hyperedge/<carrying-id>/<comprehensive-id>
5
- // rdf:type gloss:PartitiveHyperedge
6
- // gloss:comprehensive <base>/concept/<comprehensive-id>
7
- // gloss:hasPart <base>/concept/<part-id> (one per part)
8
- // gloss:enumeration "closed"|"open" (literal)
9
- // gloss:hasPluralityMarker "double"|"dashed" (literal, one per marker)
10
- // gloss:hyperedgeContent "..." (literal, omitted if absent)
11
- //
12
- // The carrying concept links to this hyperedge via `gloss:hasPartitiveHyperedge`
13
- // (emitted by gloss-concept.js#conceptToQuads).
14
- //
15
- // CROSS-REPO STATUS (validated 2026-07-22):
16
- // - Subject URI scheme: matches Ruby (`hyperedge/<c>/<comp>`).
17
- // - `comprehensive`, `enumeration`, `hasPluralityMarker`, `hyperedgeContent`:
18
- // predicates + object types match Ruby.
19
- // - `hasPart`: JS follows the concept-model ontology; Ruby emits
20
- // `gloss:part` instead. This is a Ruby bug — tracked as a cross-repo
21
- // follow-up. Once Ruby aligns with the ontology, output will be
22
- // byte-equivalent (modulo serialization order).
23
-
24
- import { PRED } from './predicates.js';
25
- import { WELL_KNOWN } from './prefixes.js';
26
- import { namedNode, literal, quad } from './terms.js';
27
-
28
- export function* hyperedgeToQuads(hyperedge, { parentUri, index }) {
29
- const heSubject = hyperedgeSubjectUri(parentUri, hyperedge, index);
30
- const s = namedNode(heSubject);
31
-
32
- yield quad(s, namedNode(WELL_KNOWN.rdfType), namedNode(PRED.gloss.PartitiveHyperedge));
33
-
34
- const comp = hyperedge.comprehensive;
35
- if (comp && (comp.source || comp.id)) {
36
- yield quad(s, namedNode(PRED.gloss.comprehensive),
37
- namedNode(conceptRefUri(comp, parentUri)));
38
- }
39
-
40
- const parts = Array.isArray(hyperedge.parts) ? hyperedge.parts : [];
41
- for (const p of parts) {
42
- if (p && (p.source || p.id)) {
43
- yield quad(s, namedNode(PRED.gloss.hasPart),
44
- namedNode(conceptRefUri(p, parentUri)));
45
- }
46
- }
47
-
48
- if (hyperedge.enumeration) {
49
- yield quad(s, namedNode(PRED.gloss.enumeration), literal(hyperedge.enumeration));
50
- }
51
-
52
- for (const marker of Array.isArray(hyperedge.markers) ? hyperedge.markers : []) {
53
- yield quad(s, namedNode(PRED.gloss.hasPluralityMarker), literal(marker));
54
- }
55
-
56
- if (hyperedge.content) {
57
- const content = hyperedge.content;
58
- if (typeof content === 'string') {
59
- yield quad(s, namedNode(PRED.gloss.hyperedgeContent), literal(content));
60
- } else if (typeof content === 'object') {
61
- for (const [lang, text] of Object.entries(content)) {
62
- if (typeof text !== 'string') continue;
63
- yield quad(s, namedNode(PRED.gloss.hyperedgeContent), literal(text, lang));
64
- }
65
- }
66
- }
67
- }
68
-
69
- // Subject URI scheme: `<base>/hyperedge/<carrying-id>/<comprehensive-id>`.
70
- // Matches Ruby's `subject { |h| "hyperedge/#{h.identifier}/#{h.comprehensive_id}" }`.
71
- // Falls back to an index suffix if comprehensive has no id, so two
72
- // hyperedges on the same concept never collide.
73
- export function hyperedgeSubjectUri(parentUri, hyperedge, index) {
74
- const base = parentUri.replace(/\/concept\/[^/]+$/, '');
75
- const carrierId = parentUri.split('/concept/')[1] ?? `carrier-${index}`;
76
- const compId = hyperedge?.comprehensive?.id ?? `comp-${index}`;
77
- return `${base}/hyperedge/${carrierId}/${compId}`;
78
- }
79
-
80
- // ConceptRef URI: `<base>/concept/<id>`. Matches Ruby's
81
- // `concept_to_gloss_transform.rb#hyperedge_concept_uri`. Source is
82
- // dropped from the URI; it stays on the ConceptRef object if needed.
83
- function conceptRefUri(ref, parentUri) {
84
- const id = ref?.id;
85
- if (id) {
86
- const base = parentUri.split('/concept/')[0];
87
- return `${base}/concept/${id}`;
88
- }
89
- return `urn:glossarist:${ref?.source ?? 'unknown'}:`;
90
- }
@@ -1,51 +0,0 @@
1
- // Warns when a concept expresses the same partitive relationship via
2
- // BOTH a binary edge (broader_partitive / narrower_partitive /
3
- // has_part / is_part_of) AND a PartitiveHyperedge part targeting the
4
- // same concept. Round 1's decision (TODO.hyperedges/24) was "binary
5
- // and hyperedge coexist, no automatic reconciliation." This rule is
6
- // the detector for the resulting drift: pure warning, doesn't block
7
- // save, just surfaces the duplication so the author can pick one
8
- // encoding.
9
- //
10
- // See TODO.hyperedges-v2/21 for the design rationale.
11
-
12
- import { ValidationRule } from './validation-rule.js';
13
-
14
- const BINARY_PARTITIVE_TYPES = new Set([
15
- 'broader_partitive', 'narrower_partitive',
16
- 'has_part', 'is_part_of',
17
- ]);
18
-
19
- export class PartitiveDriftRule extends ValidationRule {
20
- constructor() { super('partitive-drift', 'warning'); }
21
-
22
- validate(concept, path, result) {
23
- const hyperedgeTargets = new Set();
24
- for (const he of concept.partitiveHyperedges ?? []) {
25
- const comp = he.comprehensive;
26
- const compKey = `${comp?.source ?? ''}|${comp?.id ?? ''}`;
27
- for (const part of he.parts ?? []) {
28
- const key = `${part.source ?? ''}|${part.id ?? ''}`;
29
- if (key === '|' || key === compKey) continue;
30
- hyperedgeTargets.add(key);
31
- }
32
- }
33
-
34
- if (hyperedgeTargets.size === 0) return;
35
-
36
- const related = concept.relatedConcepts ?? [];
37
- for (let i = 0; i < related.length; i++) {
38
- const rc = related[i];
39
- if (!BINARY_PARTITIVE_TYPES.has(rc.type)) continue;
40
- const ref = rc.ref;
41
- const key = `${ref?.source ?? ''}|${ref?.id ?? ''}`;
42
- if (hyperedgeTargets.has(key)) {
43
- this.addIssue(result,
44
- `${path}relatedConcepts[${i}]`,
45
- `concept has both a binary '${rc.type}' edge and a ` +
46
- `PartitiveHyperedge part targeting (${ref?.source ?? '?'}, ` +
47
- `${ref?.id ?? '?'}); pick one encoding to avoid divergence`);
48
- }
49
- }
50
- }
51
- }
@@ -1,75 +0,0 @@
1
- import { ValidationRule } from './validation-rule.js';
2
-
3
- // Validates semantic invariants of PartitiveHyperedge entries that
4
- // the model constructor does NOT enforce. The constructor already
5
- // rejects empty comprehensive, empty parts, self-loops, invalid
6
- // marker values, and duplicate markers. This rule provides a
7
- // defensive walk at validation time plus a warning for the
8
- // implicit-enumeration case (absent in the source YAML, filled by
9
- // the model's DEFAULT_ENUMERATION constant).
10
- export class PartitiveHyperedgeShapeRule extends ValidationRule {
11
- constructor() { super('partitive-hyperedge-shape'); }
12
-
13
- validate(concept, path, result) {
14
- const edges = concept.partitiveHyperedges ?? [];
15
- for (let i = 0; i < edges.length; i++) {
16
- const he = edges[i];
17
- const base = `${path}partitiveHyperedges[${i}]`;
18
-
19
- this._checkComprehensive(he, i, base, result);
20
- this._checkParts(he, i, base, result);
21
- this._checkMarkers(he, i, base, result);
22
- this._checkSelfLoop(he, i, base, result);
23
- }
24
- }
25
-
26
- _checkComprehensive(he, idx, base, result) {
27
- const ref = he.comprehensive;
28
- if (!ref || (!ref.source && !ref.id)) {
29
- this.addIssue(result, `${base}.comprehensive`,
30
- `partitive_hyperedge ${idx + 1} has empty comprehensive`);
31
- }
32
- }
33
-
34
- _checkParts(he, idx, base, result) {
35
- const parts = he.parts ?? [];
36
- if (parts.length === 0) {
37
- this.addIssue(result, `${base}.parts`,
38
- `partitive_hyperedge ${idx + 1} has no parts`);
39
- return;
40
- }
41
- for (let j = 0; j < parts.length; j++) {
42
- const p = parts[j];
43
- if (!p || (!p.source && !p.id)) {
44
- this.addIssue(result, `${base}.parts[${j}]`,
45
- `partitive_hyperedge ${idx + 1} part ${j + 1} has empty ref`);
46
- }
47
- }
48
- }
49
-
50
- _checkMarkers(he, idx, base, result) {
51
- const markers = he.markers ?? [];
52
- const seen = new Set();
53
- for (const m of markers) {
54
- if (seen.has(m)) {
55
- this.addIssue(result, `${base}.markers`,
56
- `partitive_hyperedge ${idx + 1} has duplicate marker '${m}'`);
57
- }
58
- seen.add(m);
59
- }
60
- }
61
-
62
- _checkSelfLoop(he, idx, base, result) {
63
- const c = he.comprehensive;
64
- if (!c) return;
65
- const cKey = `${c.source ?? ''}:${c.id ?? ''}`;
66
- for (let j = 0; j < (he.parts ?? []).length; j++) {
67
- const p = he.parts[j];
68
- const pKey = `${p?.source ?? ''}:${p?.id ?? ''}`;
69
- if (pKey === cKey) {
70
- this.addIssue(result, `${base}.parts[${j}]`,
71
- `partitive_hyperedge ${idx + 1} part ${j + 1} cannot be the comprehensive`);
72
- }
73
- }
74
- }
75
- }