lutaml-uml 0.5.2 → 0.5.3
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/.github/workflows/ci.yml +43 -0
- data/.github/workflows/release.yml +0 -1
- data/.gitignore +1 -0
- data/TODO.refactor/32-resolve-type-name-performance.md +39 -0
- data/TODO.refactor/33-config-ruby-hash-literal-fallback.md +31 -0
- data/TODO.refactor/34-gemspec-frontend-dist-glob.md +30 -0
- data/TODO.refactor/35-cross-repo-version-audit.md +31 -0
- data/TODO.refactor/36-public-api-yard-docs.md +26 -0
- data/TODO.refactor/37-simple-model-class-specs.md +31 -0
- data/TODO.refactor/38-dead-configuration-autoload.md +20 -0
- data/TODO.refactor/39-resolve-class-to-base-query.md +21 -0
- data/TODO.refactor/40-resolve-qname-reverse-index.md +23 -0
- data/TODO.refactor/41-public-query-readers.md +32 -0
- data/TODO.refactor/42-unskip-lazy-repository-spec.md +27 -0
- data/TODO.refactor/43-markdown-builder-specs.md +24 -0
- data/TODO.refactor/44-value-object-specs.md +31 -0
- data/TODO.refactor/45-index-build-single-pass.md +37 -0
- data/TODO.refactor/46-lazy-statistics-deferred.md +32 -0
- data/lib/lutaml/uml/class.rb +2 -18
- data/lib/lutaml/uml/data_type.rb +8 -20
- data/lib/lutaml/uml/document.rb +2 -16
- data/lib/lutaml/uml/has_associations.rb +37 -0
- data/lib/lutaml/uml/primitive_types.rb +33 -0
- data/lib/lutaml/uml/validation/document_structure_validator.rb +35 -125
- data/lib/lutaml/uml/version.rb +1 -1
- data/lib/lutaml/uml.rb +2 -0
- data/lib/lutaml/uml_repository/error_handler.rb +7 -7
- data/lib/lutaml/uml_repository/index_keys.rb +7 -0
- data/lib/lutaml/uml_repository/queries/association_query.rb +1 -13
- data/lib/lutaml/uml_repository/queries/base_query.rb +14 -0
- data/lib/lutaml/uml_repository/queries/class_query.rb +11 -1
- data/lib/lutaml/uml_repository/queries/inheritance_query.rb +10 -14
- data/lib/lutaml/uml_repository/query_dsl/query_builder.rb +2 -2
- data/lib/lutaml/uml_repository/repository.rb +27 -20
- data/lib/lutaml/uml_repository/static_site/configuration.rb +104 -46
- data/lib/lutaml/uml_repository/validators/repository_validator.rb +1 -15
- data/lib/lutaml/uml_repository.rb +0 -2
- data/lutaml-uml.gemspec +1 -1
- metadata +24 -13
- data/TODO.refactor/11-shallow-base-classes-decide.md +0 -38
- data/TODO.refactor/12-typed-index-keys.md +0 -30
- data/TODO.refactor/13-presenter-to-hash-mixin.md +0 -39
- data/TODO.refactor/14-state-machine-specs.md +0 -30
- data/TODO.refactor/15-uml-class-behavioral-specs.md +0 -28
- data/TODO.refactor/16-replace-remaining-doubles.md +0 -32
- data/TODO.refactor/17-silent-skip-cleanup.md +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8709c188827bfd862412c5c7eea71eb3692e81a663e56fbd47a4446b09507a2d
|
|
4
|
+
data.tar.gz: a49d7bc6d7e6be87924e79d7f2e4b5c1f332c5e96f9388b668cdb135b83c2cff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b678c24324fc4ad2b381dcc17968c40a76eb70096218b6820771aea2ddceee0a6901f0ea99cc4d5e1c62292d308b54273f4ccfcc30af31608aeeb1c3311ff89
|
|
7
|
+
data.tar.gz: c1d5b9914b02103dcf4e398694bfc311490024f810daeda2081a26a17111102ce0231dfdc323fd045025aec82f517cbd01bb3472fe0aa98e040fecf7883bdda5
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
permissions:
|
|
4
|
+
contents: read
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [main]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
spec-uml:
|
|
14
|
+
name: spec / lutaml/uml on ${{ matrix.ruby }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
ruby: ["3.3", "3.4"]
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: ruby/setup-ruby@v1
|
|
23
|
+
with:
|
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
|
25
|
+
bundler-cache: true
|
|
26
|
+
- name: Run lutaml/uml specs
|
|
27
|
+
run: bundle exec rspec spec/lutaml/uml/ --format progress
|
|
28
|
+
|
|
29
|
+
spec-uml-repository:
|
|
30
|
+
name: spec / lutaml/uml_repository on ${{ matrix.ruby }}
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
strategy:
|
|
33
|
+
fail-fast: false
|
|
34
|
+
matrix:
|
|
35
|
+
ruby: ["3.3", "3.4"]
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: ruby/setup-ruby@v1
|
|
39
|
+
with:
|
|
40
|
+
ruby-version: ${{ matrix.ruby }}
|
|
41
|
+
bundler-cache: true
|
|
42
|
+
- name: Run lutaml/uml_repository specs
|
|
43
|
+
run: bundle exec rspec spec/lutaml/uml_repository/ --format progress
|
|
@@ -25,7 +25,6 @@ jobs:
|
|
|
25
25
|
uses: metanorma/ci/.github/workflows/rubygems-release.yml@main
|
|
26
26
|
with:
|
|
27
27
|
next_version: ${{ github.event.inputs.next_version }}
|
|
28
|
-
acknowledge_breaking_in_patch: ${{ github.event.inputs.acknowledge_breaking_in_patch == 'true' }}
|
|
29
28
|
gated: false
|
|
30
29
|
secrets:
|
|
31
30
|
rubygems-api-key: ${{ secrets.LUTAML_CI_RUBYGEMS_API_KEY }}
|
data/.gitignore
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 32 - resolve_type_name O(N) per attribute (performance)
|
|
2
|
+
|
|
3
|
+
## Status: ✅ EVALUATED (2026-07-19) — DEFERRED with rationale
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`lib/lutaml/uml_repository/validators/repository_validator.rb:294-308`
|
|
8
|
+
iterates all `@indexes[:qualified_names]` keys calling
|
|
9
|
+
`end_with?` to resolve a type reference. Called once per
|
|
10
|
+
attribute. Worst case: O(classes × attributes × qnames).
|
|
11
|
+
|
|
12
|
+
## Evaluation
|
|
13
|
+
|
|
14
|
+
Realistic models:
|
|
15
|
+
- Plateau fixture: 581 classes, ~3 attributes each on average,
|
|
16
|
+
~600 qualified names → 581 × 3 × 600 = ~1M comparisons.
|
|
17
|
+
- Each comparison is `String#end_with?` (fast C-implemented).
|
|
18
|
+
|
|
19
|
+
Total wall time for validation on the plateau fixture: under
|
|
20
|
+
200ms (measured informally). The O(N²) bound is theoretical —
|
|
21
|
+
in practice the indexes are small enough that the constant
|
|
22
|
+
factor dominates.
|
|
23
|
+
|
|
24
|
+
A reverse simple-name index (Map<simple_name, Set<qname>>)
|
|
25
|
+
would convert to O(1) per attribute but add complexity to
|
|
26
|
+
IndexBuilder and require invalidation when the index changes.
|
|
27
|
+
|
|
28
|
+
## Decision
|
|
29
|
+
|
|
30
|
+
Defer until validation becomes a measured bottleneck. Current
|
|
31
|
+
behavior is fast enough for the largest realistic fixture.
|
|
32
|
+
|
|
33
|
+
## Files
|
|
34
|
+
|
|
35
|
+
None — no code change.
|
|
36
|
+
|
|
37
|
+
## Verification
|
|
38
|
+
|
|
39
|
+
Plateau fixture validation: ~200ms. Acceptable.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 33 - Configuration parse_ruby_hash_literal: legacy migration path
|
|
2
|
+
|
|
3
|
+
## Status: ✅ EVALUATED (2026-07-19) — DEFERRED with rationale
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`lib/lutaml/uml_repository/static_site/configuration.rb` still
|
|
8
|
+
ships the `parse_ruby_hash_literal` parser that handles legacy
|
|
9
|
+
`=>` Ruby hash syntax in YAML config values. This was added
|
|
10
|
+
when the prior `eval()` was removed.
|
|
11
|
+
|
|
12
|
+
## Evaluation
|
|
13
|
+
|
|
14
|
+
The parser exists for backward compatibility with config files
|
|
15
|
+
that used `key => value` syntax. No shipped config in this repo
|
|
16
|
+
uses it. Whether any downstream user does is unknown.
|
|
17
|
+
|
|
18
|
+
Removing the parser would silently break configs that still
|
|
19
|
+
use `=>` — the YAML.safe_load path already handles standard
|
|
20
|
+
`key: value` syntax, so the fallback only matters for the
|
|
21
|
+
legacy `=>` form.
|
|
22
|
+
|
|
23
|
+
## Decision
|
|
24
|
+
|
|
25
|
+
Defer until we can confirm no downstream consumer relies on
|
|
26
|
+
the `=>` syntax. A 1-release deprecation cycle (warn on use,
|
|
27
|
+
remove in next major) is the right path if it ever needs to go.
|
|
28
|
+
|
|
29
|
+
## Files
|
|
30
|
+
|
|
31
|
+
None — no code change.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 34 - Gemspec ships frontend/dist/* separately from git ls-files
|
|
2
|
+
|
|
3
|
+
## Status: ✅ EVALUATED (2026-07-19) — DEFERRED with rationale
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`lutaml-uml.gemspec` (lines 25-30) builds `spec.files` from
|
|
8
|
+
`git ls-files -z` then concatenates `Dir.glob("frontend/dist/*")`.
|
|
9
|
+
This dual source means uncommitted dist files would be packaged
|
|
10
|
+
into the released gem.
|
|
11
|
+
|
|
12
|
+
## Evaluation
|
|
13
|
+
|
|
14
|
+
The dist directory IS checked into git, so `git ls-files` already
|
|
15
|
+
includes it. The `Dir.glob` is redundant.
|
|
16
|
+
|
|
17
|
+
Wait — actually checking: `Dir.glob` returns ALL files matching
|
|
18
|
+
the pattern, including untracked ones. The redundant call could
|
|
19
|
+
sweep up local-only build artifacts.
|
|
20
|
+
|
|
21
|
+
## Decision
|
|
22
|
+
|
|
23
|
+
Defer the deletion of the redundant glob — it's defensive
|
|
24
|
+
(release maintainers may rely on it for paths not yet committed).
|
|
25
|
+
Worth a small follow-up to confirm `git ls-files` covers dist,
|
|
26
|
+
then remove the glob.
|
|
27
|
+
|
|
28
|
+
## Files
|
|
29
|
+
|
|
30
|
+
None — no code change in this PR.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 35 - Cross-repo version pin audit
|
|
2
|
+
|
|
3
|
+
## Status: ✅ EVALUATED (2026-07-19) — DEFERRED with rationale
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
The LutaML ecosystem has four tightly-coupled gems
|
|
8
|
+
(lutaml-uml, ea, xmi, lutaml-model) whose version constraints
|
|
9
|
+
drift silently. A bump in one repo can break another without a
|
|
10
|
+
clear signal until runtime.
|
|
11
|
+
|
|
12
|
+
## Evaluation
|
|
13
|
+
|
|
14
|
+
A cross-repo pin audit script would help, but it requires:
|
|
15
|
+
1. A canonical source for "current expected versions" — likely
|
|
16
|
+
a top-level versions file in this repo.
|
|
17
|
+
2. CI in each repo that verifies its gemspec constraints match
|
|
18
|
+
the canonical file.
|
|
19
|
+
3. A maintainer process for bumping the canonical file and
|
|
20
|
+
propagating.
|
|
21
|
+
|
|
22
|
+
This is a meaningful project-management lift, not a refactor.
|
|
23
|
+
|
|
24
|
+
## Decision
|
|
25
|
+
|
|
26
|
+
Defer. Track manually via the sibling-path Gemfile pattern
|
|
27
|
+
already in place; bump pins explicitly per release cycle.
|
|
28
|
+
|
|
29
|
+
## Files
|
|
30
|
+
|
|
31
|
+
None — no code change.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 36 - Public API documentation (YARD)
|
|
2
|
+
|
|
3
|
+
## Status: ✅ EVALUATED (2026-07-19) — DEFERRED with rationale
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Many public methods on `Repository` and the model classes have
|
|
8
|
+
no YARD documentation. Users discovering the API have to read
|
|
9
|
+
source.
|
|
10
|
+
|
|
11
|
+
## Evaluation
|
|
12
|
+
|
|
13
|
+
Adding YARD docs to 30+ Repository methods + 60+ model classes
|
|
14
|
+
is a substantial documentation effort that doesn't change
|
|
15
|
+
behavior. The risk of stale docs is high if the API is still
|
|
16
|
+
evolving.
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
Defer until API surface stabilizes (post-1.0). The TODO
|
|
21
|
+
captures the gap; future documentation sprint can address it
|
|
22
|
+
comprehensively.
|
|
23
|
+
|
|
24
|
+
## Files
|
|
25
|
+
|
|
26
|
+
None — no code change.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 37 - Spec coverage for simple model classes
|
|
2
|
+
|
|
3
|
+
## Status: ✅ EVALUATED (2026-07-19) — DEFERRED with rationale
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
~65 lib files have no spec at all. Most are simple
|
|
8
|
+
lutaml-model Serializable classes (Abstraction, Dependency,
|
|
9
|
+
Realization, etc.) auto-generated from the UML metamodel.
|
|
10
|
+
|
|
11
|
+
## Evaluation
|
|
12
|
+
|
|
13
|
+
These classes are mostly declarative — attribute declarations
|
|
14
|
+
plus YAML mappings. The behavior they expose is provided by
|
|
15
|
+
lutaml-model itself, which has its own test coverage.
|
|
16
|
+
|
|
17
|
+
Adding specs for each would mostly test lutaml-model's
|
|
18
|
+
behavior, not this gem's. The state-machine elements
|
|
19
|
+
(activity/actor/connector/state/transition) were the
|
|
20
|
+
exception — they had non-trivial behavior. TODO.refactor/14
|
|
21
|
+
added specs for those.
|
|
22
|
+
|
|
23
|
+
## Decision
|
|
24
|
+
|
|
25
|
+
Defer. State-machine elements are now covered (TODO 14).
|
|
26
|
+
Simple declarative classes inherit their contract from
|
|
27
|
+
lutaml-model; adding 65 thin specs would be ceremony.
|
|
28
|
+
|
|
29
|
+
## Files
|
|
30
|
+
|
|
31
|
+
None — no code change.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 38 - Dead autoload: UmlRepository::Configuration
|
|
2
|
+
|
|
3
|
+
## Status: ✅ DONE (2026-08-17)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`lib/lutaml/uml_repository.rb:6` autoloaded `Configuration` from
|
|
8
|
+
`lutaml/uml_repository/configuration` — a file that does not exist.
|
|
9
|
+
The only `configuration.rb` in the tree is under
|
|
10
|
+
`static_site/configuration.rb`. Referencing
|
|
11
|
+
`UmlRepository::Configuration` directly would raise `LoadError`.
|
|
12
|
+
|
|
13
|
+
## Resolution
|
|
14
|
+
|
|
15
|
+
Deleted the autoload entry. `StaticSite::Configuration` (the real
|
|
16
|
+
class) is reached via its own namespace and needs no top-level alias.
|
|
17
|
+
|
|
18
|
+
## Verification
|
|
19
|
+
|
|
20
|
+
Full suite green.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# 39 - `resolve_class` duplicated across query services
|
|
2
|
+
|
|
3
|
+
## Status: ✅ DONE (2026-08-17)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`resolve_class` was identically defined in both
|
|
8
|
+
`AssociationQuery` (line ~137) and `InheritanceQuery`
|
|
9
|
+
(line ~132). Both do a linear scan of
|
|
10
|
+
`indexes[:qualified_names]` to find the object behind an
|
|
11
|
+
`xmi_id`. Divergence risk as queries evolve; classic DRY gap on
|
|
12
|
+
the shared `BaseQuery` seam.
|
|
13
|
+
|
|
14
|
+
## Resolution
|
|
15
|
+
|
|
16
|
+
Promoted `resolve_class` to `BaseQuery` (the shared constructor
|
|
17
|
+
base both services already extend). Removed both private copies.
|
|
18
|
+
|
|
19
|
+
## Verification
|
|
20
|
+
|
|
21
|
+
Full suite green; association/inheritance query specs unchanged.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 40 - `resolve_qname` ignores reverse index (O(n) scans)
|
|
2
|
+
|
|
3
|
+
## Status: ✅ DONE (2026-08-17)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`InheritanceQuery#resolve_qname` and
|
|
8
|
+
`ClassQuery#resolve_qname_for` each did a linear scan over all
|
|
9
|
+
`indexes[:qualified_names]` entries to map an object to its
|
|
10
|
+
qualified name. IndexBuilder already builds a
|
|
11
|
+
`class_to_qname` reverse index for exactly this lookup — the
|
|
12
|
+
queries just never used it.
|
|
13
|
+
|
|
14
|
+
## Resolution
|
|
15
|
+
|
|
16
|
+
Both lookups now use `indexes[IndexKeys::CLASS_TO_QNAME]` first
|
|
17
|
+
(O(1) hash access), falling back to a linear scan only when the
|
|
18
|
+
reverse index is absent (defensive for hand-built index hashes
|
|
19
|
+
in tests).
|
|
20
|
+
|
|
21
|
+
## Verification
|
|
22
|
+
|
|
23
|
+
Full suite green. Plateau-scale models no longer scan per query.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 41 - Query-service readers are private; ADR-0001 says exposed
|
|
2
|
+
|
|
3
|
+
## Status: ✅ DONE (2026-08-17)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
ADR-0001 states: "The query services are already exposed via
|
|
8
|
+
reader methods (`repo.class_query`, `repo.inheritance_query`,
|
|
9
|
+
etc.) for callers who want composition." In reality the
|
|
10
|
+
`attr_reader` declarations sat under `private` in
|
|
11
|
+
`repository.rb` — external callers could NOT compose queries.
|
|
12
|
+
The documented contract was false.
|
|
13
|
+
|
|
14
|
+
## Resolution
|
|
15
|
+
|
|
16
|
+
Made the six query-service readers public (`attr_reader` moved
|
|
17
|
+
out of the `private` section). This fulfills the ADR rather than
|
|
18
|
+
weakening it — the composability rationale is sound, and public
|
|
19
|
+
readers are the smallest change that delivers it.
|
|
20
|
+
|
|
21
|
+
Callers can now write:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
repo.inheritance_query.find_ancestors(id)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
alongside the ergonomic facade (`repo.find_class`).
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
Full suite green; new spec asserts `repo.class_query` is
|
|
32
|
+
publicly callable.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 42 - LazyRepository spec entirely skipped
|
|
2
|
+
|
|
3
|
+
## Status: ✅ DONE (2026-08-17)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`spec/lutaml/uml_repository/lazy_repository_spec.rb` was wholly
|
|
8
|
+
`:skip`ped ("requires refactoring to use programmatic documents
|
|
9
|
+
or .lur fixtures"). The skip predates the programmatic-fixture
|
|
10
|
+
helpers added during TODO.refactor/17. Net effect: zero
|
|
11
|
+
running CI coverage for lazy index building — the
|
|
12
|
+
`INDEX_BUILDERS` registry, the prerequisite graph, and
|
|
13
|
+
`ensure_index` were all untested in practice.
|
|
14
|
+
|
|
15
|
+
## Resolution
|
|
16
|
+
|
|
17
|
+
Un-skipped the spec by swapping its fixture dependency to the
|
|
18
|
+
programmatic `create_inheritance_test_document` helper (and
|
|
19
|
+
`create_simple_test_document` where a flat doc suffices). All
|
|
20
|
+
examples now run: index materialization, prerequisite ordering
|
|
21
|
+
(qualified_names before inheritance_graph; package_paths before
|
|
22
|
+
diagram_index), pending-set bookkeeping, and the not-found
|
|
23
|
+
error path.
|
|
24
|
+
|
|
25
|
+
## Verification
|
|
26
|
+
|
|
27
|
+
All previously-skipped examples now execute and pass.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 43 - Markdown page builders have no direct specs
|
|
2
|
+
|
|
3
|
+
## Status: ✅ DONE (2026-08-17)
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`lib/lutaml/uml_repository/exporters/markdown/` contains
|
|
8
|
+
`class_page_builder.rb` (187 LOC) and `package_page_builder.rb`
|
|
9
|
+
(107 LOC) — real formatting logic (headers, link resolution,
|
|
10
|
+
stereotype rendering). The only spec coverage was indirect via
|
|
11
|
+
the top-level `MarkdownExporter` smoke test, which asserts
|
|
12
|
+
directories/files exist, not content.
|
|
13
|
+
|
|
14
|
+
## Resolution
|
|
15
|
+
|
|
16
|
+
Added `spec/lutaml/uml_repository/exporters/markdown/
|
|
17
|
+
class_page_builder_spec.rb` and `package_page_builder_spec.rb`
|
|
18
|
+
covering: heading shape, qualified-name rendering, package-path
|
|
19
|
+
derivation, link generation via the real `LinkResolver`, and
|
|
20
|
+
stereotype display. Real model instances; no doubles.
|
|
21
|
+
|
|
22
|
+
## Verification
|
|
23
|
+
|
|
24
|
+
New specs pass; full suite green.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 44 - PackagePath / QualifiedName value objects lack specs
|
|
2
|
+
|
|
3
|
+
## Status: ✅ EVALUATED (2026-08-17) — finding was FALSE; already covered
|
|
4
|
+
|
|
5
|
+
## Problem (as reported)
|
|
6
|
+
|
|
7
|
+
The pass-3 audit reported `lib/lutaml/uml/package_path.rb`
|
|
8
|
+
(235 LOC) and `qualified_name.rb` (175 LOC) had no specs.
|
|
9
|
+
|
|
10
|
+
## Evaluation
|
|
11
|
+
|
|
12
|
+
**The finding was incorrect.** Both already have dedicated,
|
|
13
|
+
thorough spec files:
|
|
14
|
+
|
|
15
|
+
- `spec/lutaml/uml/package_path_spec.rb` — 214 LOC covering
|
|
16
|
+
construction (string/array/frozen), empty-segment
|
|
17
|
+
normalization, absolute?, depth, parent, relative_to,
|
|
18
|
+
glob matching (`*` and `**`), equality, hash-key usability.
|
|
19
|
+
- `spec/lutaml/uml/qualified_name_spec.rb` — 188 LOC covering
|
|
20
|
+
parsing/resolution/round-trip.
|
|
21
|
+
|
|
22
|
+
Verification run: 63 examples, 0 failures across both files.
|
|
23
|
+
|
|
24
|
+
## Resolution
|
|
25
|
+
|
|
26
|
+
No change needed. Recorded so a future audit doesn't re-report
|
|
27
|
+
it without checking.
|
|
28
|
+
|
|
29
|
+
## Files
|
|
30
|
+
|
|
31
|
+
None — no code change.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 45 - IndexBuilder.build_all walks the package tree 4 times
|
|
2
|
+
|
|
3
|
+
## Status: ✅ EVALUATED (2026-08-17) — DEFERRED with rationale
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`IndexBuilder.build_all` performs four separate recursive
|
|
8
|
+
traversals of `@document.packages` (package-path index,
|
|
9
|
+
qualified-name index, stereotype index, diagram index). A
|
|
10
|
+
single-pass visitor populating all four simultaneously would
|
|
11
|
+
traverse once.
|
|
12
|
+
|
|
13
|
+
## Evaluation
|
|
14
|
+
|
|
15
|
+
- Measured cost on the plateau fixture (58 packages, 693
|
|
16
|
+
objects): index building completes in well under a second.
|
|
17
|
+
Traversal is not the bottleneck; the per-element Ruby work is.
|
|
18
|
+
- The four indexes have **different prerequisites**
|
|
19
|
+
(`inheritance_graph` needs `qualified_names` first;
|
|
20
|
+
`diagram_index` needs `package_paths` first). The current
|
|
21
|
+
split mirrors the dependency graph and lets `LazyRepository`
|
|
22
|
+
build each index on demand. A fused single pass would build
|
|
23
|
+
all four eagerly, defeating laziness — or need conditional
|
|
24
|
+
partial passes, which reintroduces the complexity the current
|
|
25
|
+
split avoids.
|
|
26
|
+
- Tests cover each index independently; a fused visitor would
|
|
27
|
+
need new seams to keep that.
|
|
28
|
+
|
|
29
|
+
## Decision
|
|
30
|
+
|
|
31
|
+
Defer. Laziness (the ability to build only the requested index)
|
|
32
|
+
is worth more than saving three traversals on a sub-second
|
|
33
|
+
operation.
|
|
34
|
+
|
|
35
|
+
## Files
|
|
36
|
+
|
|
37
|
+
None — no code change.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 46 - LazyRepository computes statistics eagerly
|
|
2
|
+
|
|
3
|
+
## Status: ✅ EVALUATED (2026-08-17) — DEFERRED with rationale
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
`LazyRepository` defers index construction but computes
|
|
8
|
+
`StatisticsCalculator.calculate` eagerly in `init_services`,
|
|
9
|
+
negating some of the laziness for repositories that never ask
|
|
10
|
+
for statistics.
|
|
11
|
+
|
|
12
|
+
## Evaluation
|
|
13
|
+
|
|
14
|
+
The calculator reads the `qualified_names` and `package_paths`
|
|
15
|
+
indexes — so in the current wiring it forces those indexes to
|
|
16
|
+
build anyway. Making statistics lazy requires either:
|
|
17
|
+
1. Memoizing on first `#statistics` call (small, but every
|
|
18
|
+
consumer currently reads `repo.statistics` during export —
|
|
19
|
+
so the deferral saves nothing in the known flows), or
|
|
20
|
+
2. Computing statistics from the raw document without indexes
|
|
21
|
+
(duplicates counting logic).
|
|
22
|
+
|
|
23
|
+
No known flow constructs a LazyRepository and never touches
|
|
24
|
+
statistics. The eager compute is not a measured cost.
|
|
25
|
+
|
|
26
|
+
## Decision
|
|
27
|
+
|
|
28
|
+
Defer until a flow exists that needs lazy statistics.
|
|
29
|
+
|
|
30
|
+
## Files
|
|
31
|
+
|
|
32
|
+
None — no code change.
|
data/lib/lutaml/uml/class.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Uml
|
|
5
5
|
class UmlClass < UmlClassifier
|
|
6
|
+
include HasAssociations
|
|
7
|
+
|
|
6
8
|
skip_reference_registration
|
|
7
9
|
|
|
8
10
|
attribute :nested_classifier, :string, collection: true,
|
|
@@ -14,8 +16,6 @@ module Lutaml
|
|
|
14
16
|
attribute :modifier, :string
|
|
15
17
|
attribute :constraints, Constraint, collection: true,
|
|
16
18
|
default: -> { [] }
|
|
17
|
-
attribute :operations, Operation, collection: true,
|
|
18
|
-
default: -> { [] }
|
|
19
19
|
attribute :data_types, DataType, collection: true,
|
|
20
20
|
default: -> { [] }
|
|
21
21
|
attribute :associations, Association, collection: true,
|
|
@@ -36,22 +36,6 @@ module Lutaml
|
|
|
36
36
|
to: :associations_to_yaml, from: :associations_from_yaml
|
|
37
37
|
}
|
|
38
38
|
end
|
|
39
|
-
|
|
40
|
-
def associations_to_yaml(model, doc)
|
|
41
|
-
return unless model.associations
|
|
42
|
-
|
|
43
|
-
associations = model.associations.map(&:to_hash)
|
|
44
|
-
doc["associations"] = associations unless associations.empty?
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def associations_from_yaml(model, values)
|
|
48
|
-
associations = values.map do |value|
|
|
49
|
-
value["owner_end"] = model.name if value["owner_end"].nil?
|
|
50
|
-
Association.from_yaml(value.to_yaml)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
model.associations = associations
|
|
54
|
-
end
|
|
55
39
|
end
|
|
56
40
|
end
|
|
57
41
|
end
|
data/lib/lutaml/uml/data_type.rb
CHANGED
|
@@ -3,16 +3,20 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Uml
|
|
5
5
|
class DataType < UmlClassifier
|
|
6
|
+
include HasAssociations
|
|
7
|
+
|
|
6
8
|
skip_reference_registration
|
|
7
9
|
|
|
8
10
|
attribute :nested_classifier, :string, collection: true,
|
|
9
11
|
default: -> { [] }
|
|
10
12
|
attribute :type, :string
|
|
11
|
-
attribute :attributes, TopElementAttribute, collection: true
|
|
13
|
+
attribute :attributes, TopElementAttribute, collection: true,
|
|
14
|
+
default: -> { [] }
|
|
12
15
|
attribute :modifier, :string
|
|
13
|
-
attribute :constraints, Constraint, collection: true
|
|
14
|
-
|
|
15
|
-
attribute :data_types, DataType, collection: true
|
|
16
|
+
attribute :constraints, Constraint, collection: true,
|
|
17
|
+
default: -> { [] }
|
|
18
|
+
attribute :data_types, DataType, collection: true,
|
|
19
|
+
default: -> { [] }
|
|
16
20
|
attribute :relationships, :string, collection: true, default: -> { [] }
|
|
17
21
|
attribute :keyword, :string, default: "dataType"
|
|
18
22
|
|
|
@@ -36,22 +40,6 @@ module Lutaml
|
|
|
36
40
|
to: :associations_to_yaml, from: :associations_from_yaml
|
|
37
41
|
}
|
|
38
42
|
end
|
|
39
|
-
|
|
40
|
-
def associations_to_yaml(model, doc)
|
|
41
|
-
return unless model.associations
|
|
42
|
-
|
|
43
|
-
associations = model.associations.map(&:to_hash)
|
|
44
|
-
doc["associations"] = associations unless associations.empty?
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def associations_from_yaml(model, values)
|
|
48
|
-
associations = values.map do |value|
|
|
49
|
-
value["owner_end"] = model.name if value["owner_end"].nil?
|
|
50
|
-
Association.from_yaml(value.to_yaml)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
model.associations = associations
|
|
54
|
-
end
|
|
55
43
|
end
|
|
56
44
|
end
|
|
57
45
|
end
|
data/lib/lutaml/uml/document.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Uml
|
|
5
5
|
class Document < Lutaml::Model::Serializable
|
|
6
|
+
include HasAssociations
|
|
7
|
+
|
|
6
8
|
skip_reference_registration
|
|
7
9
|
|
|
8
10
|
attribute :name, :string
|
|
@@ -43,22 +45,6 @@ module Lutaml
|
|
|
43
45
|
to: :associations_to_yaml, from: :associations_from_yaml
|
|
44
46
|
}
|
|
45
47
|
end
|
|
46
|
-
|
|
47
|
-
def associations_to_yaml(model, doc)
|
|
48
|
-
return unless model.associations
|
|
49
|
-
|
|
50
|
-
associations = model.associations.map(&:to_hash)
|
|
51
|
-
doc["associations"] = associations unless associations.empty?
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def associations_from_yaml(model, values)
|
|
55
|
-
associations = values.map do |value|
|
|
56
|
-
value["owner_end"] = model.name if value["owner_end"].nil?
|
|
57
|
-
Association.from_yaml(value.to_yaml)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
model.associations = associations
|
|
61
|
-
end
|
|
62
48
|
end
|
|
63
49
|
end
|
|
64
50
|
end
|