Skip to content

feat: extract ea gem from lutaml-uml with TODO.next consolidation#1

Open
ronaldtse wants to merge 6 commits into
mainfrom
chore/consolidate-todos
Open

feat: extract ea gem from lutaml-uml with TODO.next consolidation#1
ronaldtse wants to merge 6 commits into
mainfrom
chore/consolidate-todos

Conversation

@ronaldtse

@ronaldtse ronaldtse commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Extracts the ea gem into a standalone Sparx Enterprise Architect parser (QEA SQLite + Sparx XMI) and consolidates all TODO planning into TODO.next/. The gem is usable standalone; lutaml-uml is an optional dependency for the EA→UML bridge.

Subsystems added

  • Ea::Qea — SQLite-based EA database parser. Models, infrastructure, services, immutable Database container with hash indexes, repositories, factory (EA→UML via TransformerRegistry), validation, verification.
  • Ea::Xmi — Sparx-only XMI parser (uses ::Xmi::Sparx::Root). Cannot parse MagicDraw or Papyrus XMI; .xmi registration uses content detection.
  • Ea::Diagram — Style resolution, layout, path building, element renderers, SVG extractor. StyleResolver is the single entry point; StyleParser holds only the BGR→hex color utility (MECE: orchestration vs parsing).
  • Ea::TransformationsFormatRegistry-based parser dispatch with BaseParser template method. QEA and XMI parsers both produce Lutaml::Uml::Document.
  • Ea::Transformers — QEA→XMI and UML→XMI emitters.
  • Ea::Cli — Thor-based CLI with JSON/YAML/table output formatters.

Architecture principles applied

  • autoload only — no require_relative in lib/; every autoload declared in the immediate parent namespace file
  • OCP — registries throughout (TransformerRegistry, ElementRendererRegistry, EmitterRegistry, FormatRegistry)
  • MECEStyleParser (BGR color util) vs StyleResolver (orchestration) vs Configuration (YAML defaults); no overlap
  • Composition over load-time registration — bridge uses Repository.from_document, no if defined? hooks
  • No doubles in specs — real model instances or Struct.new
  • No reflection bypasses — no send on private, no instance_variable_get/set, no respond_to?, no method_defined?

Dependency graph (zero load-time cross-requires)

ea (standalone runtime: lutaml-model, lutaml-path, sqlite3, rubyzip, xmi, nokogiri, liquid, thor)
  └── [optional, dev] lutaml-uml (lazy require inside Ea::Qea.require_uml!)

lutaml-uml is declared as add_development_dependency only. The UML bridge lazy-requires lutaml/uml inside the method body with a clear LoadError rescue, so gem install ea does not pull lutaml-uml or its dep tree.

TODO.next consolidated

All 20 plans in TODO.next/ carry explicit status headers (DONE / PARTIALLY DONE / DESIGN-CORRECT / BLOCKED). Items closed this branch:

  • 03 slim lutaml-uml — cross-requires eliminated
  • 11 style MECE — stripped dead StyleParser API (6 unused methods, 3 unused constants)
  • 15 exception narrowing — Configuration now rescues only specific exceptions; documented intentionally-broad rescues at trust boundaries
  • 16 repository indexes — TransformationEngine push+shift (O(1) append, amortized O(1) overflow); BaseRepository#find already O(1) via lazy PK index
  • 17 spec quality — stdlib method shadowing audit on BaseRepository (intentional ActiveRecord-like API)
  • 18 XMI architecture — Sparx-only parser is design-correct
  • 19 ea gemspec — fixed optional-vs-runtime dep declarations (this was the dependency-confusion bug: lutaml-uml was hard runtime, lutaml-model/lutaml-path were undeclared)

CI fixes added this branch:

  • 20 CI requires an unreleased lutaml-uml — BLOCKED, see below

Test suite

bundle exec rspec (local, with sibling checkouts): 1953 examples, 0 failures, 37 pending

⚠️ CI status: BLOCKED on lutaml-uml release

CI rake jobs fail at spec_helper.rb load:

LoadError: cannot load such file -- lutaml/uml_repository

No published version of lutaml-uml ships lib/lutaml/uml_repository.rb
(audited 0.2.0, 0.2.12, 0.3.0, 0.4.3, 1.0.0). The constant only exists
in the lutaml-uml source tree and has not been released. The same gap
applies to several Lutaml::Uml::* constants the ea bridge targets
(e.g. Lutaml::Uml::UmlClass, which 1.x renamed to Lutaml::Uml::Class).

Unblocking requires:

  1. A new lutaml-uml release containing uml_repository and the
    pre-1.x Lutaml::Uml::* constant names, OR
  2. Bridge specs refactored to skip when Lutaml::UmlRepository is
    not loadable (architectural follow-up, TODO.next/20).

This PR includes the prerequisite plumbing so CI installs cleanly
against published versions:

  • Gemfile conditional path-or-rubygems selector for lutaml-uml/canon
    (with EA_FORCE_RUBYGEMS=1 to reproduce CI state locally)
  • ea.gemspec dev dep pinned to ~> 0.2.0 to match the spec suite's API
  • Gemfile.lock regenerated in rubygems mode (CI installs published gems
    rather than failing on missing sibling paths)
  • .github/workflows/rake.yml and release.yml — removed cd frontend
    build steps inherited from a Cimas template (ea is pure-Ruby, no
    frontend/)
  • Gemfile.lock PLATFORMS now includes x86_64-linux,
    x86_64-linux-gnu, ruby (was arm64-darwin-23 only)

Notes

  • Branch created off main at d1c5886 (initial commit).
  • No tags pushed, no merge to main (per project rules).
  • Companion PR in lutaml-uml removes phantom liquid/lutaml-path deps and dead autoload modules.

Standalone Ruby gem for parsing Sparx Enterprise Architect data files
(QEA SQLite database and Sparx-flavored XMI). Namespace: `Ea::*`. The
gem is usable standalone; `lutaml-uml` is an optional dependency for
the EA-to-UML bridge.

Subsystems:
- `Ea::Qea` — SQLite-based EA database parser. Models, infrastructure,
  services, immutable Database container with hash indexes, repositories,
  factory (EA→UML via TransformerRegistry), validation, verification.
- `Ea::Xmi` — Sparx-only XMI parser (uses `::Xmi::Sparx::Root`).
  Cannot parse MagicDraw or Papyrus XMI; `.xmi` registration uses
  content detection to avoid claiming generic XMI.
- `Ea::Diagram` — Style resolution, layout, path building, element
  renderers, SVG extractor. StyleResolver is the single entry point;
  StyleParser holds only the BGR→hex color utility (MECE: orchestration
  vs parsing).
- `Ea::Transformations` — FormatRegistry-based parser dispatch with
  BaseParser template method. QEA and XMI parsers both produce
  `Lutaml::Uml::Document`.
- `Ea::Transformers` — QEA→XMI and UML→XMI emitters.
- `Ea::Cli` — Thor-based CLI with JSON/YAML/table output formatters.

Bridge to lutaml-uml uses composition (`Repository.from_document`) with
lazy requires inside method bodies — zero load-time cross-requires.

Config files in `config/`:
- `qea_schema.yml` — EA database table/column definitions
- `diagram_styles.yml` — default diagram styling
- `model_transformations.yml` — parser configurations

Specs: 1953 examples, 0 failures, 37 pending. No doubles, no
`send` on private methods, no `instance_variable_get/set`, no
`respond_to?`/`method_defined?`, no `require_relative` in lib/
(autoload only, declared in immediate parent namespace file).
- `examples/` — QEA standalone queries, QEA→Repository bridge, LUR
  workflows, and a real-QEA smoke test. Demonstrates both standalone
  usage (no lutaml-uml) and the optional UML bridge.
- `exe/ea` — CLI entry point.
- `docs/` — EA→UML type mapping reference and XMI↔QEA conversion
  capability notes.
- `.github/workflows/` — `rake` (test), `release`, `docs`, `link-check`.
  Replaces the generic `main.yml`.
Migrates all planning items to the numbered TODO.next/ format. Each
file carries a status header (DONE / PARTIALLY DONE / DESIGN-CORRECT)
and an explicit "what was applied" or "why deferred" section.

Items closed this session:
- 03 slim lutaml-uml — cross-requires eliminated (composition-based
  Repository.from_document API; zero load-time ea/lutaml-uml requires).
- 11 style MECE — stripped dead StyleParser API (6 unused methods,
  3 unused constants); StyleResolver owns EA-data-driven resolution,
  Configuration owns YAML-driven resolution.
- 15 exception narrowing — diagram Configuration now rescues only
  Psych::SyntaxError/Errno::ENOENT/EACCES/IOError; documented the
  intentionally-broad rescues at trust boundaries (DatabaseLoader
  callback isolation, per-record resilience in parsers).
- 16 repository indexes — TransformationEngine switched from
  unshift+pop to push+shift (O(1) append, amortized O(1) overflow);
  BaseRepository find already O(1) via lazy PK index; remaining
  O(n) scans audited and justified for EA repository sizes.
- 17 spec quality — stdlib method shadowing on BaseRepository audited
  as intentional ActiveRecord-like API design.
- 18 XMI architecture — Sparx-only Ea::Xmi::Parser is design-correct;
  each tool gets its own parser gem built on the xmi gem schemas.

Items already DONE in prior sessions: 00, 01, 02, 04 to 10, 12 to 14.
Two bugs:

1. `lutaml-uml` was declared as a hard runtime dependency, defeating
   the documented standalone design. The UML bridge already lazy-
   requires `lutaml/uml` inside `Ea::Qea.require_uml!` with a clear
   LoadError rescue, so users who only want QEA/XMI parsing shouldn't
   be forced to install `lutaml-uml` and its dep tree.

2. `lutaml-model` and `lutaml-path` were missing from the gemspec,
   relying on transitive pull via `lutaml-uml` -> lutaml-path /
   lutaml-model. Once `lutaml-uml` moved to dev-only, 11 specs broke
   with `LoadError: lutaml/path`. Both are load-time requires in
   `lib/ea/qea/models.rb`, `lib/ea/qea/models/base_model.rb`,
   `lib/ea/qea/services/configuration.rb`,
   `lib/ea/transformations/configuration.rb`, and `lib/ea/xmi/parser.rb`.

Fix:
- Remove runtime `lutaml-uml` dep
- Add dev `lutaml-uml` dep (for spec suite)
- Add runtime `lutaml-model` and `lutaml-path` deps

Suite: 1953 examples, 0 failures, 37 pending (unchanged).

Documented in TODO.next/19.
Two CI fixes for the ea gem, which has no frontend/ directory:

1. rake.yml and release.yml referenced 'cd frontend && npm install &&
   npm run build' inherited from a Cimas template that assumed a JS
   frontend. ea is pure-Ruby, so the steps failed with 'cd: no such
   file or directory: frontend'. Removed from both workflows.

2. Gemfile.lock only declared the arm64-darwin-23 platform (the
   developer's macOS). CI runs on x86_64-linux and ruby, causing
   'Could not find compatible gem' for native extensions (ffi,
   nokogiri, sqlite3). Added x86_64-linux, x86_64-linux-gnu, and
   ruby platforms via 'bundle lock --add-platform'.
Two coupled changes so CI fails loudly with a useful error instead of
the opaque 'path does not exist' message:

1. Gemfile: lutaml-uml and canon are sibling-repo path dependencies
   in local dev (../lutaml-uml, ../canon). When the sibling checkout
   doesn't exist (CI, gem install), fall back to the published
   rubygems versions. EA_FORCE_RUBYGEMS=1 forces rubygems mode locally
   to reproduce the CI-resolved dependency set.

2. ea.gemspec: pin the dev dep to '~> 0.2.0'. The bridge code and
   spec suite target the pre-1.0 API (Lutaml::Uml::UmlClass,
   Lutaml::UmlRepository). 1.x renamed these constants; bridge work
   is needed before unpinning.

3. Gemfile.lock regenerated against rubygems so CI installs published
   versions rather than failing on missing sibling paths.

CI will still fail at spec_helper load: 'cannot load such file --
lutaml/uml_repository'. No published lutaml-uml version ships that
file (audited 0.2.0, 0.2.12, 0.3.0, 0.4.3, 1.0.0). Unblock requires
a new lutaml-uml release containing uml_repository. Details and
follow-up architecture in TODO.next/20.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant