lutaml-xsd 1.1.13 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/CLAUDE.md +18 -1
  4. data/Gemfile +2 -1
  5. data/Gemfile.lock +327 -0
  6. data/Rakefile +78 -1
  7. data/TODO.VISION.md +123 -0
  8. data/TODO.cleanup.md +43 -0
  9. data/TODO.conformance.md +99 -0
  10. data/TODO.docs-spa.md +46 -0
  11. data/TODO.interop.md +52 -0
  12. data/TODO.performance.md +80 -0
  13. data/TODO.som.md +47 -0
  14. data/benchmark/engines.rb +77 -0
  15. data/config/validation.yml +4 -0
  16. data/docs/CONFORMANCE.adoc +43 -0
  17. data/docs/JSON_SCHEMA.adoc +42 -0
  18. data/docs/superpowers/plans/2026-10-20-moxml-leptris-migration.md +735 -0
  19. data/docs/superpowers/specs/2026-10-20-moxml-leptris-migration-design.md +169 -0
  20. data/docs/superpowers/specs/2026-10-21-rule-engine-wiring-design.md +84 -0
  21. data/lib/lutaml/xsd/conformance/runner.rb +169 -0
  22. data/lib/lutaml/xsd/json_schema_exporter.rb +240 -0
  23. data/lib/lutaml/xsd/package_builder.rb +2 -2
  24. data/lib/lutaml/xsd/spa/schema_serializer.rb +12 -7
  25. data/lib/lutaml/xsd/validation/engine_context.rb +190 -0
  26. data/lib/lutaml/xsd/validation/rule_engine.rb +36 -18
  27. data/lib/lutaml/xsd/validation/rule_registry.rb +34 -0
  28. data/lib/lutaml/xsd/validation/rules/attribute_validation_rule.rb +126 -17
  29. data/lib/lutaml/xsd/validation/rules/content_model_validation_rule.rb +304 -194
  30. data/lib/lutaml/xsd/validation/rules/element_structure_rule.rb +25 -15
  31. data/lib/lutaml/xsd/validation/rules/identity_constraints_rule.rb +134 -0
  32. data/lib/lutaml/xsd/validation/rules/occurrence_validation_rule.rb +76 -25
  33. data/lib/lutaml/xsd/validation/rules/type_validation_rule.rb +134 -17
  34. data/lib/lutaml/xsd/validation/schema_definition_resolver.rb +254 -0
  35. data/lib/lutaml/xsd/validation/validation_configuration.rb +1 -0
  36. data/lib/lutaml/xsd/validation/validation_job.rb +70 -3
  37. data/lib/lutaml/xsd/validation/validation_rule.rb +44 -5
  38. data/lib/lutaml/xsd/validation/validator.rb +2 -1
  39. data/lib/lutaml/xsd/validation/xml_element.rb +2 -2
  40. data/lib/lutaml/xsd/version.rb +1 -1
  41. data/lib/lutaml/xsd.rb +14 -0
  42. data/lutaml-xsd.gemspec +2 -2
  43. metadata +27 -8
@@ -0,0 +1,99 @@
1
+ # TODO.conformance — measured validation conformance (W3C XSD Test Suite)
2
+
3
+ Goal: conformance is a number, not a feeling. Vendor the W3C XML Schema
4
+ test-suite categories, build the harness, publish the pass-rate, grow it.
5
+
6
+ ## Baseline (2026-10-22, 20 cases / 42 checks)
7
+
8
+ Corpus: 20 case classes under `spec/conformance/cases/`, modelled on the
9
+ W3C XML Schema Test Suite category taxonomy — self-authored because the
10
+ canonical 2007 suite distribution is no longer served from w3.org (owner
11
+ can vendor a tarball later; provenance README goes with it).
12
+
13
+ | mode | pass-rate |
14
+ |---|---|
15
+ | engine (Moxml::XSD tier-1, moxml 0.5.150) | 35/42 (83.3%) |
16
+ | pipeline (ValidationJob + wired rule engine) | 40/42 (95.2%) |
17
+
18
+ The pipeline outperforms the engine by five checks: the root-declaration
19
+ check catches a wrong-namespace root that engine root-matching silently
20
+ skips; the identity-constraints rule catches duplicate keys the engine
21
+ lacks; xsi:nil handling rejects non-nillable nils; strict wildcards
22
+ (element and attribute) reject undeclared content. Zero false positives
23
+ retained.
24
+
25
+ Gap register (each failure is a tracked gap, not a test bug):
26
+
27
+ - `attributes-any` valid-known — tier-1 engine reports a qualified
28
+ global attribute as "not declared" under strict anyAttribute (engine
29
+ limitation; pipeline accepts it correctly)
30
+ - `identity-keykeyref` — dangling keyref not detected: implemented in
31
+ `IdentityConstraintsRule`, but activation needs lutaml-model to map the
32
+ `refer` attribute on `xs:keyref` (upstream fix in flight)
33
+ - `xsi-type` — a VALID instance derived via `xsi:type` is rejected by the
34
+ tier-1 engine (false positive, structural: engine scope is stricter than
35
+ derivation); the unknown-type half passes in pipeline mode
36
+ - engine-only: `namespaces-qualified` wrong-namespace root accepted
37
+ (EngineContext root-matching skips it; pipeline's root check catches it)
38
+
39
+ History: 2026-10-20 baseline 20/25 (80.0%) with pipeline == engine
40
+ (empty `RuleRegistry`, mismatched rule signatures); 2026-10-21 wiring
41
+ baseline 34/39 (87.2%); gap rules 37/39 (94.9%); anyAttribute lifted the
42
+ pipeline to 40/42 (95.2%).
43
+
44
+ ## Harness
45
+
46
+ - [x] Conformance corpus under `spec/conformance/cases/` (directory
47
+ convention: schema.xsd + valid-*.xml + invalid-*.xml)
48
+ - [x] `Lutaml::Xsd::Conformance::Runner`: discovery, per-mode
49
+ classification, pass-rate report (engine + production pipeline)
50
+ - [x] `rake conformance` → prints report and writes
51
+ `tmp/conformance_report.md`
52
+ - [x] Deliverable doc: `docs/CONFORMANCE.adoc` (methodology, baseline,
53
+ gap register pointer, how to add cases)
54
+ - [ ] Vendor a real W3C XSD TS subset — DEFERRED: upstream distribution
55
+ URLs are dead as of 2026-10-20; the owner supplies the corpus
56
+ tarball and a provenance/license README is added with it
57
+ - [ ] Harness run against the rule engine with registries explicitly
58
+ populated — DEFERRED: subsumed by the rule-registry wiring design
59
+ below
60
+
61
+ ## Rule-engine wiring (done 2026-10-21)
62
+
63
+ - [x] Design note: `docs/superpowers/specs/2026-10-21-rule-engine-wiring-design.md`
64
+ - [x] Element-scoped dispatch contract: `ValidationRule#schema_argument`
65
+ (defaults to the resolved declaration; occurrence rule returns the
66
+ content-model particle) and `runs_without_schema?`
67
+ - [x] `SchemaDefinitionResolver`: top-level + inline element index with
68
+ namespace matching and named-type resolution
69
+ - [x] `RuleRegistry.default(config)`: production rule set, feature-gated;
70
+ shared by `Validator` and the conformance harness
71
+ - [x] Rules resolve `type="prefix:Name"` references via the repository
72
+ - [x] Substitution groups: content-model and occurrence matching are
73
+ substitution-aware (substitution case now passes in pipeline mode)
74
+ - [x] Facet-collection handling fixed (empty arrays were treated as
75
+ present facets; `XmlAttribute` namespace ported to `.uri`)
76
+ - [x] Integration specs prove rule findings fire with the engine phase
77
+ disabled (ordering, missing content) and valid instances stay clean
78
+
79
+ ## Rule-engine depth (remaining)
80
+
81
+ - [x] Identity constraints: `key`, `keyref`, `unique` —
82
+ `IdentityConstraintsRule` (selector/field XPath evaluation,
83
+ duplicate detection, keyref resolution); dangling-keyref activation
84
+ waits on lutaml-model `refer` mapping
85
+ - [x] Wildcards: `any` namespace constraints (##any/##other/##local/
86
+ ##targetNamespace/lists) + `processContents` strict declaration
87
+ checking in the content-model walk; `anyAttribute` honors namespace
88
+ constraints and strict checking where the parse layer exposes the
89
+ declaration (attributeGroup, content-model restriction) —
90
+ complexType-level and extension-shape anyAttribute mappings are
91
+ missing upstream (lutaml-model), keeping those on pre-wildcard
92
+ behavior until mapped
93
+ - [x] `xsi:nil` (nillable + empty-content checks) and `xsi:type`
94
+ resolution (unknown-type rejection; content walk follows the
95
+ override); valid-derived acceptance needs engine-scope rework
96
+ - [ ] Particle checking: UPA detection — DEFERRED (engine covers the
97
+ tier-1 subset; UPA is schema-side checking)
98
+ - [ ] XSD 1.1 constructs behind a version flag — DEFERRED: 1.1 strategy
99
+ decision needed (see TODO.som.md)
data/TODO.docs-spa.md ADDED
@@ -0,0 +1,46 @@
1
+ # TODO.docs-spa — comprehension SOTA: interactive, live-validated documentation
2
+
3
+ Goal: leapfrog static commercial doc-generators (Oxygen/Altova) with an
4
+ interactive corpus browser where the documentation *executes*.
5
+
6
+ Status 2026-10-20: DEFERRED as a pillar. Reason: the SPA features below
7
+ need the frontend development loop (`rake build_frontend`, Vue dev server)
8
+ and a backend seam decision (CLI vs HTTP). The validation core these
9
+ features depend on is ready (EngineContext + `engine_validation` +
10
+ Conformance runner), so the UI work can start against a stable base. The
11
+ one prerequisite wiring — rule-registry gaps surfacing as findings — is
12
+ tracked in TODO.conformance.md.
13
+
14
+ ## Live validation playground (the killer feature)
15
+
16
+ - [ ] SPA page: paste/upload an XML instance → validate against the loaded
17
+ package (engine + rule stack via a CLI/HTTP seam) → findings with
18
+ line/column/path — DEFERRED: backend seam decision + frontend build
19
+ loop
20
+ - [ ] Click-through: finding → schema component page — DEFERRED (needs
21
+ the playground first)
22
+ - [ ] Instance tree view with per-node validity — DEFERRED (needs
23
+ element-path findings in the UI; engine already reports paths)
24
+
25
+ ## Navigation & comprehension
26
+
27
+ - [ ] Global search across types/elements/attributes/groups — DEFERRED:
28
+ the search index exists (`build_index`); ranking design + UI work
29
+ - [ ] Usage navigation matrix for attributes and groups — DEFERRED:
30
+ liquid methods pattern exists; extend `used_by` coverage first
31
+ - [ ] Type hierarchy graph in the SPA — DEFERRED (pairs with TODO.som.md)
32
+ - [ ] Dependency graph page — DEFERRED
33
+
34
+ ## Evolution
35
+
36
+ - [ ] Schema diff between package versions — DEFERRED: structural diff
37
+ design needed (component identity across versions)
38
+ - [ ] Coverage view wired to `coverage_analyzer` — DEFERRED
39
+
40
+ ## Polish
41
+
42
+ - [ ] SPA generation performance on large corpora — DEFERRED until the
43
+ post-recalibration corpus exists (TODO.performance.md)
44
+ - [ ] Accessibility pass on the SPA — DEFERRED (rides the next UI change)
45
+ - [ ] Nightly demo package regeneration in CI — DEFERRED: cimas-owned
46
+ workflow; needs an owner decision on schedule
data/TODO.interop.md ADDED
@@ -0,0 +1,52 @@
1
+ # TODO.interop — bridges: RNG, JSON Schema, LutaML cross-model
2
+
3
+ Goal: no schema format is an island; every bridge lands in the LutaML
4
+ model-driven documentation pipeline.
5
+
6
+ Status 2026-10-20: RNG bridge migrated to Moxml; JSON Schema exporter
7
+ delivered; remaining items deferred with reasons below.
8
+
9
+ ## RelaxNG bridge (rng gem, ~/src/lutaml/rng — its own repo + PR flow)
10
+
11
+ - [x] Migrate rng gem to Moxml (rng PR #35): five Nokogiri files ported
12
+ (rnc_to_rng_converter, rnc_parser, test_suite_parser,
13
+ schema_validator, external_ref_resolver); `nokogiri` removed from
14
+ the runtime dependencies, `moxml` added; dev Gemfile runs leptris
15
+ - [x] Round-trip coverage: the rng suite's RNC ↔ RNG round-trip and
16
+ spec-test suites pass on the Moxml converter (1056 examples; two
17
+ environmental timing guards were widened to pathological-regression
18
+ budgets — they also failed on pre-migration main on loaded machines)
19
+ - [x] rng 0.3.9 baseline recalibration (2026-10-21): converter expectations
20
+ re-baselined against the fixed include processor — complex types
21
+ 170, simple types 13, elements 203, groups 237, attribute_groups
22
+ 44; sections sequence: 7 choices / 1 group / 1 element (`sections`
23
+ present via bsi's override, trace in rng PR #34)
24
+ - [x] `rng ~> 0.3.9` pinned in lutaml-xsd (2026-10-21)
25
+
26
+ ## JSON Schema bridge
27
+
28
+ - [x] XSD → JSON Schema (draft 2020-12) exporter:
29
+ `Lutaml::Xsd::JsonSchemaExporter` — objects from complexTypes,
30
+ properties/required from sequences and attributes, arrays from
31
+ repeated particles, enum/pattern/length/range facets, builtin type
32
+ mapping, `const` for fixed values, nillable → null union
33
+ - [x] Loss table documented in docs/JSON_SCHEMA.adoc (XSD regex dialect,
34
+ identity constraints, substitution groups, derivation flattening,
35
+ wildcards, defaults, mixed content, XSD 1.1)
36
+ - [ ] SPA "export JSON Schema" action per package — DEFERRED: needs the
37
+ SPA work in TODO.docs-spa.md
38
+
39
+ ## LutaML cross-model (the differentiator)
40
+
41
+ - [ ] XSD → LutaML model export — DEFERRED: there is no cross-model
42
+ contract in this repo yet (LutaML class/association export needs a
43
+ design note agreed with the lutaml-model owners); the exporter
44
+ here is the template for that work
45
+ - [ ] Metanorma integration for schema-package documentation — DEFERRED:
46
+ depends on the SPA data contract being frozen
47
+
48
+ ## Adjacent (evaluate, don't commit)
49
+
50
+ - [ ] ISO Schematron rule-engine plugin — DEFERRED: evaluate demand;
51
+ the rule_registry pattern fits, and the conformance harness gives
52
+ the measurement hook when it is taken up
@@ -0,0 +1,80 @@
1
+ # TODO.performance — measured speed on the fastest Ruby XML engine
2
+
3
+ Goal: stay the fastest, prove it continuously, and adopt every new engine
4
+ surface as moxml/leptris ship it.
5
+
6
+ ## Engine integration maturation
7
+
8
+ - [x] Track https://github.com/lutaml/moxml/issues/410 — FIXED and
9
+ RELEASED (moxml 0.5.149, moxml PR #411). lutaml-xsd now locks
10
+ 0.5.149: `Schema#content_valid?` answers correctly on leptris and
11
+ the engine content-seam specs run live.
12
+ - [x] Content delegation verified live: engine answers true/false for
13
+ matching/mismatching child sequences (engine_context_spec + the
14
+ formerly gated content-model rule specs).
15
+ - [ ] Adopt moxml `Plan`/`StructPlan` materialization for the hot
16
+ parse path if lutaml-model exposes seams for it (profile first)
17
+ - [ ] Engine error payload richness: once line/column/path are stable
18
+ across adapters, surface them in CLI output and the SPA findings UI
19
+ — PARTIAL: CLI JSON already carries code/message/severity/
20
+ location/line_number/context (verified 2026-10-21); SPA wiring
21
+ rides TODO.docs-spa.md
22
+
23
+ ## Benchmarks
24
+
25
+ - [x] In-repo benchmark suite (`benchmark/engines.rb` + `rake
26
+ benchmark:engines`): engine parse/xpath/serialize, pipeline parse,
27
+ schema to_xml, compiled-schema validation
28
+ - [x] Baseline table: leptris vs nokogiri adapter (2026-10-20, Ruby 3.4.8,
29
+ arm64-darwin, 10.8 KB corpus converted from
30
+ metanorma-model-iso-grammars/bsi.rnc under rng 0.3.7 — corpus is
31
+ SMALL because rng 0.3.7 partially resolves the include tree;
32
+ re-run after the rng 0.3.9 recalibration for the full corpus):
33
+
34
+ | measurement | leptris | nokogiri |
35
+ |---|---|---|
36
+ | engine_parse | 0.3 ms | 0.4 ms |
37
+ | engine_xpath_elements | 0.9 ms | 0.4 ms |
38
+ | engine_serialize | 0.7 ms | 0.1 ms |
39
+ | pipeline_schema_parse | 274 ms | 377 ms |
40
+ | pipeline_schema_to_xml | 224 ms | 223 ms |
41
+ | engine_xsd_compile | 0.1 ms | n/a (SyntaxError on rng-0.3.7 corpus) |
42
+ | engine_xsd_validate | 0.0 ms | n/a |
43
+
44
+ Caveat: per-op values are noisy run-to-run (0.1–12 ms spread observed)
45
+ on a small corpus; the harness is the deliverable, numbers firm up on
46
+ the post-recalibration corpus.
47
+ - [ ] CI-optional benchmark run (manual dispatch) with trend notes —
48
+ no PR-blocking perf gates yet
49
+
50
+ ## Profiling targets
51
+
52
+ - [ ] SPA generation memory profile on 100+ schema corpora — DEFERRED:
53
+ needs the post-recalibration corpus (TODO.cleanup.md) and the SPA
54
+ pillar (TODO.docs-spa.md); the marshal-cleanup code is already in
55
+ place
56
+ - [ ] `EngineContext.compile` cost on multi-schema repositories —
57
+ DEFERRED: measured at 0.1 ms/compile on the benchmark corpus (well
58
+ below noise); revisit only when the full-include corpus changes the
59
+ picture
60
+ - [ ] XmlNavigator walk cost on deep instance trees — DEFERRED: no
61
+ evidence of a hotspot yet; take it with the rule-engine wiring,
62
+ which is the walker's main consumer
63
+
64
+ ## moxml Builder DSL profile (2026-10-21, reported to moxml sota/04)
65
+
66
+ rng's converter drives `Moxml::Builder`; the same 500-define synthetic
67
+ tree through the same DSL:
68
+
69
+ | path | time |
70
+ |---|---|
71
+ | Moxml Builder + nokogiri adapter | 0.059 s |
72
+ | Moxml Builder + leptris adapter | 0.463 s |
73
+ | direct `Leptris::XML::Builder` | 0.007 s |
74
+
75
+ The ~8x gap is in moxml's leptris builder-face interaction (create_tag /
76
+ method_missing / face flush), not the binding — the DSL through the
77
+ nokogiri adapter is only ~2x native Nokogiri's builder. moxml's scoreboard
78
+ covers the mutation API, not the Builder DSL; this is offered as a row +
79
+ profile lead for the moxml perf branch. Work belongs in moxml, not here.
80
+
data/TODO.som.md ADDED
@@ -0,0 +1,47 @@
1
+ # TODO.som — schema object model completeness (XSOM parity)
2
+
3
+ Goal: the Ruby SOM must answer every structural question XSOM can, with
4
+ XSD 1.1 constructs layered on.
5
+
6
+ Status 2026-10-20: DEFERRED as a pillar. Reason: every item here is a
7
+ specification-parity design cycle (component resolution semantics,
8
+ derivation graphs, 1.1 constructs), and the conformance gap register
9
+ (rule-engine wiring first) and the release-gated rng recalibration are
10
+ higher-leverage prerequisites. Track this file as the design backlog; do
11
+ not start mid-cycle.
12
+
13
+ ## Parse-side completeness
14
+
15
+ - [ ] `xs:redefine` semantics (redefinition chains, component shadowing)
16
+ — DEFERRED: needs a component-resolution design note first
17
+ - [ ] Chameleon includes (namespace adoption rules) — DEFERRED: verify
18
+ current behavior against the rng include fixes, then design
19
+ - [ ] Substitution group closure and membership enumeration on the model
20
+ — DEFERRED: pairs with the conformance substitution-group gap
21
+ - [ ] `xs:group` / `xs:attributeGroup` reference resolution including
22
+ redefined groups — DEFERRED (same design cycle)
23
+ - [ ] Complex type derivation graph: extension/restriction chains,
24
+ `{base type definition}` reachability — DEFERRED: the type-hierarchy
25
+ analyzer exists; extending it into a full derivation graph is the
26
+ design entry point
27
+ - [ ] Simple type facets carried losslessly — DEFERRED: covered
28
+ opportunistically by the JSON Schema exporter tests; a dedicated
29
+ round-trip matrix should come with the recalibration
30
+
31
+ ## Query surfaces
32
+
33
+ - [ ] Structural queries via moxml XPath where trees are involved —
34
+ DEFERRED: profile first (EngineContext already avoids Ruby walks)
35
+ - [ ] `SchemaRepository#types_in_namespace` deterministic ordering and
36
+ stable public docs — DEFERRED: small, take it with the next
37
+ repository-touching PR
38
+ - [ ] Type hierarchy export (DOT/mermaid) into the SPA — DEFERRED: needs
39
+ the SPA work in TODO.docs-spa.md
40
+
41
+ ## 1.1 constructs (behind `version: "1.1"`)
42
+
43
+ - [ ] `xs:assert` / `xs:assertion` model + evaluation hooks — DEFERRED:
44
+ XSD 1.1 strategy decision needed (engine support vs pure Ruby)
45
+ - [ ] `xs:alternative` conditional types; `openContent` modes — DEFERRED
46
+ (same 1.1 strategy)
47
+ - [ ] `xs:override` and `xs:defaultOpenContent` — DEFERRED (same)
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Engine benchmark: leptris vs nokogiri through Moxml, on a real corpus.
4
+ #
5
+ # ADAPTER=leptris bundle exec ruby benchmark/engines.rb
6
+ #
7
+ # Per-adapter numbers land on stdout as `key: value` lines; the rake task
8
+ # (`rake benchmark:engines`) runs both adapters for side-by-side recording
9
+ # in TODO.performance.md.
10
+
11
+ require "benchmark"
12
+ require "tmpdir"
13
+ require "fileutils"
14
+
15
+ ADAPTER = ENV.fetch("ADAPTER", "leptris")
16
+
17
+ require "rng" if ADAPTER == "leptris" # load first so Moxml picks it
18
+ require "lutaml/xsd"
19
+
20
+ def corpus_path
21
+ corpus = File.join(__dir__, "..", "tmp", "bench_corpus.xsd")
22
+ unless File.exist?(corpus)
23
+ rnc = File.expand_path(
24
+ "../spec/fixtures/metanorma-model-iso-grammars/bsi.rnc", __dir__
25
+ )
26
+ grammar = Rng.parse_file(rnc)
27
+ schema = Lutaml::Xsd::RngToXsdConverter.new(grammar, file_path: rnc).convert
28
+ FileUtils.mkdir_p(File.dirname(corpus))
29
+ File.write(corpus, schema.to_xml)
30
+ end
31
+ corpus
32
+ end
33
+
34
+ def measure(label, iterations: 5, &)
35
+ gc_was = GC.enable
36
+ GC.start
37
+ elapsed = Benchmark.realtime { iterations.times(&) }
38
+ GC.start if gc_was
39
+ format("%-34s %8.1f ms/op", label, (elapsed / iterations) * 1000)
40
+ end
41
+
42
+ corpus = File.read(corpus_path)
43
+ ctx = Moxml::Context.new(ADAPTER.to_sym)
44
+
45
+ puts "adapter: #{ADAPTER}"
46
+ puts "corpus_bytes: #{corpus.bytesize}"
47
+
48
+ # Engine-level parse
49
+ doc = nil
50
+ puts measure("engine_parse") { doc = ctx.parse(corpus) }
51
+
52
+ # Engine-level XPath
53
+ puts measure("engine_xpath_elements") { doc.xpath("//*[local-name() = 'element']") }
54
+
55
+ # Engine-level serialize
56
+ puts measure("engine_serialize") { doc.to_xml }
57
+
58
+ # Schema pipeline parse (lutaml-model over Moxml)
59
+ Lutaml::Xml::Schema::Xsd.parse(corpus) # warm autoloads/registration
60
+ schema = nil
61
+ puts measure("pipeline_schema_parse") { schema = Lutaml::Xml::Schema::Xsd.parse(corpus) }
62
+
63
+ # Schema model serialization
64
+ puts measure("pipeline_schema_to_xml") { schema.to_xml }
65
+
66
+ # Compiled-schema instance validation (tier-1 engines)
67
+ instance = <<~XML
68
+ <bible xmlns="http://example.com/bsi"><content><section-title>t</section-title></content></bible>
69
+ XML
70
+ begin
71
+ compiled = ctx.xsd(corpus)
72
+ puts measure("engine_xsd_compile", iterations: 3) { ctx.xsd(corpus) }
73
+ puts measure("engine_xsd_validate") { compiled.validate(instance) }
74
+ rescue StandardError => e
75
+ puts "engine_xsd_compile n/a (#{e.class})"
76
+ puts "engine_xsd_validate n/a (#{e.class})"
77
+ end
@@ -48,6 +48,10 @@ validation:
48
48
  # Validate namespace declarations
49
49
  validate_namespaces: true
50
50
 
51
+ # Engine-native XSD validation (Moxml::XSD) when the adapter
52
+ # provides an engine (leptris/nokogiri); findings are additive
53
+ engine_validation: true
54
+
51
55
  # Error reporting configuration
52
56
  error_reporting:
53
57
  # Include XPath location in error messages
@@ -0,0 +1,43 @@
1
+ # Conformance suite
2
+
3
+ `spec/conformance/cases/` holds one directory per case class, modelled on
4
+ the W3C XML Schema Test Suite category taxonomy. The canonical 2007 suite
5
+ distribution is no longer served from w3.org (checked 2026-10-20); cases
6
+ here are original, and a vendored upstream subset can be added later with
7
+ a provenance + license README alongside it.
8
+
9
+ ## Case layout
10
+
11
+ ```
12
+ spec/conformance/cases/<case-name>/
13
+ schema.xsd # the schema under test
14
+ valid-*.xml # instances that MUST validate
15
+ invalid-*.xml # instances that MUST NOT validate
16
+ ```
17
+
18
+ ## Running
19
+
20
+ ```bash
21
+ bundle exec rake conformance
22
+ ```
23
+
24
+ Prints a per-mode report and writes `tmp/conformance_report.md`.
25
+
26
+ Two modes are measured independently:
27
+
28
+ - **engine** — `Moxml::XSD` tier-1 validation through `EngineContext`
29
+ (leptris when the adapter ships an engine)
30
+ - **pipeline** — the production `Validator` stack (`ValidationJob` with
31
+ the default rule registry)
32
+
33
+ ## Baseline and gap register
34
+
35
+ Recorded in `TODO.conformance.md` (2026-10-20: 20/25 = 80.0% in both
36
+ modes). Each failing check maps to a tracked gap; the gap register is the
37
+ work queue for the conformance pillar.
38
+
39
+ ## Adding a case
40
+
41
+ Create the directory, add `schema.xsd`, at least one `valid-*.xml` and one
42
+ `invalid-*.xml`, and re-run the rake task. No registration needed — the
43
+ runner discovers directories containing `schema.xsd`.
@@ -0,0 +1,42 @@
1
+ # JSON Schema export
2
+
3
+ `Lutaml::Xsd::JsonSchemaExporter` writes an XSD schema as a JSON Schema
4
+ 2020-12 document.
5
+
6
+ ```ruby
7
+ schema = Lutaml::Xml::Schema::Xsd.parse(File.read("schema.xsd"))
8
+ json = Lutaml::Xsd::JsonSchemaExporter.new(schema).to_json
9
+ File.write("schema.json", json)
10
+ ```
11
+
12
+ ## What maps
13
+
14
+ - top-level `complexType`/`simpleType`/`element` declarations → `$defs`
15
+ - `sequence` / `all` elements → `properties`; `minOccurs > 0` → `required`
16
+ - `maxOccurs > 1` / `unbounded` → `{"type": "array", "items": ...}`
17
+ - `choice` and top-level elements → `oneOf`
18
+ - attributes → `properties`; `use="required"` → `required`; `fixed` → `const`
19
+ - `simpleType` restrictions: `enumeration` → `enum`; `pattern` → `pattern`;
20
+ length/range facets → `minLength`/`maxLength`/`minimum`/`maximum`/
21
+ `exclusiveMinimum`/`exclusiveMaximum`
22
+ - built-in types to JSON types/formats (date → `format: date`, dateTime →
23
+ `date-time`, time → `time`, anyURI → `uri`, integer family → `integer`,
24
+ decimal/float/double → `number`)
25
+ - `nillable="true"` → the JSON type accepts `null`
26
+
27
+ ## Loss table (deliberate)
28
+
29
+ | XSD | JSON Schema outcome |
30
+ |---|---|
31
+ | XSD regular expressions | emitted verbatim; XSD regex dialect differs from ECMA-262 — patterns may over/under-match |
32
+ | `key` / `keyref` / `unique` identity constraints | not representable; dropped |
33
+ | substitution groups | members are separate `$defs`; head/member relation is not encoded |
34
+ | complex-type derivation (extension/restriction) | flattened into the derived type; base linkage lost |
35
+ | `anyAttribute` / open content | not represented (`additionalProperties: false` is emitted) |
36
+ | wildcards (`xs:any`) | not represented |
37
+ | default values | not emitted (defaults are instance-processing semantics) |
38
+ | mixed content | modelled as element properties only; interleaved text is not captured |
39
+ | XSD 1.1 assertions / alternatives / openContent | not represented |
40
+
41
+ The exporter targets documentation, tooling interop, and contract
42
+ publication; it is not a validation-equivalence layer.