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.
- checksums.yaml +4 -4
- data/TODO.roadmap/00-overview.md +85 -0
- data/TODO.roadmap/01-mathml-namespace-deduplication.md +62 -0
- data/TODO.roadmap/02-lutaml-model-element-builder-dispatch.md +115 -0
- data/TODO.roadmap/03-issue-40-child-bearing-roots.md +57 -0
- data/TODO.roadmap/04-issue-40-recursive-roots.md +105 -0
- data/TODO.roadmap/05-content-mathml-unification.md +75 -0
- data/TODO.roadmap/07-tbxisotml-misclassification-audit.md +82 -0
- data/TODO.roadmap/08-register-versioning.md +95 -0
- data/TODO.roadmap/09-test-coverage-gaps.md +78 -0
- data/TODO.roadmap/10-iso-sts-common-attributes-module.md +115 -0
- data/TODO.roadmap/11-docs-and-readme-refresh.md +52 -0
- data/TODO.sts-refactor/00-overview.md +3 -3
- data/TODO.sts-refactor/03-namespace-coupling.md +13 -5
- data/lib/sts/iso_sts/custom_meta.rb +37 -0
- data/lib/sts/iso_sts/custom_meta_group.rb +16 -0
- data/lib/sts/iso_sts/def_list.rb +1 -1
- data/lib/sts/iso_sts/iso_meta.rb +1 -1
- data/lib/sts/iso_sts/license.rb +35 -0
- data/lib/sts/iso_sts/license_p.rb +96 -0
- data/lib/sts/iso_sts/meta_name.rb +14 -0
- data/lib/sts/iso_sts/meta_value.rb +41 -0
- data/lib/sts/iso_sts/nat_meta.rb +1 -1
- data/lib/sts/iso_sts/permissions.rb +1 -1
- data/lib/sts/iso_sts/reg_meta.rb +1 -1
- data/lib/sts/iso_sts/standard.rb +1 -1
- data/lib/sts/iso_sts/term_head.rb +74 -0
- data/lib/sts/iso_sts.rb +7 -0
- data/lib/sts/namespaces.rb +0 -5
- data/lib/sts/niso_sts/mml_content.rb +1 -1
- data/lib/sts/niso_sts/standard.rb +1 -1
- data/lib/sts/version.rb +1 -1
- metadata +20 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd02c307a6e1d5f238e8e05db78958bf73d1bc05c98c170ad89005fb205c7e88
|
|
4
|
+
data.tar.gz: 0bc64d38cf7f5ffd0151db213b9cc99dd25ca979b9eacabf0fb5b7f78b4b19af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|