suma 0.5.7 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/nightly-benchmark.yml +25 -0
- data/.github/workflows/release-provenance.yml +38 -0
- data/.rubocop_todo.yml +84 -0
- data/CLAUDE.md +44 -0
- data/Rakefile +13 -1
- data/bin/benchmark_corpus +138 -0
- data/bin/release_provenance +40 -0
- data/lib/suma/build_plan.rb +71 -0
- data/lib/suma/change_classification.rb +37 -0
- data/lib/suma/change_impact.rb +61 -0
- data/lib/suma/change_manifest.rb +147 -0
- data/lib/suma/cli/build.rb +23 -0
- data/lib/suma/cli/compare.rb +49 -2
- data/lib/suma/cli/core.rb +83 -0
- data/lib/suma/cli/export.rb +140 -10
- data/lib/suma/cli/extract_terms.rb +11 -0
- data/lib/suma/cli/generate_register.rb +12 -0
- data/lib/suma/cli/resolve.rb +45 -0
- data/lib/suma/cli/validate.rb +90 -0
- data/lib/suma/cli.rb +1 -0
- data/lib/suma/closure_validator.rb +317 -0
- data/lib/suma/corpus_graph.rb +141 -0
- data/lib/suma/express_schema.rb +22 -28
- data/lib/suma/null_cache.rb +4 -0
- data/lib/suma/owl_projection.rb +611 -0
- data/lib/suma/processor.rb +55 -6
- data/lib/suma/provenance.rb +159 -0
- data/lib/suma/publication.rb +108 -0
- data/lib/suma/register_manifest_generator.rb +76 -8
- data/lib/suma/schema_cache.rb +72 -13
- data/lib/suma/schema_category.rb +31 -2
- data/lib/suma/schema_collection.rb +58 -5
- data/lib/suma/schema_exporter.rb +5 -5
- data/lib/suma/schema_naming.rb +29 -33
- data/lib/suma/schema_profile.rb +134 -0
- data/lib/suma/term_classification.rb +9 -2
- data/lib/suma/term_extractor.rb +76 -35
- data/lib/suma/urn_resolver.rb +318 -0
- data/lib/suma/version.rb +1 -1
- data/lib/suma/worker_pool.rb +62 -0
- data/lib/suma.rb +12 -0
- metadata +19 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3674c1699305e230eabb86ed343195cd0b6915c063b77b3b1fb540ce03a6e684
|
|
4
|
+
data.tar.gz: 8f86f1e113c3774de773261c96cab1292222be7e5ae97e9b463a2136c40aefce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 60c730b7f8dc973837b72f6651877a74da259c5e887ee4429d9f884fd318192223dacd2a4be7b7631b9863b42c987fe642649cc89166f67ba343e33b78510db1
|
|
7
|
+
data.tar.gz: 6ad73db79afdc62625d6a11ce94a3759cbfdbeb74151fb6c8f89c9ca0105f372ead7f95fa36afd010a9244979612e9693712ed7a35c12789f6f64b8881aff692
|
|
@@ -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,38 @@
|
|
|
1
|
+
name: release-provenance
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
# Keyless sigstore signing exchanges this token for a short-lived certificate.
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
provenance:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
ref: ${{ github.event.release.tag_name || github.ref }}
|
|
20
|
+
- uses: ruby/setup-ruby@v1
|
|
21
|
+
with:
|
|
22
|
+
ruby-version: "3.4"
|
|
23
|
+
bundler-cache: true
|
|
24
|
+
- uses: sigstore/cosign-installer@v3
|
|
25
|
+
- name: Build gem
|
|
26
|
+
run: gem build suma.gemspec
|
|
27
|
+
- name: Write, verify, and sign provenance
|
|
28
|
+
run: bundle exec ruby bin/release_provenance
|
|
29
|
+
env:
|
|
30
|
+
SUMA_PROVENANCE_SIGN: sigstore
|
|
31
|
+
- name: Independent sha256 pass
|
|
32
|
+
run: sha256sum *.gem provenance.json
|
|
33
|
+
- uses: actions/upload-artifact@v4
|
|
34
|
+
with:
|
|
35
|
+
name: release-provenance
|
|
36
|
+
path: |
|
|
37
|
+
provenance.json
|
|
38
|
+
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/Rakefile
CHANGED
|
@@ -9,4 +9,16 @@ require "rubocop/rake_task"
|
|
|
9
9
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
|
-
|
|
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
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Nightly scale benchmark: generates a synthetic EXPRESS corpus, then times
|
|
5
|
+
# the corpus-scale operations (parse/graph, closure, OWL projection) and
|
|
6
|
+
# asserts wall-time and peak-RSS budgets. Budgets are environment-tunable so
|
|
7
|
+
# CI machines can be calibrated without code changes.
|
|
8
|
+
|
|
9
|
+
require "suma"
|
|
10
|
+
require "tmpdir"
|
|
11
|
+
require "fileutils"
|
|
12
|
+
require "json"
|
|
13
|
+
|
|
14
|
+
module CorpusBenchmark
|
|
15
|
+
SCHEMA_COUNT = Integer(ENV.fetch("SUMA_BENCH_SCHEMAS", "1000"))
|
|
16
|
+
MAX_SECONDS = Float(ENV.fetch("SUMA_BENCH_MAX_SECONDS", "180"))
|
|
17
|
+
MAX_RSS_MB = Integer(ENV.fetch("SUMA_BENCH_MAX_RSS_MB", "2048"))
|
|
18
|
+
URN = "urn:example:benchmark:corpus:ed-1:en"
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# Half resources, half modules; each module USEs one resource so the graph
|
|
23
|
+
# has interface edges (dependents are exercised by the closure walk).
|
|
24
|
+
def generate(directory)
|
|
25
|
+
resources = SCHEMA_COUNT / 2
|
|
26
|
+
FileUtils.mkdir_p(File.join(directory, "resources"))
|
|
27
|
+
FileUtils.mkdir_p(File.join(directory, "modules"))
|
|
28
|
+
manifest = { "schemas" => {} }
|
|
29
|
+
|
|
30
|
+
resources.times do |index|
|
|
31
|
+
id = format("res_%04d", index)
|
|
32
|
+
write_schema(directory, "resources", id, resource_source(id, index))
|
|
33
|
+
manifest["schemas"][id] = { "path" => "resources/#{id}.exp" }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
(SCHEMA_COUNT - resources).times do |index|
|
|
37
|
+
id = format("mod_%04d", index)
|
|
38
|
+
resource = format("res_%04d", index)
|
|
39
|
+
write_schema(directory, "modules", id,
|
|
40
|
+
module_source(id, index, resource))
|
|
41
|
+
manifest["schemas"][id] = { "path" => "modules/#{id}.exp" }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
path = File.join(directory, "schemas.yaml")
|
|
45
|
+
File.write(path, YAML.dump(manifest))
|
|
46
|
+
path
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def resource_source(id, index)
|
|
50
|
+
<<~EXPRESS
|
|
51
|
+
SCHEMA #{id};
|
|
52
|
+
ENTITY thing_#{format('%04d', index)};
|
|
53
|
+
name : STRING;
|
|
54
|
+
weight : REAL;
|
|
55
|
+
END_ENTITY;
|
|
56
|
+
END_SCHEMA;
|
|
57
|
+
EXPRESS
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def module_source(id, index, resource)
|
|
61
|
+
<<~EXPRESS
|
|
62
|
+
SCHEMA #{id};
|
|
63
|
+
USE FROM #{resource};
|
|
64
|
+
ENTITY wrapper_#{format('%04d', index)};
|
|
65
|
+
item : thing_#{format('%04d', index)};
|
|
66
|
+
END_ENTITY;
|
|
67
|
+
END_SCHEMA;
|
|
68
|
+
EXPRESS
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def write_schema(directory, kind, id, source)
|
|
72
|
+
File.write(File.join(directory, kind, "#{id}.exp"), source)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def measure
|
|
76
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
77
|
+
value = yield
|
|
78
|
+
[value, Process.clock_gettime(Process::CLOCK_MONOTONIC) - started]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def peak_rss_mb
|
|
82
|
+
if File.exist?("/proc/self/status")
|
|
83
|
+
File.read("/proc/self/status")[/VmHWM:\s+(\d+)/, 1].to_i / 1024.0
|
|
84
|
+
else
|
|
85
|
+
`ps -o rss= -p #{Process.pid}`.to_i / 1024.0
|
|
86
|
+
end.round
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def run
|
|
90
|
+
Dir.mktmpdir("suma-benchmark") do |directory|
|
|
91
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
92
|
+
manifest = generate(directory)
|
|
93
|
+
|
|
94
|
+
graph, graph_seconds = measure { Suma::CorpusGraph.from_manifest(manifest) }
|
|
95
|
+
violations, closure_seconds = measure do
|
|
96
|
+
Suma::ClosureValidator.new(graph).validate_manifest(manifest)
|
|
97
|
+
end
|
|
98
|
+
projection, owl_seconds = measure do
|
|
99
|
+
Suma::OwlProjection.new(manifest, urn: URN)
|
|
100
|
+
end
|
|
101
|
+
turtle, turtle_seconds = measure { projection.to_turtle }
|
|
102
|
+
total = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
|
103
|
+
rss = peak_rss_mb
|
|
104
|
+
|
|
105
|
+
report = {
|
|
106
|
+
schemas: SCHEMA_COUNT,
|
|
107
|
+
graph_seconds: graph_seconds.round(2),
|
|
108
|
+
closure_seconds: closure_seconds.round(2),
|
|
109
|
+
owl_seconds: owl_seconds.round(2),
|
|
110
|
+
turtle_seconds: turtle_seconds.round(2),
|
|
111
|
+
total_seconds: total.round(2),
|
|
112
|
+
peak_rss_mb: rss,
|
|
113
|
+
closure_violations: violations.length,
|
|
114
|
+
turtle_bytes: turtle.bytesize,
|
|
115
|
+
budgets: { max_seconds: MAX_SECONDS, max_rss_mb: MAX_RSS_MB },
|
|
116
|
+
}
|
|
117
|
+
puts JSON.pretty_generate(report)
|
|
118
|
+
|
|
119
|
+
failures = []
|
|
120
|
+
unless violations.empty?
|
|
121
|
+
failures << "#{violations.length} closure violations"
|
|
122
|
+
end
|
|
123
|
+
if total > MAX_SECONDS
|
|
124
|
+
failures << "wall time #{total.round(2)}s > #{MAX_SECONDS}s"
|
|
125
|
+
end
|
|
126
|
+
if rss > MAX_RSS_MB
|
|
127
|
+
failures << "peak RSS #{rss}MB > #{MAX_RSS_MB}MB"
|
|
128
|
+
end
|
|
129
|
+
unless failures.empty?
|
|
130
|
+
warn "benchmark FAILED: #{failures.join('; ')}"
|
|
131
|
+
exit 1
|
|
132
|
+
end
|
|
133
|
+
puts "benchmark passed"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
CorpusBenchmark.run if $PROGRAM_NAME == __FILE__
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Emit provenance for a release build: the built gem's sha256 plus the
|
|
5
|
+
# dependency lockfile and toolchain versions. Run after `gem build`.
|
|
6
|
+
|
|
7
|
+
require "suma"
|
|
8
|
+
|
|
9
|
+
ROOT = File.expand_path("..", __dir__)
|
|
10
|
+
gem_file = Dir[File.join(ROOT, "*.gem")].max
|
|
11
|
+
abort "release_provenance: no built gem found in #{ROOT}" unless gem_file
|
|
12
|
+
|
|
13
|
+
manifest_path = File.join(Dir.pwd, "provenance.json")
|
|
14
|
+
Suma::Provenance.write(
|
|
15
|
+
manifest_path,
|
|
16
|
+
inputs: [
|
|
17
|
+
File.join(ROOT, "Gemfile.lock"),
|
|
18
|
+
File.join(ROOT, "suma.gemspec"),
|
|
19
|
+
],
|
|
20
|
+
outputs: [gem_file],
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
problems = Suma::Provenance.verify(manifest_path)
|
|
24
|
+
unless problems.empty?
|
|
25
|
+
warn "release_provenance: verification failed: #{problems.join('; ')}"
|
|
26
|
+
exit 1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
puts "Wrote #{manifest_path} (#{File.basename(gem_file)})"
|
|
30
|
+
|
|
31
|
+
sign_method = ENV.fetch("SUMA_PROVENANCE_SIGN", nil)
|
|
32
|
+
sign_method = nil if sign_method.to_s.empty?
|
|
33
|
+
if sign_method
|
|
34
|
+
sign_key = ENV.fetch("SUMA_PROVENANCE_SIGN_KEY", nil)
|
|
35
|
+
sign_key = nil if sign_key.to_s.empty?
|
|
36
|
+
signature = Suma::Provenance.sign(
|
|
37
|
+
manifest_path, method: sign_method, key: sign_key
|
|
38
|
+
)
|
|
39
|
+
puts "Signed provenance (#{sign_method}): #{signature}"
|
|
40
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "json"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
module Suma
|
|
8
|
+
# Decides which schemas an incremental build must re-render.
|
|
9
|
+
#
|
|
10
|
+
# The state is a map of schema id → sha256 of its source. A schema is
|
|
11
|
+
# changed when its digest differs from the previous build; rendering is
|
|
12
|
+
# required for every changed schema plus all transitive dependents, because
|
|
13
|
+
# a dependent's cross-references and rendered anchors may shift. The
|
|
14
|
+
# dependents come from the corpus graph (S1), the same USE/REFERENCE edges
|
|
15
|
+
# that drive change impact (S3).
|
|
16
|
+
class BuildPlan
|
|
17
|
+
# @param entries [Array<#id,#path>] schema manifest entries
|
|
18
|
+
# @param graph [CorpusGraph]
|
|
19
|
+
# @param previous_state [Hash{String=>String}] digest per schema id
|
|
20
|
+
def initialize(entries:, graph:, previous_state: {})
|
|
21
|
+
@entries = entries
|
|
22
|
+
@graph = graph
|
|
23
|
+
@previous_state = previous_state || {}
|
|
24
|
+
@ids_by_key = entries.to_h { |entry| [entry.id.downcase, entry.id] }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Array<String>] declared ids whose source differs since the
|
|
28
|
+
# previous build (all ids on a first build)
|
|
29
|
+
def changed
|
|
30
|
+
current_state.reject do |id, digest|
|
|
31
|
+
@previous_state[id] == digest
|
|
32
|
+
end.keys
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Array<String>] schemas to re-render: every changed schema plus
|
|
36
|
+
# its transitive dependents; empty when nothing changed
|
|
37
|
+
def rebuild
|
|
38
|
+
changed_ids = changed
|
|
39
|
+
return [] if changed_ids.empty?
|
|
40
|
+
|
|
41
|
+
expanded = changed_ids.flat_map do |id|
|
|
42
|
+
[id] + @graph.dependents_of(id).map { |key| @ids_by_key[key] || key }
|
|
43
|
+
end
|
|
44
|
+
expanded.uniq
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Hash{String=>String}] digest per schema id
|
|
48
|
+
def current_state
|
|
49
|
+
@current_state ||= @entries.to_h do |entry|
|
|
50
|
+
[entry.id, Digest::SHA256.file(entry.path.to_s).hexdigest]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Persist the current state for the next incremental build.
|
|
55
|
+
def self.write_state(path, state)
|
|
56
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
57
|
+
temp = "#{path}.#{Process.pid}.tmp"
|
|
58
|
+
File.write(temp, JSON.pretty_generate(state))
|
|
59
|
+
File.rename(temp, path)
|
|
60
|
+
ensure
|
|
61
|
+
File.delete(temp) if temp && File.exist?(temp)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Hash{String=>String}] the previous state, empty when absent
|
|
65
|
+
def self.read_state(path)
|
|
66
|
+
return {} unless path && File.exist?(path)
|
|
67
|
+
|
|
68
|
+
JSON.parse(File.read(path))
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|