suma 0.5.7 → 0.5.9

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/nightly-benchmark.yml +25 -0
  3. data/.github/workflows/release-provenance.yml +41 -0
  4. data/.rubocop_todo.yml +84 -0
  5. data/CLAUDE.md +44 -0
  6. data/README.adoc +136 -11
  7. data/Rakefile +13 -1
  8. data/bin/benchmark_corpus +138 -0
  9. data/bin/release_provenance +40 -0
  10. data/lib/suma/build_plan.rb +71 -0
  11. data/lib/suma/change_classification.rb +37 -0
  12. data/lib/suma/change_impact.rb +61 -0
  13. data/lib/suma/change_manifest.rb +147 -0
  14. data/lib/suma/cli/build.rb +23 -0
  15. data/lib/suma/cli/compare.rb +49 -2
  16. data/lib/suma/cli/core.rb +83 -0
  17. data/lib/suma/cli/export.rb +140 -10
  18. data/lib/suma/cli/extract_terms.rb +11 -0
  19. data/lib/suma/cli/generate_register.rb +12 -0
  20. data/lib/suma/cli/resolve.rb +45 -0
  21. data/lib/suma/cli/validate.rb +90 -0
  22. data/lib/suma/cli.rb +1 -0
  23. data/lib/suma/closure_validator.rb +317 -0
  24. data/lib/suma/corpus_graph.rb +141 -0
  25. data/lib/suma/express_schema.rb +22 -28
  26. data/lib/suma/null_cache.rb +4 -0
  27. data/lib/suma/owl_projection.rb +611 -0
  28. data/lib/suma/processor.rb +55 -6
  29. data/lib/suma/provenance.rb +159 -0
  30. data/lib/suma/publication.rb +108 -0
  31. data/lib/suma/register_manifest_generator.rb +76 -8
  32. data/lib/suma/schema_cache.rb +72 -13
  33. data/lib/suma/schema_category.rb +31 -2
  34. data/lib/suma/schema_collection.rb +58 -5
  35. data/lib/suma/schema_exporter.rb +5 -5
  36. data/lib/suma/schema_naming.rb +29 -33
  37. data/lib/suma/schema_profile.rb +134 -0
  38. data/lib/suma/term_classification.rb +9 -2
  39. data/lib/suma/term_extractor.rb +76 -35
  40. data/lib/suma/urn_resolver.rb +368 -0
  41. data/lib/suma/version.rb +1 -1
  42. data/lib/suma/worker_pool.rb +62 -0
  43. data/lib/suma.rb +12 -0
  44. metadata +19 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98f57b1e48077074a8b282922f6b1196a718fc50bfae43d31fe81dad940d0092
4
- data.tar.gz: 3c0982d9e700c84b6cfc9030d835cef3f2bd34ae3309df3ce622f25301124a11
3
+ metadata.gz: 54c10b8d56459f9d53cd1b5988cbcc2193675f4dffa43e115845400497faa8f9
4
+ data.tar.gz: b46df0c3ef57f69e2e0e5e3b7dfdf3c464189a66f372ef2a5f7a5d3a8d47b34b
5
5
  SHA512:
6
- metadata.gz: 95856f79045ee2124742c28d86c18fe50401bbc642ad44872912b0e05e31713a9fb479601a18f0320810583f2a2ae1b5de2d5d897815668d56087f6078af7d90
7
- data.tar.gz: d0514ee213feaed81c9f4d79ff10728634c60ba5c731cc9a2dc9adae64fa87d45ec7512c6697eb31c5de9efc9f75198449d90631d1a032c69e65833b3aee7bc3
6
+ metadata.gz: 51f6a6ed42abb76af7342d06e0b73c3132ddf7e65ffdb5d29c96892860fd3be534557581f6bd14b6dd15e022a8e2ac250cdb23f6ed4d68242457515b8951b415
7
+ data.tar.gz: c562c0691baa4b3fb708b94dd5853014a18e4bad0cd8527cbf7922a7273ebd982a5239ea13914c0d60f279e2ceae9b534a43a58ff42b339c7dd5114f7e5be576
@@ -0,0 +1,25 @@
1
+ name: nightly-benchmark
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "17 3 * * *"
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ benchmark:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: "3.4"
19
+ bundler-cache: true
20
+ - name: Run synthetic corpus benchmark
21
+ run: bundle exec ruby bin/benchmark_corpus
22
+ env:
23
+ SUMA_BENCH_SCHEMAS: "1000"
24
+ SUMA_BENCH_MAX_SECONDS: "180"
25
+ SUMA_BENCH_MAX_RSS_MB: "2048"
@@ -0,0 +1,41 @@
1
+ name: release-provenance
2
+
3
+ on:
4
+ # Tag pushes reliably fan out (the release workflow pushes v* tags with a
5
+ # PAT); release-published events created by CI tokens may not.
6
+ push:
7
+ tags:
8
+ - "v*"
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+ # Keyless sigstore signing exchanges this token for a short-lived certificate.
14
+ id-token: write
15
+
16
+ jobs:
17
+ provenance:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ with:
22
+ ref: ${{ github.event.release.tag_name || github.ref }}
23
+ - uses: ruby/setup-ruby@v1
24
+ with:
25
+ ruby-version: "3.4"
26
+ bundler-cache: true
27
+ - uses: sigstore/cosign-installer@v3
28
+ - name: Build gem
29
+ run: gem build suma.gemspec
30
+ - name: Write, verify, and sign provenance
31
+ run: bundle exec ruby bin/release_provenance
32
+ env:
33
+ SUMA_PROVENANCE_SIGN: sigstore
34
+ - name: Independent sha256 pass
35
+ run: sha256sum *.gem provenance.json
36
+ - uses: actions/upload-artifact@v4
37
+ with:
38
+ name: release-provenance
39
+ path: |
40
+ provenance.json
41
+ provenance.json.bundle
data/.rubocop_todo.yml CHANGED
@@ -116,6 +116,11 @@ Lint/UnusedMethodArgument:
116
116
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
117
117
  Metrics/AbcSize:
118
118
  Exclude:
119
+ - 'bin/benchmark_corpus'
120
+ - 'lib/suma/worker_pool.rb'
121
+ - 'spec/suma/reproducibility_spec.rb'
122
+ - 'lib/suma/cli/resolve.rb'
123
+ - 'lib/suma/urn_resolver.rb'
119
124
  - 'lib/suma/cli/check_svg_quality.rb'
120
125
  - 'lib/suma/cli/core.rb'
121
126
  - 'lib/suma/jsdai/figure.rb'
@@ -131,18 +136,27 @@ Metrics/AbcSize:
131
136
  - 'lib/suma/svg_quality/formatters/terminal_formatter.rb'
132
137
  - 'lib/suma/term_extractor.rb'
133
138
  - 'lib/suma/thor_ext.rb'
139
+ - 'lib/suma/closure_validator.rb'
140
+ - 'lib/suma/corpus_graph.rb'
141
+ - 'lib/suma/change_manifest.rb'
142
+ - 'lib/suma/cli/generate_register.rb'
143
+ - 'lib/suma/owl_projection.rb'
144
+ - 'lib/suma/cli/export.rb'
134
145
 
135
146
  # Offense count: 2
136
147
  # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
137
148
  # AllowedMethods: refine
138
149
  Metrics/BlockLength:
139
150
  Exclude:
151
+ - 'bin/benchmark_corpus'
140
152
  - 'lib/suma/term_extractor.rb'
141
153
 
142
154
  # Offense count: 8
143
155
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
144
156
  Metrics/CyclomaticComplexity:
145
157
  Exclude:
158
+ - 'lib/suma/worker_pool.rb'
159
+ - 'lib/suma/urn_resolver.rb'
146
160
  - 'lib/suma/cli/check_svg_quality.rb'
147
161
  - 'lib/suma/eengine/wrapper.rb'
148
162
  - 'lib/suma/express_schema.rb'
@@ -151,11 +165,19 @@ Metrics/CyclomaticComplexity:
151
165
  - 'lib/suma/schema_comparer.rb'
152
166
  - 'lib/suma/svg_quality/formatters/terminal_formatter.rb'
153
167
  - 'lib/suma/thor_ext.rb'
168
+ - 'lib/suma/closure_validator.rb'
169
+ - 'lib/suma/owl_projection.rb'
154
170
 
155
171
  # Offense count: 32
156
172
  # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
157
173
  Metrics/MethodLength:
158
174
  Exclude:
175
+ - 'bin/benchmark_corpus'
176
+ - 'lib/suma/cli/build.rb'
177
+ - 'lib/suma/worker_pool.rb'
178
+ - 'lib/suma/provenance.rb'
179
+ - 'lib/suma/cli/resolve.rb'
180
+ - 'lib/suma/urn_resolver.rb'
159
181
  - 'spec/**/*'
160
182
  - 'lib/suma/cli/check_svg_quality.rb'
161
183
  - 'lib/suma/cli/core.rb'
@@ -172,23 +194,35 @@ Metrics/MethodLength:
172
194
  - 'lib/suma/staged_collection_builder.rb'
173
195
  - 'lib/suma/svg_quality/formatters/terminal_formatter.rb'
174
196
  - 'lib/suma/term_extractor.rb'
197
+ - 'lib/suma/closure_validator.rb'
198
+ - 'lib/suma/cli/validate.rb'
199
+ - 'lib/suma/cli/generate_register.rb'
200
+ - 'lib/suma/owl_projection.rb'
201
+ - 'lib/suma/cli/export.rb'
175
202
 
176
203
  # Offense count: 3
177
204
  # Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters.
178
205
  Metrics/ParameterLists:
179
206
  Exclude:
207
+ - 'lib/suma/processor.rb'
208
+ - 'lib/suma/express_schema.rb'
209
+ - 'lib/suma/term_extractor.rb'
180
210
  - 'lib/suma/cli/check_svg_quality.rb'
181
211
  - 'lib/suma/link_validator.rb'
182
212
  - 'lib/suma/register_manifest_generator.rb'
213
+ - 'lib/suma/closure_validator.rb'
183
214
 
184
215
  # Offense count: 4
185
216
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
186
217
  Metrics/PerceivedComplexity:
187
218
  Exclude:
219
+ - 'lib/suma/worker_pool.rb'
188
220
  - 'lib/suma/eengine/wrapper.rb'
189
221
  - 'lib/suma/jsdai/figure_image.rb'
190
222
  - 'lib/suma/schema_comparer.rb'
191
223
  - 'lib/suma/svg_quality/formatters/terminal_formatter.rb'
224
+ - 'lib/suma/closure_validator.rb'
225
+ - 'lib/suma/owl_projection.rb'
192
226
 
193
227
  # Offense count: 1
194
228
  # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
@@ -225,6 +259,15 @@ RSpec/ContextWording:
225
259
  # Configuration parameters: Max, CountAsOne.
226
260
  RSpec/ExampleLength:
227
261
  Exclude:
262
+ - 'spec/suma/schema_cache_spec.rb'
263
+ - 'spec/suma/worker_pool_spec.rb'
264
+ - 'spec/suma/schema_collection_rebuild_targets_spec.rb'
265
+ - 'spec/suma/build_plan_spec.rb'
266
+ - 'spec/suma/reproducibility_spec.rb'
267
+ - 'spec/suma/provenance_spec.rb'
268
+ - 'spec/suma/term_extractor_spec.rb'
269
+ - 'spec/suma/cli/resolve_spec.rb'
270
+ - 'spec/suma/urn_resolver_spec.rb'
228
271
  - 'spec/suma/cli/export_spec.rb'
229
272
  - 'spec/suma/cli/extract_terms_spec.rb'
230
273
  - 'spec/suma/cli/generate_register_spec.rb'
@@ -232,7 +275,11 @@ RSpec/ExampleLength:
232
275
  - 'spec/suma/cli/reformat_spec.rb'
233
276
  - 'spec/suma/cli_spec.rb'
234
277
  - 'spec/suma/collection_config_spec.rb'
278
+ - 'spec/suma/closure_validator_spec.rb'
235
279
  - 'spec/suma/collection_manifest_spec.rb'
280
+ - 'spec/suma/corpus_graph_spec.rb'
281
+ - 'spec/suma/owl_projection_spec.rb'
282
+ - 'spec/suma/schema_profile_spec.rb'
236
283
  - 'spec/suma/eengine_converter_spec.rb'
237
284
  - 'spec/suma/express_reformatter_spec.rb'
238
285
  - 'spec/suma/express_schema_spec.rb'
@@ -242,6 +289,8 @@ RSpec/ExampleLength:
242
289
  - 'spec/suma/link_validator_spec.rb'
243
290
  - 'spec/suma/manifest_traverser_spec.rb'
244
291
  - 'spec/suma/note_processor_spec.rb'
292
+ - 'spec/suma/publication_spec.rb'
293
+ - 'spec/suma/owl_projection_spec.rb'
245
294
  - 'spec/suma/processor_spec.rb'
246
295
  - 'spec/suma/register_manifest_generator_spec.rb'
247
296
  - 'spec/suma/schema_category_spec.rb'
@@ -256,6 +305,9 @@ RSpec/ExampleLength:
256
305
  - 'spec/suma/term_classification_spec.rb'
257
306
  - 'spec/suma/term_extractor_spec.rb'
258
307
  - 'spec/suma/urn_spec.rb'
308
+ - 'spec/suma/change_classification_spec.rb'
309
+ - 'spec/suma/change_manifest_spec.rb'
310
+ - 'spec/suma/change_impact_spec.rb'
259
311
 
260
312
  # Offense count: 4
261
313
  # Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns.
@@ -286,6 +338,13 @@ RSpec/MessageSpies:
286
338
  # Configuration parameters: Max.
287
339
  RSpec/MultipleExpectations:
288
340
  Exclude:
341
+ - 'spec/suma/schema_cache_spec.rb'
342
+ - 'spec/suma/worker_pool_spec.rb'
343
+ - 'spec/suma/reproducibility_spec.rb'
344
+ - 'spec/suma/provenance_spec.rb'
345
+ - 'spec/suma/term_extractor_spec.rb'
346
+ - 'spec/suma/cli/resolve_spec.rb'
347
+ - 'spec/suma/urn_resolver_spec.rb'
289
348
  - 'spec/suma/cli/build_spec.rb'
290
349
  - 'spec/suma/cli/export_spec.rb'
291
350
  - 'spec/suma/cli/expressir_spec.rb'
@@ -293,7 +352,11 @@ RSpec/MultipleExpectations:
293
352
  - 'spec/suma/cli/reformat_spec.rb'
294
353
  - 'spec/suma/cli_spec.rb'
295
354
  - 'spec/suma/collection_config_spec.rb'
355
+ - 'spec/suma/closure_validator_spec.rb'
296
356
  - 'spec/suma/collection_manifest_spec.rb'
357
+ - 'spec/suma/corpus_graph_spec.rb'
358
+ - 'spec/suma/owl_projection_spec.rb'
359
+ - 'spec/suma/schema_profile_spec.rb'
297
360
  - 'spec/suma/eengine_converter_spec.rb'
298
361
  - 'spec/suma/eengine_spec.rb'
299
362
  - 'spec/suma/express_reformatter_spec.rb'
@@ -304,6 +367,8 @@ RSpec/MultipleExpectations:
304
367
  - 'spec/suma/link_validator_spec.rb'
305
368
  - 'spec/suma/manifest_traverser_spec.rb'
306
369
  - 'spec/suma/note_processor_spec.rb'
370
+ - 'spec/suma/publication_spec.rb'
371
+ - 'spec/suma/owl_projection_spec.rb'
307
372
  - 'spec/suma/processor_spec.rb'
308
373
  - 'spec/suma/register_manifest_generator_spec.rb'
309
374
  - 'spec/suma/schema_category_spec.rb'
@@ -312,12 +377,16 @@ RSpec/MultipleExpectations:
312
377
  - 'spec/suma/schema_discovery_spec.rb'
313
378
  - 'spec/suma/schema_exporter_spec.rb'
314
379
  - 'spec/suma/schema_index_spec.rb'
380
+ - 'spec/suma/schema_naming_spec.rb'
315
381
  - 'spec/suma/schema_template_spec.rb'
316
382
  - 'spec/suma/site_config/config_spec.rb'
317
383
  - 'spec/suma/staged_collection_builder_spec.rb'
318
384
  - 'spec/suma/svg_quality/scanner_spec.rb'
319
385
  - 'spec/suma/term_classification_spec.rb'
320
386
  - 'spec/suma/term_extractor_spec.rb'
387
+ - 'spec/suma/change_classification_spec.rb'
388
+ - 'spec/suma/change_manifest_spec.rb'
389
+ - 'spec/suma/change_impact_spec.rb'
321
390
 
322
391
  # Offense count: 3
323
392
  # Configuration parameters: Max, AllowedGroups.
@@ -328,6 +397,10 @@ RSpec/NestedGroups:
328
397
  # Offense count: 1
329
398
  # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
330
399
  # SupportedInflectors: default, active_support
400
+ RSpec/DescribeClass:
401
+ Exclude:
402
+ - 'spec/suma/reproducibility_spec.rb'
403
+
331
404
  RSpec/SpecFilePathFormat:
332
405
  Exclude:
333
406
  - '**/spec/routing/**/*'
@@ -337,3 +410,14 @@ RSpec/SpecFilePathFormat:
337
410
  RSpec/StubbedMock:
338
411
  Exclude:
339
412
  - 'spec/suma/schema_comparer_integration_spec.rb'
413
+
414
+ # Offense count: 2
415
+ # Configuration parameters: Max.
416
+ RSpec/MultipleMemoizedHelpers:
417
+ Exclude:
418
+ - 'spec/suma/provenance_spec.rb'
419
+ - 'spec/suma/worker_pool_spec.rb'
420
+ - 'spec/suma/schema_cache_spec.rb'
421
+ - 'spec/suma/urn_resolver_spec.rb'
422
+ - 'spec/suma/register_manifest_generator_spec.rb'
423
+ - 'spec/suma/owl_projection_spec.rb'
data/CLAUDE.md CHANGED
@@ -23,15 +23,47 @@ rspec spec/path/to_spec.rb:42 # Run a single test at line 42
23
23
 
24
24
  ```bash
25
25
  exe/suma build METANORMA_YAML # Build collection from site manifest
26
+ exe/suma build METANORMA_YAML --incremental --jobs 4 # Re-render changed schemas + dependents; parallel export
27
+ SUMA_SCHEMA_CACHE_DIR=.suma-cache exe/suma build METANORMA_YAML # Reuse parsed models/outputs across builds
28
+ rake benchmark # 1,000-schema synthetic corpus budget check
29
+ exe/suma build METANORMA_YAML --provenance provenance.json # sha256 of inputs/outputs + toolchain
30
+ exe/suma build METANORMA_YAML --provenance provenance.json --sign sigstore # optional signature (minisign|sigstore)
26
31
  exe/suma export -o OUTPUT_DIR schema1.yml schema2.exp # Export schemas (YAML manifest or .exp files)
32
+ exe/suma export -o OUTPUT_DIR --publication AP_LEADER_ID schemas.yaml # Export an AP publication bundle (lead + USE-FROM closure)
33
+ exe/suma export --format owl -o OUT --urn BASE schemas.yaml # Project the corpus to OWL/Turtle (one file per schema; add --corpus for a corpus ontology)
27
34
  exe/suma compare TRIAL_SCHEMA REFERENCE_SCHEMA -v VER # Compare schemas, generate .changes.yaml
35
+ exe/suma compare ... -v VER --typed-output FILE --changelog --impact --manifest SCHEMAS_YAML # Typed change manifest, changelog, impact analysis
28
36
  exe/suma reformat PATH # Reformat EXP files (use -r for recursive)
29
37
  exe/suma generate-schemas METANORMA_YAML SCHEMAS_YAML # Generate schema manifest from site manifest
30
38
  exe/suma extract-terms SCHEMA_YAML GLOSSARIST_DIR -u URN # Extract EXPRESS entity concepts to Glossarist v3 format
39
+ exe/suma extract-terms ... --edition 3 --language-codes fra deu # Provenance + linked localizations
40
+ exe/suma resolve URN --manifest SCHEMAS_YAML --concepts DIR --register register.yaml -o OUT # Emit canonical JSON + HTML
41
+ exe/suma resolve --all --manifest SCHEMAS_YAML --concepts DIR --register register.yaml -o OUT # Emit every URN artifact
31
42
  exe/suma generate-register SCHEMA_YAML OUT_DIR -u URN --id ID --ref REF # Generate hierarchical register.yaml from schema manifest
32
43
  exe/suma validate links SCHEMAS_FILE DOCS_PATH # Validate EXPRESS cross-reference links
44
+ exe/suma validate closure SCHEMAS_MANIFEST [DOCUMENTS_PATH] # Referential closure: every reference resolves (exit 1 on violations)
33
45
  ```
34
46
 
47
+ ## Schema profiles
48
+
49
+ Suma works with any schema collection. `Suma::SchemaProfile` declares how schema
50
+ identifiers and paths map to types (`ExpressSchema::Type`), how
51
+ identifiers become display names (`SchemaNaming`), and the ordered
52
+ register categories (`SchemaCategory`). Registered profiles: `iso10303`
53
+ (default; STEP conventions incl. `/applications/` → application
54
+ protocol) and `generic` (no STEP conventions). The CLI takes
55
+ `--profile` on `export`, `extract-terms`, and `generate-register`.
56
+
57
+ ## ISO 10303 AP publications
58
+
59
+ An AP publication is a lead schema (the AP's ARM/MIM) bundled with the
60
+ module/resource schemas it USEs. `Suma::Publication` computes the
61
+ transitive USE-FROM/REFERENCE closure from the parsed EXPRESS interfaces
62
+ and resolves it against a schema manifest; `export --publication ID`
63
+ writes the bundle flat under `<output>/<lead_id>/`. In the register,
64
+ the "Application Protocols" category groups each AP's ARM/MIM under a
65
+ per-publication parent section.
66
+
35
67
  ## Architecture
36
68
 
37
69
  ### Core pipeline (build command)
@@ -59,6 +91,15 @@ exe/suma validate links SCHEMAS_FILE DOCS_PATH # Validate EXPRESS cross
59
91
  - **`ManifestTraverser`** — tree-walking service over a `CollectionManifest` (`find_schemas_only`, `export_schema_config`, `expand_schemas_only`, `remove_schemas_only_sources`)
60
92
  - **`SchemaDiscovery`** — schema-config I/O service on a single manifest node (`load_config` from `schemas.yaml`, `build_doc_entries`, `build_added_manifest`)
61
93
  - **`SchemaIndex`** — O(1) lookup index for schema and element names, used by `LinkValidator`
94
+ - **`ChangeClassification` / `ChangeManifest` / `ChangeImpact`** — S3 typed change ontology: eengine actions are classified (additive/compatible/breaking/editorial), the typed manifest (`change-entry` records via lutaml-model) is emitted alongside `.changes.yaml` with a markdown changelog, and impact analysis walks `CorpusGraph#dependents_of` to report affected schemas and AP publications
95
+ - **`CorpusGraph`** — the declared-element graph of a whole schema collection: schemas, declared elements, USE/REFERENCE interface edges; the resolution substrate for `ClosureValidator` (and later change-impact/incremental builds)
96
+ - **`ClosureValidator`** — S1 referential closure: extracts every EXPRESS-internal reference (attribute domains, SELECT/aggregate branches, where-rule expressions, subtype constraints, supertype/subtype lists, rule applies-to, interfaces) and resolves it against a `CorpusGraph`; zero violations is the corpus well-formedness gate (`rake closure`)
97
+ - **`OwlProjection`** — S2 formal semantics: projects a parsed corpus into OWL/Turtle. Entities → `owl:Class`; `SUBTYPE OF` → `rdfs:subClassOf`; SELECT → `owl:unionOf`; enums → `owl:oneOf` named individuals; simple type aliases → `rdfs:Datatype`; explicit/derived/inverse attributes → object/datatype properties attached through named `owl:Restriction` nodes (cardinality from optionality and aggregate bounds); WHERE rules and DERIVE/INVERSE clauses → annotated (`rdfs:comment`) constraints. IRIs come from `Urn` (`<base>:tech:<schema>.<element>`), per-schema ontologies declare `owl:imports` for their `USE FROM`/`REFERENCE` targets, and the corpus ontology imports every schema. `#validate` checks materialization, property declarations, orphan classes, and superclass acyclicity. Output contains no blank nodes, so projections are deterministic and golden-comparable
98
+ - **`SchemaCache`** — content-addressed on-disk store for generated `.exp` output and parsed Expressir repositories. Keys compose source bytes with the Expressir version (plus Suma/Marshal versions for parsed entries), so a hit is equivalent to a fresh build. Enabled by `SUMA_SCHEMA_CACHE_DIR`; hit/miss counters expose parse reuse. **`NullCache`** is the disabled-mode null object
99
+ - **`BuildPlan`** — incremental-build decision: hashes every schema source, diffs against the persisted state, and expands changed schemas to their transitive dependents through `CorpusGraph`. State defaults to `plain_schemas/.suma-build-state.json`; `SchemaCollection#rebuild_targets` is the build-facing query
100
+ - **`WorkerPool`** — bounded worker pool for independent per-schema work. Schema export runs in the pool (`build --jobs N`); Metanorma document compilation stays sequential and composes with `StagedCollectionBuilder`'s process isolation
101
+ - **`UrnResolver`** — S4 dereferenceable identity: resolves `tech:` schema/element URNs from a manifest, `term:` concept URNs from Glossarist documents, and the dataset URN from `register.yaml`; emits canonical JSON plus a static HTML document per URN at a path derived from the URN itself. Every URN the register emits resolves against the artifact set
102
+ - **`Provenance`** — S5 build provenance: sha256 of every input and output (files or directory trees) plus Ruby/Expressir/Suma toolchain versions, written as canonical JSON by `build --provenance FILE`. `Provenance.verify` recomputes the digests independently; `build --sign minisign|sigstore` (with `--sign-key` where needed) optionally signs the manifest, and `bin/release_provenance` signs keylessly with cosign in the release workflow (`SUMA_PROVENANCE_SIGN`)
62
103
  - **`LinkValidator`** — validates EXPRESS cross-reference links against a `SchemaIndex`; returns `LinkValidationResult` structs for unresolved links
63
104
 
64
105
  ### CLI structure
@@ -74,6 +115,8 @@ The `extract-terms` command reads an EXPRESS schema manifest, parses each `.exp`
74
115
  - Entity remarks processed into notes (first-sentence extraction, redundant note removal, invalid reference filtering)
75
116
  - Domain classification via `ExpressSchema::Type` (resource vs application module)
76
117
  - Section references for hierarchical grouping
118
+ - Provenance on every concept: the source schema citation, the entity URN under `sourced_from`, and the owning edition/version
119
+ - Linked localizations: `--language-codes` emits additional `localizations` of the same managed concept (same stable UUID, per-language localized UUIDs); the text is the source-language content until translations are supplied
77
120
 
78
121
  The `generate-register` command reads the same schema manifest and emits `register.yaml` with hierarchical sections:
79
122
  - Top-level groups: Resources (133) before Application Modules (1123)
@@ -86,6 +129,7 @@ The `generate-register` command reads the same schema manifest and emits `regist
86
129
  - **metanorma** — document compilation (`.adoc` → XML/HTML/PDF)
87
130
  - **lutaml-model** — YAML/XML model serialization (used in `SiteConfig`, `CollectionConfig`)
88
131
  - **glossarist** — term extraction output format
132
+ - **rdf / rdf-turtle** — OWL projection serialization (already required through glossarist; no direct gemspec entry)
89
133
  - **eengine** (optional, external binary) — schema comparison via `Eengine::Wrapper`
90
134
 
91
135
  ### Schema comparison flow
data/README.adoc CHANGED
@@ -14,12 +14,18 @@ to build the following artifacts:
14
14
 
15
15
  == Features
16
16
 
17
- * EXPRESS schema management for STEP standards
17
+ * EXPRESS schema management for STEP and other schema collections
18
18
  * Document collection building and compilation
19
+ * Referential closure validation over the whole corpus
19
20
  * EXPRESS links validation and extraction
20
21
  * Schema listing file generation
22
+ * OWL/Turtle projection with structural validation
23
+ * Typed change manifests, changelogs, and dependency impact analysis
24
+ * Glossarist terminology and hierarchical registers with edition metadata
25
+ * Dereferenceable URN artifacts (canonical JSON + static HTML)
26
+ * Incremental, parallel, and cached corpus builds
27
+ * Reproducible outputs with verifiable, optionally signed provenance
21
28
  * Integration with the Metanorma ecosystem
22
- * Progress tracking for schema loading operations
23
29
 
24
30
  == Installation
25
31
 
@@ -37,13 +43,16 @@ $ gem install suma
37
43
  # Defaults to `suma help`
38
44
  $ suma
39
45
  Commands:
40
- suma build METANORMA_SITE_MANIFEST # Build collection specified in site manifest (`metanorma*.yml`)
41
- suma convert-jsdai XML_FILE IMAGE_FILE OUTPUT_DIR # Convert JSDAI XML and image files to SVG and EXP files
42
- suma extract-terms SCHEMA_MANIFEST_FILE GLOSSARIST_OUTPUT_PATH # Extract EXPRESS entity concepts into Glossarist v3 format
43
- suma generate-register SCHEMA_MANIFEST_FILE OUTPUT_PATH # Generate hierarchical register.yaml from schema manifest
44
- suma generate-schemas METANORMA_MANIFEST_FILE SCHEMA_MANIFEST_FILE # Generate EXPRESS schema manifest file from Metanorma site manifest
45
- suma help [COMMAND] # Describe available commands or one specific command
46
+ suma build METANORMA_SITE_MANIFEST # Build collection from site manifest
47
+ suma check_svg_quality [PATH] # Check SVG quality
48
+ suma compare TRIAL_SCHEMA REFERENCE_SCHEMA # Compare schemas; typed changes, impact
49
+ suma convert-jsdai XML_FILE IMAGE_FILE OUTPUT_DIR # Convert JSDAI XML and image files to SVG and EXP
50
+ suma export *FILES -o OUTPUT # Export EXPRESS schemas or project them to OWL
51
+ suma extract-terms SCHEMA_MANIFEST_FILE GLOSSARIST_OUTPUT_PATH # Extract entity concepts to Glossarist v3
52
+ suma generate-register SCHEMA_MANIFEST_FILE OUTPUT_PATH # Generate hierarchical register.yaml
53
+ suma generate-schemas METANORMA_MANIFEST_FILE SCHEMA_MANIFEST_FILE # Generate a schema manifest from a site manifest
46
54
  suma reformat EXPRESS_FILE_PATH # Reformat EXPRESS files
55
+ suma resolve [URN] --manifest=MANIFEST -o OUTPUT # Emit canonical JSON + HTML for URNs
47
56
  suma validate SUBCOMMAND ...ARGS # Validate express documents
48
57
  ----
49
58
 
@@ -65,6 +74,13 @@ Options:
65
74
 
66
75
  `--[no-]compile`:: Compile or skip compilation of collection (default: true)
67
76
  `--schemas-all-path=PATH`, `-s PATH`:: Generate file that contains all schemas in the collection
77
+ `--[no-]staged`:: Memory-bounded build: compile each member in its own process, then reinflate
78
+ `--[no-]incremental`:: Re-render only changed schemas and their transitive dependents
79
+ `--state-file=PATH`:: Incremental build state file (default: `plain_schemas/.suma-build-state.json`)
80
+ `--jobs=N`:: Concurrent schema export workers (document compilation stays sequential; default: 1)
81
+ `--provenance=PATH`:: Write a provenance manifest (sha256 of inputs/outputs + toolchain versions)
82
+ `--sign=METHOD`:: Sign the provenance manifest: `minisign` or `sigstore`
83
+ `--sign-key=PATH`:: Signing key; minisign requires it, sigstore signs keylessly when omitted
68
84
 
69
85
  The generated `schemas-*.yaml` file name is derived from the input file name
70
86
  with the word `metanorma` replaced with `schemas`.
@@ -141,6 +157,7 @@ $ suma validate SUBCOMMAND [options]
141
157
  Subcommands:
142
158
 
143
159
  `links`:: Validate EXPRESS links
160
+ `closure`:: Validate referential closure over the whole corpus
144
161
 
145
162
  ==== Links subcommand
146
163
 
@@ -176,6 +193,23 @@ This command:
176
193
  * Writes validation results to the `OUTPUT_FILE`
177
194
  * Provides progress bars to track schema loading and link validation
178
195
 
196
+ ==== Closure subcommand
197
+
198
+ The `closure` subcommand extracts every EXPRESS-internal reference (attribute
199
+ domains, SELECT and aggregate branches, where-rule expressions, subtype
200
+ constraints, supertype/subtype lists, rule applies-to, and USE/REFERENCE
201
+ interfaces) and resolves it against the corpus graph. It exits non-zero when
202
+ any reference does not resolve, which makes it usable as a CI gate
203
+ (`rake closure` runs it on the fixture corpus).
204
+
205
+ [source,sh]
206
+ ----
207
+ $ suma validate closure SCHEMAS_MANIFEST [DOCUMENTS_PATH] [--format terminal|json|yaml]
208
+ ----
209
+
210
+ When `DOCUMENTS_PATH` is given, documentation-layer link failures are merged
211
+ into the same report.
212
+
179
213
 
180
214
  === Generate schemas command
181
215
 
@@ -225,7 +259,7 @@ used for other EXPRESS schema collections that require term extraction for
225
259
  glossary or dictionary applications.
226
260
 
227
261
  The `suma extract-terms` command extracts terms from EXPRESS schemas and
228
- generates a Glossarist v2 dataset in the output directory. This command processes
262
+ generates a Glossarist v3 dataset in the output directory. This command processes
229
263
  various types of STEP schemas and creates standardized terminology datasets
230
264
  suitable for glossary and dictionary applications.
231
265
 
@@ -239,12 +273,14 @@ Parameters:
239
273
  `SCHEMA_MANIFEST_FILE`:: Path to the schema manifest file that lists all schemas
240
274
  to process (e.g., "schemas-smrl-all.yml")
241
275
 
242
- `GLOSSARIST_OUTPUT_PATH`:: Path to the output directory where the Glossarist v2
276
+ `GLOSSARIST_OUTPUT_PATH`:: Path to the output directory where the Glossarist v3
243
277
  dataset will be generated
244
278
 
245
279
  Options:
246
280
 
247
281
  `--language_code`, `-l`:: Language code for the Glossarist dataset (default: "eng")
282
+ `--edition=N`:: Owning edition recorded on each concept
283
+ `--language-codes=a,b`:: Additional linked localization languages (the primary language is always included)
248
284
 
249
285
  ==== Supported schema types
250
286
 
@@ -275,7 +311,7 @@ information:
275
311
 
276
312
  ==== Output format
277
313
 
278
- The command generates a Glossarist v2 compliant dataset with:
314
+ The command generates a Glossarist v3 compliant dataset with:
279
315
 
280
316
  * `concept/` directory containing concept definition files in YAML
281
317
  * `localized_concept/` directory containing localized concept files in YAML
@@ -326,6 +362,13 @@ Options:
326
362
  The wildcard is stripped for the base URN and kept in `urnAliases`.
327
363
  *Required*.
328
364
 
365
+ `--year=N`:: Edition year recorded on the register
366
+
367
+ `--supersedes=URN`:: URN/ref of the superseded edition
368
+
369
+ `--edition=N`:: Edition number, tagged as `edition:N`; every register also
370
+ carries a `dataset:<id>` tag so sibling editions can be grouped.
371
+
329
372
  The URN is fully configurable — every component can be changed to match the
330
373
  dataset being exported. The general structure is:
331
374
 
@@ -521,6 +564,11 @@ Options:
521
564
  `--[no-]annotations`:: Include annotations (remarks/comments) in exported schemas (default: false)
522
565
 
523
566
  `--[no-]zip`:: Create ZIP archive of exported schemas (default: false)
567
+ `--profile=PROFILE`:: Schema profile: `iso10303` (default) or `generic`
568
+ `--publication=SCHEMA_ID`:: Export an AP publication bundle: the lead schema plus its transitive USE-FROM/REFERENCE closure
569
+ `--format=FORMAT`:: Output format: `express` (default) or `owl`
570
+ `--[no-]corpus`:: OWL only: emit one corpus ontology importing every schema ontology
571
+ `--urn=URN`:: OWL only: base URN for IRIs, before the `:tech` component
524
572
 
525
573
  ==== Behavior
526
574
 
@@ -639,6 +687,65 @@ express-files/
639
687
  ----
640
688
 
641
689
 
690
+ === Schema profiles and AP publications
691
+
692
+ Suma works with any schema collection. `Suma::SchemaProfile` declares how
693
+ schema identifiers and paths map to types, how identifiers become display
694
+ names, and the ordered register categories. Registered profiles:
695
+
696
+ `iso10303`:: Default; STEP conventions including `/applications/` classifying as an application protocol
697
+ `generic`:: No STEP conventions (one category for every schema)
698
+
699
+ An AP publication is a lead schema bundled with the module and resource
700
+ schemas it USEs. `--publication SCHEMA_ID` computes the transitive
701
+ USE-FROM/REFERENCE closure and writes the bundle flat under
702
+ `OUTPUT/<lead_id>/`. In the register, application protocols are grouped under
703
+ a per-publication parent section.
704
+
705
+ === OWL projection
706
+
707
+ `suma export --format owl` projects a parsed corpus into OWL/Turtle:
708
+
709
+ * entities become `owl:Class`; `SUBTYPE OF` becomes `rdfs:subClassOf`
710
+ * SELECT types become `owl:unionOf`; enumerations become `owl:oneOf` named individuals
711
+ * simple type aliases become `rdfs:Datatype`
712
+ * explicit, derived, and inverse attributes become object/datatype properties attached through named `owl:Restriction` nodes, with cardinality from optionality and aggregate bounds
713
+ * WHERE, DERIVE, and INVERSE clauses are preserved as annotated constraints
714
+
715
+ IRIs derive from the corpus URN scheme (`<base>:tech:<schema>.<element>`).
716
+ Per-schema ontologies declare `owl:imports` for their USE-FROM/REFERENCE
717
+ targets, and `--corpus` adds a corpus ontology importing every schema. The
718
+ projection is validated before writing (materialization, property
719
+ declarations, orphan classes, dangling superclasses, superclass cycles), and
720
+ contains no blank nodes, so it is deterministic and golden-comparable.
721
+
722
+ [source,sh]
723
+ ----
724
+ $ suma export --format owl --urn urn:iso:std:iso:10303:-2:ed-2:en \
725
+ -o OUTPUT schemas.yaml
726
+ $ suma export --format owl --corpus --urn urn:iso:std:iso:10303:-2:ed-2:en \
727
+ -o OUTPUT schemas.yaml
728
+ ----
729
+
730
+ === Resolve URNs command
731
+
732
+ The `resolve` command turns URNs into static, dereferenceable artifacts: a
733
+ canonical JSON document (sorted keys) and an HTML page per URN, written under
734
+ a path derived from the URN itself. It resolves:
735
+
736
+ * `tech:` schema, declared-element, and attribute (OWL property) URNs from a schema manifest
737
+ * `term:` concept URNs from Glossarist concept documents
738
+ * the dataset URN from `register.yaml`
739
+
740
+ [source,sh]
741
+ ----
742
+ $ suma resolve URN --manifest SCHEMAS_YAML [--concepts DIR] [--register register.yaml] -o OUT
743
+ $ suma resolve --all --manifest SCHEMAS_YAML [--concepts DIR] [--register register.yaml] -o OUT
744
+ ----
745
+
746
+ `--all` also writes `index.html`, linking every artifact. No server is
747
+ required.
748
+
642
749
  === Compare schemas command
643
750
 
644
751
  ==== General
@@ -667,6 +774,10 @@ Options:
667
774
  `--trial-stepmod=PATH`:: Override auto-detected trial repository root
668
775
  `--reference-stepmod=PATH`:: Override auto-detected reference repository root
669
776
  `--verbose`:: Enable verbose output
777
+ `--typed-output=PATH`:: Write a typed change manifest (lutaml-model records with classification)
778
+ `--[no-]changelog`:: Render a markdown changelog alongside the change manifest
779
+ `--[no-]impact`:: Report affected schemas and AP publications using the corpus graph
780
+ `--manifest=SCHEMAS_YAML`:: Schema manifest used for impact analysis
670
781
 
671
782
  ==== Typical workflow
672
783
 
@@ -1163,3 +1274,17 @@ Code-style rules enforced throughout `lib/suma/`:
1163
1274
  == Copyright and license
1164
1275
 
1165
1276
  Copyright Ribose. BSD 2-clause license.
1277
+
1278
+ == Development and benchmarks
1279
+
1280
+ The default gate is `rake` (specs, RuboCop, referential closure).
1281
+
1282
+ [source,sh]
1283
+ ----
1284
+ $ rake benchmark
1285
+ ----
1286
+
1287
+ `rake benchmark` builds a synthetic 1,000-schema corpus and asserts wall-time
1288
+ and peak-RSS budgets; the same benchmark runs nightly in CI. Schema export can
1289
+ run in parallel with `build --jobs N`, while Metanorma document compilation
1290
+ stays sequential and composes with staged process isolation.
data/Rakefile CHANGED
@@ -9,4 +9,16 @@ require "rubocop/rake_task"
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
12
- task default: %i[spec rubocop]
12
+ desc "Validate referential closure of the fixture corpora"
13
+ task :closure do
14
+ # `ruby` (Rake helper) rather than `bundle exec exe/suma`: the latter
15
+ # does not resolve relative executables on Windows.
16
+ ruby "exe/suma validate closure spec/fixtures/ap_publication/schemas.yaml"
17
+ end
18
+
19
+ task default: %i[spec rubocop closure]
20
+
21
+ desc "Benchmark a synthetic 1,000-schema corpus against time/memory budgets"
22
+ task :benchmark do
23
+ ruby "bin/benchmark_corpus"
24
+ end