sts 0.6.6 → 0.6.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 201840881c86c036233c5bb1305bf176bbe6bf4ca0f43a22e370105cac0ed54f
4
- data.tar.gz: 5845dd5fb849611a4a11bf173af68b3c226cf5c807d09bb721cf09cecdc8544b
3
+ metadata.gz: 53e7ba614487ea4d658f8292c403c6811255a85affd65ef9f98976ccc32aa426
4
+ data.tar.gz: a750e10d49eddc0664746e85114c7db000638b0a3e3addc4b352d179bb21684b
5
5
  SHA512:
6
- metadata.gz: 20d4010381ed9daf225ecf13afc922b5cf338ec62318b24ee22554163885c815aa5db22257be600262fe8be9f7f8668a0569805e5a201db35b98a84e1fbfa982
7
- data.tar.gz: c4078992f02ab4a9e6640e750d2f54b29136b187a58b9e42cc21e9a2cc76b7049c6e25f9b823e0827f2851468e3f0dcc618271526880ae3121be3f2fc5512d69
6
+ metadata.gz: a03cfc08c64cec197ee4a7a88a9ec41f46b3e94a80ea916092444be3419ec8c5aff06af3888094937be86cd7bbd2ed97b1663a688c16ba46650c0c61eed3cd60
7
+ data.tar.gz: 9ff6274690f18e27d2528497ffa5f4d0b5f3304e2636d276af5503b17318d6c86a090f3eeb583c0e889d26104878d8df9baed1b0d81ea227ed8b8be44e30573c
@@ -0,0 +1,80 @@
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 fixes (file BUGREPORTs, propose patches) | 01, 02 |
27
+ | B | Issue #40 namespace coupling completion | 03, 04 |
28
+ | C | Architectural improvements (DRY, versioning, audit) | 05, 06, 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.03** (child-bearing roots): model `IsoSts::License`, `IsoSts::TermHead`,
36
+ `IsoSts::CustomMetaGroup` — closes 5 of 11 issue #40 refs in one PR. Low
37
+ risk, schema-correct, no recursion.
38
+ 2. **A.01, A.02** (lutaml-model BUGREPORTs): pure documentation, zero risk.
39
+ Unblocks C.06 (MathmlNamespace deduplication).
40
+ 3. **C.07** (TbxIsoTml misclassification audit): documents a deferred audit
41
+ (Xref, TableWrap, etc.) — pattern is already proven by Fn/FnGroup and
42
+ Math removals.
43
+ 4. **C.05** (Content MathML unification): 148 parallel classes in
44
+ `lib/sts/niso_sts/mml_content/` — same DRY argument as Presentation
45
+ MathML unification (commit 9e977a5).
46
+ 5. **B.04** (recursive roots): the heavy lift. Each root pulls a ~78-element
47
+ mutually-recursive core. Requires per-element planning. Most architecturally
48
+ significant.
49
+ 6. **C.08** (register versioning): lets ISOSTS v1.1, NISO STS 1.0, NISO STS 1.2
50
+ coexist without class-name clashes.
51
+ 7. **C.06** (MathmlNamespace deduplication): blocked on A.01.
52
+ 8. **D.09, D.10, D.11** (quality, coverage, docs): ongoing.
53
+
54
+ ## Architectural principles (enforced on all new work)
55
+
56
+ - **MECE**: each model class lives in exactly one namespace; no parallel
57
+ hierarchies across namespaces.
58
+ - **Model-driven**: every XML element reference is a typed
59
+ `attribute :foo, ::Sts::<Namespace>::Foo`. No runtime string lookup.
60
+ - **Open/Closed**: adding a new STS host namespace = adding a new Foo class
61
+ there, not editing existing namespaces.
62
+ - **DRY**: one class per (namespace, XML element) pair. Shared attribute
63
+ patterns within a namespace use Ruby modules (not cross-namespace base
64
+ classes — those are forbidden by the 2026-05-07 ADR).
65
+ - **No anti-patterns**: no `send`, no `instance_variable_set/get`, no
66
+ `respond_to?` type-checks, no `require_relative` in lib/ (autoload only),
67
+ no `require` with internal library paths. Enforced by
68
+ `spec/anti_patterns_spec.rb`.
69
+ - **Autoload convention**: top-level `lib/sts.rb` autoloads namespaces;
70
+ `lib/sts/<namespace>.rb` autoloads classes. Alphabetical within category
71
+ groups.
72
+
73
+ ## Memory cross-references
74
+
75
+ - [[architecture-namespace-independence]] — IsoSts/NisoSts never share base types
76
+ - [[duplicate-fn-class-attribute-mismatch]] — the trap A.02 prevents
77
+ - [[isosts-uses-mathml2-not-mathml3]] — V2/V3 host binding pattern
78
+ - [[mml-gem-integration-failure]] — MathmlNamespace duplication context
79
+ - [[release-workflow-two-phase]] — release flow
80
+ - [[ci-rubocop-stricter-than-local]] — substring-check convention
@@ -0,0 +1,111 @@
1
+ # A.01: lutaml-model `namespace_scope` URI-based matching
2
+
3
+ **Status**: BUGREPORT drafted; sts-ruby fix blocked on upstream.
4
+ **Repo**: `lutaml/lutaml-model`
5
+ **Estimated effort**: small (lutaml-model: ~20 lines + spec; sts-ruby: ~5 lines once landed)
6
+
7
+ ## Problem
8
+
9
+ `namespace_scope` in `Lutaml::Model::Serializable.xml do … end` currently
10
+ matches namespace classes by **identity** (`==`), not by URI. Two classes
11
+ that declare the same URI/prefix are treated as different namespaces, so a
12
+ parent that lists one cannot satisfy a child that declares the other.
13
+
14
+ ## Real-world symptom in sts-ruby
15
+
16
+ `Sts::Namespaces::MathmlNamespace` (lib/sts/namespaces.rb) and
17
+ `Mml::Namespace` (the mml gem's top-level namespace class) both encode:
18
+
19
+ ```ruby
20
+ uri "http://www.w3.org/1998/Math/MathML"
21
+ prefix_default "mml"
22
+ ```
23
+
24
+ They are different classes (different `object_id`s). When `Mml::V3::Math` is
25
+ nested inside `Sts::NisoSts::Standard`, the parent's `namespace_scope` lists
26
+ `MathmlNamespace`. The child declares `Mml::Namespace`. Identity comparison
27
+ fails, the `mml:` prefix is dropped on serialization.
28
+
29
+ The current workaround — listing both classes in `namespace_scope`:
30
+
31
+ ```ruby
32
+ namespace_scope [
33
+ ::Sts::Namespaces::MathmlNamespace,
34
+ ::Mml::Namespace,
35
+ ::Sts::Namespaces::TbxNamespace,
36
+ ::Lutaml::Xml::W3c::XlinkNamespace,
37
+ ]
38
+ ```
39
+
40
+ — is a DRY violation. It documents the lutaml-model limitation rather than
41
+ fixing it. See `lib/sts/iso_sts/standard.rb:18-19` and
42
+ `lib/sts/niso_sts/standard.rb:20-21`.
43
+
44
+ ## Proposed lutaml-model fix
45
+
46
+ In `Lutaml::Xml::NamespaceScope` (or wherever the membership check lives),
47
+ compare by URI instead of (or in addition to) class identity. The public
48
+ API doesn't change; only the matching semantics do.
49
+
50
+ ```ruby
51
+ # pseudocode
52
+ def includes?(namespace_class)
53
+ namespaces.any? do |declared|
54
+ declared.uri == namespace_class.uri
55
+ end
56
+ end
57
+ ```
58
+
59
+ ## Files
60
+
61
+ - `BUGREPORT.namespace-scope-uri-matching.md` in
62
+ `~/src/lutaml/lutaml-model/` (this track files it)
63
+ - lutaml-model spec: `spec/lutaml/xml/namespace_scope_spec.rb` — assert that
64
+ two classes with the same URI are equivalent for `namespace_scope`
65
+ - sts-ruby: remove the duplicated `MathmlNamespace` entry from
66
+ `IsoSts::Standard` and `NisoSts::Standard` once the upstream fix lands
67
+
68
+ ## Verification
69
+
70
+ - Reproduction case (currently failing in 0.8.18):
71
+
72
+ ```ruby
73
+ class A < Lutaml::Model::Serializable
74
+ xml do
75
+ namespace SomeNs # uri "urn:test", prefix "t"
76
+ element "a"
77
+ end
78
+ end
79
+ class B < Lutaml::Model::Serializable
80
+ xml do
81
+ namespace OtherNs # also uri "urn:test", prefix "t" — different class
82
+ element "b"
83
+ end
84
+ end
85
+ class Parent < Lutaml::Model::Serializable
86
+ attribute :child, B
87
+ xml do
88
+ namespace_scope [SomeNs]
89
+ map_element "child", to: :child
90
+ end
91
+ end
92
+ # Currently: child's prefix is dropped on serialize.
93
+ # After fix: child's prefix is preserved (URIs match).
94
+ ```
95
+
96
+ ## Why not fix in sts-ruby today
97
+
98
+ Cannot: the duplication is the only way to make `Mml::V3::Math` and
99
+ `Mml::V2::Math` serialize with the `mml:` prefix inside STS Standard
100
+ elements. Removing `MathmlNamespace` without the upstream fix would
101
+ silently drop the prefix on every IsoSts/NisoSts document with MathML
102
+ content.
103
+
104
+ Track C.06 (MathmlNamespace deduplication) is the sts-ruby cleanup; it
105
+ blocks on this track.
106
+
107
+ ## How to apply
108
+
109
+ 1. File BUGREPORT in `~/src/lutaml/lutaml-model/BUGREPORT.namespace-scope-uri-matching.md`.
110
+ 2. Optionally open PR upstream with the fix sketch above.
111
+ 3. After upstream lands and sts-ruby bumps its lutaml-model dep, do C.06.
@@ -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,59 @@
1
+ # C.06: MathmlNamespace deduplication
2
+
3
+ **Status**: blocked on A.01 (lutaml-model namespace_scope URI matching).
4
+ **Estimated effort**: trivial (delete one class, two `namespace_scope`
5
+ entries).
6
+
7
+ ## Problem
8
+
9
+ `Sts::Namespaces::MathmlNamespace` (lib/sts/namespaces.rb:5-8) and
10
+ `Mml::Namespace` (mml gem's top-level namespace class) both encode:
11
+
12
+ ```ruby
13
+ uri "http://www.w3.org/1998/Math/MathML"
14
+ prefix_default "mml"
15
+ ```
16
+
17
+ Two classes, same URI/prefix. The current `namespace_scope` workaround on
18
+ `IsoSts::Standard` and `NisoSts::Standard` lists both:
19
+
20
+ ```ruby
21
+ namespace_scope [
22
+ ::Sts::Namespaces::MathmlNamespace,
23
+ ::Mml::Namespace,
24
+ ::Sts::Namespaces::TbxNamespace,
25
+ ::Lutaml::Xml::W3c::XlinkNamespace,
26
+ ]
27
+ ```
28
+
29
+ This is the documented smell noted in memory `mml-gem-integration-failure`
30
+ ("namespace_scope duplication is a known smell"). It exists because
31
+ lutaml-model's `namespace_scope` matches by class identity, not by URI.
32
+
33
+ ## Plan
34
+
35
+ Once A.01 lands upstream and sts-ruby has bumped its lutaml-model dep:
36
+
37
+ 1. Delete `Sts::Namespaces::MathmlNamespace` from `lib/sts/namespaces.rb`.
38
+ 2. Replace both `namespace_scope` entries with `::Mml::Namespace` only.
39
+ 3. Audit any other references to `MathmlNamespace` (likely only
40
+ `lib/sts/niso_sts/mml_content.rb:10` — but that whole module is
41
+ being deleted by C.05).
42
+
43
+ ## Verification
44
+
45
+ - `grep -rn "MathmlNamespace" lib/` returns nothing
46
+ - TBX round-trip fixtures still preserve `<mml:math>` prefix
47
+ - NISO STS fixtures still preserve `<mml:math>` prefix
48
+ - IsoSts fixtures (V2) still preserve `<mml:math>` prefix
49
+
50
+ ## How to apply
51
+
52
+ - Coordinate with A.01 — file the BUGREPORT first, then either propose the
53
+ upstream fix or wait for it
54
+ - When the upstream fix is in a released lutaml-model version, bump
55
+ sts.gemspec and ship this cleanup as a follow-up PR
56
+
57
+ See also: TODO.sts-refactor/03-namespace-coupling.md "MathML wrapper"
58
+ discussion (now historical — the wrapper itself was deleted in 9e977a5,
59
+ but the namespace duplication remains).