sts 0.6.6 → 0.6.8

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.roadmap/00-overview.md +85 -0
  3. data/TODO.roadmap/01-mathml-namespace-deduplication.md +62 -0
  4. data/TODO.roadmap/02-lutaml-model-element-builder-dispatch.md +115 -0
  5. data/TODO.roadmap/03-issue-40-child-bearing-roots.md +57 -0
  6. data/TODO.roadmap/04-issue-40-recursive-roots.md +105 -0
  7. data/TODO.roadmap/05-content-mathml-unification.md +75 -0
  8. data/TODO.roadmap/07-tbxisotml-misclassification-audit.md +82 -0
  9. data/TODO.roadmap/08-register-versioning.md +95 -0
  10. data/TODO.roadmap/09-test-coverage-gaps.md +78 -0
  11. data/TODO.roadmap/10-iso-sts-common-attributes-module.md +115 -0
  12. data/TODO.roadmap/11-docs-and-readme-refresh.md +52 -0
  13. data/TODO.sts-refactor/00-overview.md +3 -3
  14. data/TODO.sts-refactor/03-namespace-coupling.md +13 -5
  15. data/lib/sts/iso_sts/custom_meta.rb +37 -0
  16. data/lib/sts/iso_sts/custom_meta_group.rb +16 -0
  17. data/lib/sts/iso_sts/def_list.rb +1 -1
  18. data/lib/sts/iso_sts/iso_meta.rb +1 -1
  19. data/lib/sts/iso_sts/license.rb +35 -0
  20. data/lib/sts/iso_sts/license_p.rb +96 -0
  21. data/lib/sts/iso_sts/meta_name.rb +14 -0
  22. data/lib/sts/iso_sts/meta_value.rb +41 -0
  23. data/lib/sts/iso_sts/nat_meta.rb +1 -1
  24. data/lib/sts/iso_sts/permissions.rb +1 -1
  25. data/lib/sts/iso_sts/reg_meta.rb +1 -1
  26. data/lib/sts/iso_sts/standard.rb +1 -1
  27. data/lib/sts/iso_sts/term_head.rb +74 -0
  28. data/lib/sts/iso_sts.rb +7 -0
  29. data/lib/sts/namespaces.rb +0 -5
  30. data/lib/sts/niso_sts/mml_content.rb +1 -1
  31. data/lib/sts/niso_sts/standard.rb +1 -1
  32. data/lib/sts/version.rb +1 -1
  33. metadata +20 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 201840881c86c036233c5bb1305bf176bbe6bf4ca0f43a22e370105cac0ed54f
4
- data.tar.gz: 5845dd5fb849611a4a11bf173af68b3c226cf5c807d09bb721cf09cecdc8544b
3
+ metadata.gz: cd02c307a6e1d5f238e8e05db78958bf73d1bc05c98c170ad89005fb205c7e88
4
+ data.tar.gz: 0bc64d38cf7f5ffd0151db213b9cc99dd25ca979b9eacabf0fb5b7f78b4b19af
5
5
  SHA512:
6
- metadata.gz: 20d4010381ed9daf225ecf13afc922b5cf338ec62318b24ee22554163885c815aa5db22257be600262fe8be9f7f8668a0569805e5a201db35b98a84e1fbfa982
7
- data.tar.gz: c4078992f02ab4a9e6640e750d2f54b29136b187a58b9e42cc21e9a2cc76b7049c6e25f9b823e0827f2851468e3f0dcc618271526880ae3121be3f2fc5512d69
6
+ metadata.gz: 421983c008de22adbe0ca5b7e101590d1a025b76582acb6c750d4d7ebad8582abd9bfa12386ed119d11e9ac2e5ccfe6d6f1216f991ce457fc18e98dd5a86ed91
7
+ data.tar.gz: d0d7b00dbd0c8c53268c561fdb965228e753f90c7cc52c0c9c3fa842ba22d4a105232a2b0b0bec778387e86e1c76e6fd3f95e6bad3f1593e5c566e126f3d8b42
@@ -0,0 +1,85 @@
1
+ # sts-ruby + lutaml-model Roadmap
2
+
3
+ **Date**: 2026-07-27
4
+ **Replaces**: `TODO.sts-refactor/00-overview.md` (now mostly historical — that
5
+ series landed through PRs #31, #40 work, #42, #44–#50). This roadmap covers
6
+ all remaining work across both repos.
7
+
8
+ ## Status at start (2026-07-27)
9
+
10
+ - **sts gem**: 0.6.6 on RubyGems
11
+ - **Issue #40**: 11 IsoSts→NisoSts refs remaining (5 recursive roots +
12
+ 4 child-bearing roots + 2 already-enumerated ArticleTitle / ElementCitation
13
+ co-roots)
14
+ - **Open PRs/Issues**: #40 (umbrella)
15
+ - **Anti-pattern enforcement**: 9 per-file checks in
16
+ `spec/anti_patterns_spec.rb`; covers all global rules
17
+ - **Tests**: 2563 examples, 0 failures
18
+
19
+ ## Tracks
20
+
21
+ Work is grouped into four MECE tracks. Each track is independent unless
22
+ noted.
23
+
24
+ | Track | Subject | Items |
25
+ |---|---|---|
26
+ | A | lutaml-model upstream analysis (filed as robustness suggestions) | 02 |
27
+ | B | Issue #40 namespace coupling completion | 01 (done), 03 (done), 04 |
28
+ | C | Architectural improvements (DRY, versioning, audit) | 05, 07, 08 |
29
+ | D | Quality, specs, and coverage | 09, 10, 11 |
30
+
31
+ ## Priority order
32
+
33
+ The priority is "highest leverage per unit of risk" first:
34
+
35
+ 1. **B.01** (MathmlNamespace deduplication): DONE 2026-07-27. sts-ruby had
36
+ two Ruby classes for one XML namespace; deleted one, use `Mml::Namespace`
37
+ directly. Originally misdiagnosed as a lutaml-model bug — see
38
+ `01-mathml-namespace-deduplication.md` for the lesson.
39
+ 2. **B.03** (child-bearing roots): DONE 2026-07-27. Modelled License,
40
+ TermHead, CustomMetaGroup closures. Closed 5 of 11 issue #40 refs.
41
+ 3. **A.02** (lutaml-model ElementBuilder dispatch): filed as
42
+ `BUGREPORT.element-builder-dispatch-on-value-class.md` in lutaml-model.
43
+ Pure documentation; zero risk. **Reframed as a robustness suggestion,
44
+ not a bug** — sts-ruby's responsibility to use correct types was the
45
+ actual fix (PR #47 deleted duplicate Fn classes).
46
+ 4. **C.07** (TbxIsoTml misclassification audit): documents a deferred audit
47
+ (Xref, TableWrap, etc.) — pattern is already proven by Fn/FnGroup and
48
+ Math removals.
49
+ 5. **C.05** (Content MathML unification): 148 parallel classes in
50
+ `lib/sts/niso_sts/mml_content/` — same DRY argument as Presentation
51
+ MathML unification (commit 9e977a5).
52
+ 6. **B.04** (recursive roots): the heavy lift. Each root pulls a ~78-element
53
+ mutually-recursive core. Requires per-element planning. Most architecturally
54
+ significant.
55
+ 7. **C.08** (register versioning): lets ISOSTS v1.1, NISO STS 1.0, NISO STS 1.2
56
+ coexist without class-name clashes.
57
+ 8. **D.09, D.10, D.11** (quality, coverage, docs): ongoing.
58
+
59
+ ## Architectural principles (enforced on all new work)
60
+
61
+ - **MECE**: each model class lives in exactly one namespace; no parallel
62
+ hierarchies across namespaces.
63
+ - **Model-driven**: every XML element reference is a typed
64
+ `attribute :foo, ::Sts::<Namespace>::Foo`. No runtime string lookup.
65
+ - **Open/Closed**: adding a new STS host namespace = adding a new Foo class
66
+ there, not editing existing namespaces.
67
+ - **DRY**: one class per (namespace, XML element) pair. Shared attribute
68
+ patterns within a namespace use Ruby modules (not cross-namespace base
69
+ classes — those are forbidden by the 2026-05-07 ADR).
70
+ - **No anti-patterns**: no `send`, no `instance_variable_set/get`, no
71
+ `respond_to?` type-checks, no `require_relative` in lib/ (autoload only),
72
+ no `require` with internal library paths. Enforced by
73
+ `spec/anti_patterns_spec.rb`.
74
+ - **Autoload convention**: top-level `lib/sts.rb` autoloads namespaces;
75
+ `lib/sts/<namespace>.rb` autoloads classes. Alphabetical within category
76
+ groups.
77
+
78
+ ## Memory cross-references
79
+
80
+ - [[architecture-namespace-independence]] — IsoSts/NisoSts never share base types
81
+ - [[duplicate-fn-class-attribute-mismatch]] — the trap A.02 prevents
82
+ - [[isosts-uses-mathml2-not-mathml3]] — V2/V3 host binding pattern
83
+ - [[mml-gem-integration-failure]] — MathmlNamespace duplication context
84
+ - [[release-workflow-two-phase]] — release flow
85
+ - [[ci-rubocop-stricter-than-local]] — substring-check convention
@@ -0,0 +1,62 @@
1
+ # 01: MathmlNamespace deduplication (DONE)
2
+
3
+ **Status**: complete (2026-07-27).
4
+ **Originally tracked as**: `01-lutaml-model-namespace-scope-uri-matching.md`
5
+ (misdiagnosed as a lutaml-model bug) and `06-mathml-namespace-deduplication.md`
6
+ (blocked on the misdiagnosed upstream fix). Merged and completed here.
7
+
8
+ ## Original misdiagnosis
9
+
10
+ When sts-ruby unified Presentation MathML on the mml gem (commit 9e977a5),
11
+ the `mml:` prefix was dropped on serialization unless both
12
+ `Sts::Namespaces::MathmlNamespace` AND `Mml::Namespace` were listed in
13
+ `IsoSts::Standard#namespace_scope` / `NisoSts::Standard#namespace_scope`.
14
+
15
+ The initial diagnosis (filed as `BUGREPORT.namespace-scope-uri-matching.md`
16
+ in lutaml-model) blamed `namespace_scope` for matching by class identity
17
+ instead of URI. The proposed fix was to change lutaml-model's matching
18
+ semantics.
19
+
20
+ ## Actual root cause
21
+
22
+ **sts-ruby had two Ruby classes for one XML namespace.** A namespace IS
23
+ its URI (per Namespaces in XML 1.0). `Sts::Namespaces::MathmlNamespace`
24
+ and `Mml::Namespace` both declared:
25
+
26
+ ```ruby
27
+ uri "http://www.w3.org/1998/Math/MathML"
28
+ prefix_default "mml"
29
+ ```
30
+
31
+ They were the same namespace. The duplication was the smell; the
32
+ "workaround" of listing both was actually papering over the duplication.
33
+
34
+ The BUGREPORT was retracted. The fix is purely sts-ruby's responsibility.
35
+
36
+ ## Fix shipped (2026-07-27)
37
+
38
+ - Deleted `Sts::Namespaces::MathmlNamespace` from `lib/sts/namespaces.rb`.
39
+ - Replaced `::Sts::Namespaces::MathmlNamespace` with `::Mml::Namespace` in:
40
+ - `lib/sts/iso_sts/standard.rb`
41
+ - `lib/sts/niso_sts/standard.rb`
42
+ - `lib/sts/niso_sts/mml_content.rb` (the Content MathML module — still
43
+ scheduled for unification per TODO.roadmap/05)
44
+ - Added 4 regression specs asserting `MathmlNamespace` is undefined and
45
+ `Mml::Namespace` is used directly.
46
+
47
+ ## Verification
48
+
49
+ - 5153 examples, 0 failures.
50
+ - TBX fixture round-trip preserves `<mml:math>` prefix.
51
+ - NISO STS fixture round-trip preserves `<mml:math>` prefix.
52
+ - `grep -rn "MathmlNamespace" lib/` returns nothing.
53
+
54
+ ## Lesson
55
+
56
+ When you see a "framework limitation" that requires you to declare
57
+ something twice, the more likely explanation is that you have two
58
+ definitions of the same thing. Namespaces in particular are URI-identified;
59
+ two classes for one URI is a DRY violation, not a framework bug.
60
+
61
+ Memory updated: `mml-gem-integration-failure` no longer describes the
62
+ `namespace_scope` duplication as a "known smell" — it's resolved.
@@ -0,0 +1,115 @@
1
+ # A.02: lutaml-model `ElementBuilder` dispatch on `value.class`
2
+
3
+ **Status**: BUGREPORT drafted; analysis complete. Deferred per downstream
4
+ maintainer feedback (2026-07-25) — see "Why deferred" below.
5
+ **Repo**: `lutaml/lutaml-model`
6
+ **Estimated effort**: small (lutaml-model: ~10 lines + spec)
7
+
8
+ ## Problem
9
+
10
+ `Lutaml::Xml::TransformationSupport::ElementBuilder#create_nested_model_element`
11
+ (lib/lutaml/xml/transformation/element_builder.rb:131) reuses the rule's
12
+ cached `child_transformation` for serialization regardless of the value's
13
+ actual class. When a caller assigns a `Lutaml::Model::Serializable` whose
14
+ class differs from the declared `attribute_type`, the wrong transformation
15
+ is applied silently:
16
+
17
+ - The declared type's `compiled_rules` are iterated
18
+ - `extract_rule_value` (rule_applier.rb:252) calls
19
+ `model_instance.public_send(rule.attribute_name)` on the value
20
+ - If the value's class doesn't have that attribute name (e.g., declared type
21
+ uses `:paragraph`, value's class uses `:p`), it raises `NoMethodError`
22
+
23
+ The cache itself is correct: `TransformationRegistry#transformation_key`
24
+ (lib/lutaml/model/transformation_registry.rb:218) includes `object_id`, so
25
+ keys are unique per class. The bug is in the dispatch logic, not the cache.
26
+
27
+ ## Real-world trigger
28
+
29
+ The original sts-ruby report (PR #47): `Sts::TbxIsoTml::Fn` (Ruby attr
30
+ `:p`) was nested inside `Sts::NisoSts::Back` (whose `:fn_group` is typed
31
+ `NisoSts::FnGroup`, whose `:fn` is typed `NisoSts::Fn` with attr
32
+ `:paragraph`). lutaml-model applied the cached
33
+ `NisoSts::Fn`-transformation to the `TbxIsoTml::Fn` value, calling
34
+ `fn.public_send(:paragraph)` and raising.
35
+
36
+ ## Why deferred
37
+
38
+ Downstream metanorma-iso maintainer feedback: "introduces a new contract
39
+ that may break other downstream consumers who've been quietly relying on
40
+ the current lenient behavior. Worth doing eventually, but urgency drops
41
+ once #1 lands — without the duplicate classes there's no easy way to
42
+ trigger the trap."
43
+
44
+ After PR #47 (delete TbxIsoTml::Fn/FnGroup), the trap cannot fire through
45
+ sts-ruby's own classes. The fix is still correct and worth doing, just not
46
+ urgent.
47
+
48
+ ## Proposed lutaml-model fix
49
+
50
+ In `create_nested_model_element`, dispatch on `value.class` whenever value
51
+ is a `Serializable` that differs from the declared `attribute_type`:
52
+
53
+ ```ruby
54
+ dispatch_on_value_class = union || is_polymorphic ||
55
+ (value.is_a?(Lutaml::Model::Serialize) &&
56
+ value.class != rule.attribute_type)
57
+
58
+ child_transformation = if dispatch_on_value_class
59
+ value.class.transformation_for(:xml, register)
60
+ else
61
+ rule.child_transformation ||
62
+ rule.attribute_type.transformation_for(:xml, register)
63
+ end
64
+ ```
65
+
66
+ This subsumes the existing `is_polymorphic_subtype` branch (subtypes already
67
+ have `value.class != rule.attribute_type`). The fast path
68
+ (`value.class == rule.attribute_type`) preserves the cached
69
+ `rule.child_transformation` lookup, so no performance regression for the
70
+ common case.
71
+
72
+ ## Files
73
+
74
+ - `BUGREPORT.element-builder-dispatch-on-value-class.md` in
75
+ `~/src/lutaml/lutaml-model/`
76
+ - lutaml-model spec: `spec/lutaml/model/cross_class_dispatch_spec.rb` — three
77
+ examples minimum (unrelated Serializable dispatch, round-trip preserved,
78
+ declared-type fast path unchanged)
79
+
80
+ ## Verification
81
+
82
+ - Reproduction case (currently raises in 0.8.18):
83
+
84
+ ```ruby
85
+ class ParentClass < Lutaml::Model::Serializable; end
86
+ class OtherSerializable < Lutaml::Model::Serializable
87
+ attribute :name, :string
88
+ xml do
89
+ element "other"
90
+ map_element "name", to: :name
91
+ end
92
+ end
93
+ class Holder < Lutaml::Model::Serializable
94
+ attribute :child, ParentClass
95
+ xml do
96
+ map_element "child", to: :child
97
+ end
98
+ end
99
+
100
+ h = Holder.new
101
+ h.child = OtherSerializable.new(name: "x")
102
+ h.to_xml # currently: NoMethodError on ParentClass's :name
103
+ # after fix: serialises using OtherSerializable's mapping
104
+ ```
105
+
106
+ ## How to apply
107
+
108
+ 1. File BUGREPORT in `~/src/lutaml/lutaml-model/`.
109
+ 2. Pair with the existing polymorphic specs (`spec/lutaml/model/polymorphic_spec.rb`)
110
+ to ensure no regression on subtype dispatch.
111
+ 3. Open upstream PR with the fix sketch above.
112
+
113
+ Revisit when: (a) a second cross-class case emerges anywhere in the
114
+ metanorma fleet, or (b) the next lutaml-model major release (good time to
115
+ tighten contracts).
@@ -0,0 +1,57 @@
1
+ # B.03: Issue #40 — model child-bearing IsoSts roots
2
+
3
+ **Status**: in flight (License, TermHead, CustomMetaGroup all completable in one PR).
4
+ **Blocks**: nothing.
5
+ **Closes**: 5 of 11 IsoSts→NisoSts refs (issue #40).
6
+
7
+ ## Remaining child-bearing refs
8
+
9
+ | Class | Referenced from | ISOSTS schema complexity |
10
+ |---|---|---|
11
+ | `IsoSts::License` | `IsoSts::Permissions#license` | simple (3 attrs + `<license-p>` children) |
12
+ | `IsoSts::TermHead` | `IsoSts::DefList#term_head` | simple (mixed content) |
13
+ | `IsoSts::CustomMetaGroup` | `IsoSts::{IsoMeta,RegMeta,NatMeta}#custom_meta_group` (×3) | simple (`<custom-meta>` children) |
14
+
15
+ ## Plan
16
+
17
+ 1. **IsoSts::License** — model from ISOSTS.xsd (`<license>` element):
18
+ - 8 attrs: license_type, specific_use, xml_lang, xlink_* (6)
19
+ - children: `license_p` (unbounded) — needs `IsoSts::LicenseP` too
20
+ - `license_p` itself is mixed-content with the full block-element set;
21
+ for first pass, model `license_p` with content + common inline children
22
+ (similar to IsoSts::Attrib)
23
+ 2. **IsoSts::LicenseP** — needed as License child
24
+ 3. **IsoSts::TermHead** — model from ISOSTS.xsd (`<term-head>`):
25
+ - mixed content
26
+ - inline-element children (similar to IsoSts::Attrib pattern)
27
+ 4. **IsoSts::CustomMetaGroup** — model from ISOSTS.xsd:
28
+ - No attributes
29
+ - Single child type: `<custom-meta>` (unbounded)
30
+ 5. **IsoSts::CustomMeta** — needed as CustomMetaGroup child
31
+ - 9 attrs: id, specific_use, xml_lang, xlink_*
32
+ - children: `<meta-name>`, `<meta-value>`
33
+ 6. **IsoSts::MetaName** — `xs:string` element
34
+ 7. **IsoSts::MetaValue** — mixed content
35
+
36
+ ## Specs (per established pattern)
37
+
38
+ - Attribute-set assertions for each new class
39
+ - Child-class type assertions
40
+ - Round-trip test for at least one realistic XML fragment
41
+ - No-NisoSts guard on each new file
42
+ - Repoint type assertions (e.g., `IsoSts::Permissions#license is IsoSts::License`)
43
+
44
+ ## Verification
45
+
46
+ - `grep -rn "NisoSts::" lib/sts/iso_sts/` count drops from 11 → 6
47
+ - Full test suite green
48
+ - Rubocop clean (anti-patterns spec enforces all rules)
49
+
50
+ ## How to apply
51
+
52
+ - One PR (e.g., `feat/isosts-license-termhead-custommetagroup`) covering all
53
+ 7 new classes + the 5 repoints.
54
+ - Same pattern as PR #48 (DispQuote/BoxedText) and PR #50 (Attrib): model
55
+ from ISOSTS.xsd, omit not-yet-modelled IsoSts children, track omissions
56
+ in the spec comment.
57
+ - Update `TODO.sts-refactor/03-namespace-coupling.md` count.
@@ -0,0 +1,105 @@
1
+ # B.04: Issue #40 — break the recursive roots
2
+
3
+ **Status**: planning. Heavy lift; needs per-element design.
4
+ **Blocks**: closing issue #40.
5
+ **Closes**: 6 of 11 IsoSts→NisoSts refs (5 recursive roots + ArticleTitle).
6
+
7
+ ## Remaining recursive refs
8
+
9
+ | Class | Referenced from | Approx. closure size |
10
+ |---|---|---|
11
+ | `IsoSts::ElementCitation` | `IsoSts::Ref#element_citation` | ~78 elements |
12
+ | `IsoSts::PersonGroup` | `IsoSts::MixedCitation#person_group` | ~50 elements |
13
+ | `IsoSts::Collab` | `IsoSts::MixedCitation#collab` | ~40 elements |
14
+ | `IsoSts::Source` | `IsoSts::MixedCitation#source` | ~25 elements |
15
+ | `IsoSts::ArticleTitle` | `IsoSts::MixedCitation#article_title` | ~25 elements |
16
+ | `IsoSts::TermDisplay` | `IsoSts::TermSec#term_display` | ~30 elements |
17
+
18
+ Each root reaches the same ~78-element mutually-recursive core
19
+ (`sec` → `p` → `disp-quote` → `p`, etc.). The recursion resolves once
20
+ every element in the cycle has an IsoSts class.
21
+
22
+ ## Architecture decision needed
23
+
24
+ Two strategies, mutually exclusive:
25
+
26
+ ### Strategy A: bottom-up leaf modelling
27
+
28
+ Model every leaf ISOSTS element (no children) first, then bottom-up
29
+ through the dependency graph. Eventually every recursive cycle has all
30
+ its members as IsoSts classes.
31
+
32
+ - **Pro**: each PR is small and verifiable
33
+ - **Pro**: every modelled leaf reduces the closure of the recursive roots
34
+ - **Con**: many PRs (could be 50+); requires tracking the dependency DAG
35
+ in a checklist
36
+
37
+ ### Strategy B: top-down with `raw` mapping
38
+
39
+ Use lutaml-model's `raw` mapping type to capture the recursive core as
40
+ opaque XML strings, then incrementally model the surface elements.
41
+
42
+ - **Pro**: closes the recursive refs in fewer PRs
43
+ - **Con**: `raw` mapping doesn't expose the recursive content as Ruby
44
+ objects — callers can't introspect or modify the captured XML
45
+ - **Con**: violates the model-driven principle for the deferred parts
46
+
47
+ ### Recommendation: Strategy A
48
+
49
+ Track B.03 (child-bearing roots) closes 5 refs with no recursion.
50
+ Strategy A is the right continuation: model the next layer of leaves
51
+ (elements with no children, or with only already-modelled IsoSts children)
52
+ until the recursive roots' closures collapse.
53
+
54
+ ## Tracking the DAG
55
+
56
+ A `TODO.roadmap/04-dag.md` (this file's sibling) should list every
57
+ not-yet-modelled ISOSTS element with:
58
+
59
+ - Element name
60
+ - Whether it has children (and if so, which)
61
+ - Whether all its children are already IsoSts classes
62
+ - Priority (high if it's referenced by a still-NisoSts ref)
63
+
64
+ This is mechanical work but tedious. A scripted generator from ISOSTS.xsd
65
+ could populate it; the existing `lib/sts/niso_sts/mml_content/` directory
66
+ suggests the project has tolerated generated-model code before.
67
+
68
+ ## Per-root breakdown
69
+
70
+ ### `ElementCitation` (~78 elements)
71
+
72
+ The biggest. The ISOSTS schema for `<element-citation>` is enormous (37+
73
+ child elements, many recursive). Schema-correct modelling requires:
74
+ - `IsoSts::ElementCitation` itself
75
+ - Many `<name>`, `<date>`, `<page>` style leaves — most already exist as
76
+ NisoSts classes; need IsoSts equivalents per namespace independence
77
+ - `<person-group>` (also a recursive root — see below)
78
+
79
+ Likely 10+ PRs to close this root alone.
80
+
81
+ ### `PersonGroup`, `Collab`, `Source`, `ArticleTitle` (~25-50 elements each)
82
+
83
+ All referenced from `<mixed-citation>`. Each pulls in citation-relevant
84
+ inline content. PersonGroup is itself a minor recursive root (contains
85
+ `<name>` which contains its own structure).
86
+
87
+ ### `TermDisplay` (~30 elements)
88
+
89
+ Referenced from `<term-sec>`. Pulls in TBX-flavored terminology elements
90
+ plus the regular block-element core.
91
+
92
+ ## Verification
93
+
94
+ - After each leaf is modelled: `bundle exec rspec` green
95
+ - After each root is closed: `grep -rn "NisoSts::<Root>"` returns nothing
96
+ - After all 6 roots closed: issue #40 down to ~0 refs (only ArticleTitle
97
+ co-root count check)
98
+
99
+ ## How to apply
100
+
101
+ - Do B.03 first (closes 5 refs cheaply)
102
+ - Then start B.04 with the smallest root (Source or ArticleTitle)
103
+ - Track DAG progress in `TODO.roadmap/04-dag.md` (sibling file)
104
+ - Each leaf modelled = one PR (same pattern as PRs #48, #50)
105
+ - Cross-reference each PR in `TODO.sts-refactor/03-namespace-coupling.md`
@@ -0,0 +1,75 @@
1
+ # C.05: Content MathML unification on the mml gem
2
+
3
+ **Status**: planning. Medium-large effort (148 classes to migrate).
4
+ **Depends on**: nothing (independent of A.01, A.02).
5
+ **Memory**: `mml-gem-integration-failure` (Presentation MathML is already
6
+ unified; Content MathML is not).
7
+
8
+ ## Problem
9
+
10
+ `lib/sts/niso_sts/mml_content/` ships 148 Content MathML classes (Apply,
11
+ Bind, Ci, Cn, Plus, Eq, Lambda, etc.) as a parallel in-gem implementation.
12
+ The mml gem has its own Content MathML set under `Mml::V2::*` and
13
+ `Mml::V3::*` (same pattern as the Presentation MathML it already provides,
14
+ which we unified in commit 9e977a5).
15
+
16
+ The duplication means:
17
+ - Two implementations of every Content MathML element
18
+ - Attribute sets can drift between them (no schema sync)
19
+ - sts-ruby users get sts-specific behavior, not mml-gem-correct behavior
20
+ - Bug fixes have to land in both repos
21
+
22
+ ## Plan
23
+
24
+ Same pattern as commit 9e977a5 (which deleted `TbxIsoTml::{Math, Mrow, Mi,
25
+ …}` and replaced with `Mml::V2::Math` / `Mml::V3::Math`):
26
+
27
+ 1. **Inventory** — `ls lib/sts/niso_sts/mml_content/` to enumerate the 148
28
+ classes (already autoloaded in `lib/sts/niso_sts/mml_content.rb`).
29
+ 2. **Verify mml gem coverage** — for each sts class, confirm the mml gem
30
+ has an equivalent (under `Mml::V2::*` and/or `Mml::V3::*`).
31
+ 3. **Repoint** — every `Sts::NisoSts::MmlContent::*` reference becomes
32
+ `Mml::V2::*` (for ISOSTS-hosted documents) or `Mml::V3::*` (for NISO STS).
33
+ Follow the host-binding pattern established in PR #45.
34
+ 4. **Delete** — once all references are repointed, delete
35
+ `lib/sts/niso_sts/mml_content/` entirely (and its autoload block).
36
+ 5. **Spec** — round-trip tests for documents with Content MathML must
37
+ continue to pass.
38
+
39
+ ## Risks
40
+
41
+ - **Coverage gap**: if the mml gem lacks a class for some sts Content
42
+ element, the migration is blocked. Mitigation: contribute the missing
43
+ class to the mml gem first.
44
+ - **Attribute divergence**: sts Content classes may have attributes the
45
+ mml gem lacks (or vice versa). Schema authority is `mathml3.xsd` (NISO
46
+ STS host) or `ncbi-mathml2/content/*.xsd` (ISOSTS host).
47
+ - **MathML version**: same V2/V3 split as Presentation MathML. sts Content
48
+ classes are version-agnostic; the mml gem's are not. Migration must
49
+ preserve the host-binding pattern.
50
+
51
+ ## Files affected
52
+
53
+ - `lib/sts/niso_sts/mml_content.rb` (the autoload block; will be deleted)
54
+ - `lib/sts/niso_sts/mml_content/*.rb` (~30 files, 148 classes total via
55
+ grouping files like `operators.rb`, `relations.rb`, `containers.rb`)
56
+ - Any `NisoSts::*` class that references `MmlContent::*` (audit with
57
+ `grep -rn "MmlContent::" lib/`)
58
+ - `lib/sts/namespaces.rb` — `MathmlNamespace` (relevant if A.01 hasn't
59
+ landed; the cleanup in C.06 may run before this)
60
+
61
+ ## How to apply
62
+
63
+ - Read [[isosts-uses-mathml2-not-mathml3]] for the host-binding pattern
64
+ - Read [[mml-gem-integration-failure]] for why the analogous Presentation
65
+ MathML unification took two attempts
66
+ - Open as a series of PRs (one per mml_content subdirectory) rather than
67
+ one giant PR; each PR closes one grouping file
68
+ - Final PR deletes the directory and the autoload block
69
+
70
+ ## Verification
71
+
72
+ - `bundle exec rspec spec/round_trip/reference_docs_spec.rb` — all
73
+ reference docs with Content MathML still round-trip
74
+ - `grep -rn "MmlContent::" lib/` returns nothing after the final PR
75
+ - Rubocop clean
@@ -0,0 +1,82 @@
1
+ # C.07: TbxIsoTml misclassification audit
2
+
3
+ **Status**: deferred. Same pattern as the prior Fn/FnGroup (PR #47) and
4
+ Math (commit 9e977a5) removals — proven approach.
5
+ **Memory**: `architecture-namespace-independence` (TbxIsoTml is a shared
6
+ namespace, but only for elements that are *actually* TBX-ISO-TML).
7
+
8
+ ## Problem
9
+
10
+ `Sts::TbxIsoTml::*` is the namespace for elements defined by ISO 30042
11
+ (TBX-ISO-TML). The standard defines terminology structures: `<termEntry>`,
12
+ `<tig>`, `<term>`, `<descrip>`, `<descripGrp>`, `<note>`, `<definition>`,
13
+ `<source>`, `<langSet>`, `<subjectField>`, `<partOfSpeech>`,
14
+ `<entailedTerm>`, etc.
15
+
16
+ The current `lib/sts/tbx_iso_tml/` also contains elements that are **not**
17
+ defined by TBX-ISO-TML — they're JATS / NISO STS elements borrowed into TBX
18
+ contexts. These are misclassifications: they should live in the host
19
+ namespace (NisoSts or IsoSts), not TbxIsoTml.
20
+
21
+ ## Suspects (likely misclassifications)
22
+
23
+ Each was identified by cross-referencing the TBX-ISO-TML (ISO 30042)
24
+ element list. Anything NOT in TBX-ISO-TML is a misfit.
25
+
26
+ | Class | Actual element source | Should move to |
27
+ |---|---|---|
28
+ | `TbxIsoTml::Xref` | JATS `<xref>` | (already shared via TbxIsoTml per Bold/Italic precedent — but the precedent itself may be wrong) |
29
+ | `TbxIsoTml::TableWrap` | JATS `<table-wrap>` | (same) |
30
+ | `TbxIsoTml::Table`, `Thead`, `Tbody`, `Tfoot`, `Tr`, `Th`, `Td`, `Col`, `Colgroup` | XHTML table model | IsoSts already has equivalents; NisoSts has equivalents |
31
+ | `TbxIsoTml::Caption` | JATS `<caption>` | (same) |
32
+ | `TbxIsoTml::TableWrapFoot` | JATS `<table-wrap-foot>` | (same) |
33
+ | `TbxIsoTml::TableBreak` | JATS extension | (same) |
34
+ | `TbxIsoTml::ExternalGraphic` | JATS `<external-graphic>` | (same) |
35
+ | `TbxIsoTml::ExternalSource` | JATS extension | (same) |
36
+ | `TbxIsoTml::Example` | JATS `<example>` (or NISO STS `<non-normative-example>`) | IsoSts has `NonNormativeExample` |
37
+ | `TbxIsoTml::Fn` ❌ | already removed (PR #47) | — |
38
+ | `TbxIsoTml::FnGroup` ❌ | already removed (PR #47) | — |
39
+ | `TbxIsoTml::Math`, `Mrow`, `Mi`, etc. ❌ | already removed (9e977a5) | — |
40
+ | `TbxIsoTml::Sup`, `TbxIsoTml::Bold`, `TbxIsoTml::Italic` | JATS emphasis group | IsoSts and NisoSts each have equivalents |
41
+
42
+ ## Per-element decision matrix
43
+
44
+ For each suspect:
45
+
46
+ 1. Confirm the element is NOT in TBX-ISO-TML (ISO 30042 spec lookup).
47
+ 2. Identify all call sites (`grep -rn "TbxIsoTml::<Class>" lib/`).
48
+ 3. Decide:
49
+ - **Delete and repoint** — if the host-equivalent class already exists
50
+ and has compatible API (like Fn → IsoSts::Fn + NisoSts::Fn).
51
+ - **Promote** — if no host-equivalent exists, create one in each host
52
+ namespace (parallel class per the ADR 2026-05-07).
53
+ - **Keep** — if the element genuinely belongs in shared TBX (rare; only
54
+ for true TBX-ISO-TML elements like `TermEntry`, `Term`, `Tig`).
55
+
56
+ ## Why this matters
57
+
58
+ Each misclassified class is a potential version-mismatch footgun (same
59
+ trap as the deleted `TbxIsoTml::Fn`). They also blur the architectural
60
+ boundary between TBX (shared, frozen) and STS host namespaces
61
+ (independent, evolving).
62
+
63
+ ## How to apply
64
+
65
+ - Same pattern as PR #47 (delete TbxIsoTml::Fn/FnGroup)
66
+ - One PR per element family (e.g., one PR for the XHTML table model, one
67
+ for emphasis, etc.) — keeps review tractable
68
+ - Each PR must update all call sites and add specs asserting the
69
+ TbxIsoTml class stays deleted (mirror of `spec/fn_namespace_spec.rb`)
70
+
71
+ ## Verification
72
+
73
+ - After each PR: `grep -rn "TbxIsoTml::<Class>" lib/` returns nothing
74
+ - TBX fixture round-trips still pass
75
+ - `bundle exec rake` green
76
+
77
+ ## Open question
78
+
79
+ The shared-namespace role of TbxIsoTml (for elements used in both IsoSts
80
+ and NisoSts hosts without their own version of the element) needs explicit
81
+ documentation. Today the boundary is implicit. Consider an ADR documenting
82
+ which elements are TBX-ISO-TML (shared) vs which are host-specific.