suma 0.5.8 → 0.5.10

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release-provenance.yml +5 -2
  3. data/.rubocop_todo.yml +27 -0
  4. data/CLAUDE.md +17 -5
  5. data/README.adoc +237 -11
  6. data/lib/suma/anchor_validator.rb +85 -0
  7. data/lib/suma/artifact_server.rb +102 -0
  8. data/lib/suma/cli/artifact_server_cli.rb +22 -0
  9. data/lib/suma/cli/build.rb +4 -0
  10. data/lib/suma/cli/core.rb +50 -4
  11. data/lib/suma/cli/export.rb +1 -1
  12. data/lib/suma/cli/extract_terms.rb +1 -1
  13. data/lib/suma/cli/generate_register.rb +1 -1
  14. data/lib/suma/cli/ingest_translations.rb +36 -0
  15. data/lib/suma/cli/package_gcr.rb +51 -0
  16. data/lib/suma/cli/validate.rb +16 -0
  17. data/lib/suma/cli.rb +4 -1
  18. data/lib/suma/closure_validator.rb +124 -20
  19. data/lib/suma/corpus_graph.rb +79 -16
  20. data/lib/suma/gcr_package.rb +230 -0
  21. data/lib/suma/link_validation_result.rb +8 -0
  22. data/lib/suma/link_validator.rb +0 -3
  23. data/lib/suma/output_canonicalizer.rb +107 -0
  24. data/lib/suma/processor.rb +94 -20
  25. data/lib/suma/provenance.rb +92 -12
  26. data/lib/suma/schema_collection.rb +1 -0
  27. data/lib/suma/schema_compiler.rb +79 -13
  28. data/lib/suma/schema_manifest_generator.rb +10 -3
  29. data/lib/suma/schema_profile.rb +12 -0
  30. data/lib/suma/schema_template/anchors.rb +154 -0
  31. data/lib/suma/schema_template/document.rb +2 -93
  32. data/lib/suma/schema_template/plain.rb +10 -6
  33. data/lib/suma/schema_template.rb +1 -0
  34. data/lib/suma/site_build.rb +153 -0
  35. data/lib/suma/staged_collection_builder.rb +11 -3
  36. data/lib/suma/translation_ingester.rb +169 -0
  37. data/lib/suma/urn_resolver.rb +51 -1
  38. data/lib/suma/version.rb +1 -1
  39. data/lib/suma.rb +7 -0
  40. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3674c1699305e230eabb86ed343195cd0b6915c063b77b3b1fb540ce03a6e684
4
- data.tar.gz: 8f86f1e113c3774de773261c96cab1292222be7e5ae97e9b463a2136c40aefce
3
+ metadata.gz: 01e0b1dd212172912b654cac481e0ce1eea1efee1275f45d4332c0a6bbb28e69
4
+ data.tar.gz: 54399e492be26b0a09595debdc61c256f566da28ed13ca002cae2a6eb0925b57
5
5
  SHA512:
6
- metadata.gz: 60c730b7f8dc973837b72f6651877a74da259c5e887ee4429d9f884fd318192223dacd2a4be7b7631b9863b42c987fe642649cc89166f67ba343e33b78510db1
7
- data.tar.gz: 6ad73db79afdc62625d6a11ce94a3759cbfdbeb74151fb6c8f89c9ca0105f372ead7f95fa36afd010a9244979612e9693712ed7a35c12789f6f64b8881aff692
6
+ metadata.gz: 9fb0aebf85233af1df6162efb518605bd7ab01c7396e7af6012144fd05c74a135ba7d2833b7bb633fa45c755e067d6799569412df0b21458b33d2730a5081a3a
7
+ data.tar.gz: d9836497a01e7711aece5c76534ecfa4e7e0876bfd414aab783ada288b1906e07a3f3b6994d17085f294b79a827c16ecb61bab7cf305a823b9527933e5aa7f1c
@@ -1,8 +1,11 @@
1
1
  name: release-provenance
2
2
 
3
3
  on:
4
- release:
5
- types: [published]
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*"
6
9
  workflow_dispatch:
7
10
 
8
11
  permissions:
data/.rubocop_todo.yml CHANGED
@@ -116,9 +116,11 @@ Lint/UnusedMethodArgument:
116
116
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
117
117
  Metrics/AbcSize:
118
118
  Exclude:
119
+ - 'lib/suma/gcr_package.rb'
119
120
  - 'bin/benchmark_corpus'
120
121
  - 'lib/suma/worker_pool.rb'
121
122
  - 'spec/suma/reproducibility_spec.rb'
123
+ - 'spec/suma/cli/build_integration_spec.rb'
122
124
  - 'lib/suma/cli/resolve.rb'
123
125
  - 'lib/suma/urn_resolver.rb'
124
126
  - 'lib/suma/cli/check_svg_quality.rb'
@@ -155,6 +157,8 @@ Metrics/BlockLength:
155
157
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
156
158
  Metrics/CyclomaticComplexity:
157
159
  Exclude:
160
+ - 'lib/suma/gcr_package.rb'
161
+ - 'lib/suma/corpus_graph.rb'
158
162
  - 'lib/suma/worker_pool.rb'
159
163
  - 'lib/suma/urn_resolver.rb'
160
164
  - 'lib/suma/cli/check_svg_quality.rb'
@@ -172,6 +176,8 @@ Metrics/CyclomaticComplexity:
172
176
  # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
173
177
  Metrics/MethodLength:
174
178
  Exclude:
179
+ - 'lib/suma/artifact_server.rb'
180
+ - 'lib/suma/gcr_package.rb'
175
181
  - 'bin/benchmark_corpus'
176
182
  - 'lib/suma/cli/build.rb'
177
183
  - 'lib/suma/worker_pool.rb'
@@ -204,6 +210,7 @@ Metrics/MethodLength:
204
210
  # Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters.
205
211
  Metrics/ParameterLists:
206
212
  Exclude:
213
+ - 'lib/suma/gcr_package.rb'
207
214
  - 'lib/suma/processor.rb'
208
215
  - 'lib/suma/express_schema.rb'
209
216
  - 'lib/suma/term_extractor.rb'
@@ -216,6 +223,7 @@ Metrics/ParameterLists:
216
223
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
217
224
  Metrics/PerceivedComplexity:
218
225
  Exclude:
226
+ - 'lib/suma/gcr_package.rb'
219
227
  - 'lib/suma/worker_pool.rb'
220
228
  - 'lib/suma/eengine/wrapper.rb'
221
229
  - 'lib/suma/jsdai/figure_image.rb'
@@ -259,6 +267,16 @@ RSpec/ContextWording:
259
267
  # Configuration parameters: Max, CountAsOne.
260
268
  RSpec/ExampleLength:
261
269
  Exclude:
270
+ - 'spec/suma/site_build_spec.rb'
271
+ - 'spec/suma/output_canonicalizer_spec.rb'
272
+ - 'spec/suma/anchor_validator_spec.rb'
273
+ - 'spec/suma/translation_ingester_spec.rb'
274
+ - 'spec/suma/ifc_profile_pipeline_spec.rb'
275
+ - 'spec/suma/schema_manifest_generator_spec.rb'
276
+ - 'spec/suma/schema_naming_spec.rb'
277
+ - 'spec/suma/cli/build_integration_spec.rb'
278
+ - 'spec/suma/artifact_server_spec.rb'
279
+ - 'spec/suma/gcr_package_spec.rb'
262
280
  - 'spec/suma/schema_cache_spec.rb'
263
281
  - 'spec/suma/worker_pool_spec.rb'
264
282
  - 'spec/suma/schema_collection_rebuild_targets_spec.rb'
@@ -338,6 +356,11 @@ RSpec/MessageSpies:
338
356
  # Configuration parameters: Max.
339
357
  RSpec/MultipleExpectations:
340
358
  Exclude:
359
+ - 'spec/suma/site_build_spec.rb'
360
+ - 'spec/suma/output_canonicalizer_spec.rb'
361
+ - 'spec/suma/cli/build_integration_spec.rb'
362
+ - 'spec/suma/artifact_server_spec.rb'
363
+ - 'spec/suma/gcr_package_spec.rb'
341
364
  - 'spec/suma/schema_cache_spec.rb'
342
365
  - 'spec/suma/worker_pool_spec.rb'
343
366
  - 'spec/suma/reproducibility_spec.rb'
@@ -400,11 +423,13 @@ RSpec/NestedGroups:
400
423
  RSpec/DescribeClass:
401
424
  Exclude:
402
425
  - 'spec/suma/reproducibility_spec.rb'
426
+ - 'spec/suma/ifc_profile_pipeline_spec.rb'
403
427
 
404
428
  RSpec/SpecFilePathFormat:
405
429
  Exclude:
406
430
  - '**/spec/routing/**/*'
407
431
  - 'spec/suma/cli/expressir_spec.rb'
432
+ - 'spec/suma/ifc_profile_pipeline_spec.rb'
408
433
 
409
434
  # Offense count: 1
410
435
  RSpec/StubbedMock:
@@ -415,6 +440,8 @@ RSpec/StubbedMock:
415
440
  # Configuration parameters: Max.
416
441
  RSpec/MultipleMemoizedHelpers:
417
442
  Exclude:
443
+ - 'spec/suma/ifc_profile_pipeline_spec.rb'
444
+ - 'spec/suma/gcr_package_spec.rb'
418
445
  - 'spec/suma/provenance_spec.rb'
419
446
  - 'spec/suma/worker_pool_spec.rb'
420
447
  - 'spec/suma/schema_cache_spec.rb'
data/CLAUDE.md CHANGED
@@ -37,11 +37,15 @@ exe/suma reformat PATH # Reformat EXP files (us
37
37
  exe/suma generate-schemas METANORMA_YAML SCHEMAS_YAML # Generate schema manifest from site manifest
38
38
  exe/suma extract-terms SCHEMA_YAML GLOSSARIST_DIR -u URN # Extract EXPRESS entity concepts to Glossarist v3 format
39
39
  exe/suma extract-terms ... --edition 3 --language-codes fra deu # Provenance + linked localizations
40
+ exe/suma ingest-translations CONCEPTS_DIR TRANSLATIONS_FILE [-o OUT] # Overlay translated localized-concept content onto extracted concepts
40
41
  exe/suma resolve URN --manifest SCHEMAS_YAML --concepts DIR --register register.yaml -o OUT # Emit canonical JSON + HTML
41
42
  exe/suma resolve --all --manifest SCHEMAS_YAML --concepts DIR --register register.yaml -o OUT # Emit every URN artifact
43
+ exe/suma package-gcr CONCEPTS_DIR --register register.yaml --shortname ID --version V --title T --description D -o OUT # Package concepts into a .gcr archive
44
+ exe/suma serve ARTIFACTS_DIR --port 2113 # Serve resolved URN artifacts with Accept negotiation
42
45
  exe/suma generate-register SCHEMA_YAML OUT_DIR -u URN --id ID --ref REF # Generate hierarchical register.yaml from schema manifest
43
46
  exe/suma validate links SCHEMAS_FILE DOCS_PATH # Validate EXPRESS cross-reference links
44
47
  exe/suma validate closure SCHEMAS_MANIFEST [DOCUMENTS_PATH] # Referential closure: every reference resolves (exit 1 on violations)
48
+ exe/suma validate anchors SCHEMA_DOCS_PATH # Rendered docs: every xref and in-page link resolves (exit 1 on dead links)
45
49
  ```
46
50
 
47
51
  ## Schema profiles
@@ -51,8 +55,10 @@ identifiers and paths map to types (`ExpressSchema::Type`), how
51
55
  identifiers become display names (`SchemaNaming`), and the ordered
52
56
  register categories (`SchemaCategory`). Registered profiles: `iso10303`
53
57
  (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`.
58
+ protocol), `generic` (no STEP conventions), and `ifc` (buildingSMART
59
+ IFC editions: standalone classification, `IFC4`-style edition names via
60
+ `/\Aifc\d+(x\d+)*\z/`). The CLI takes `--profile` on `export`,
61
+ `extract-terms`, and `generate-register`.
56
62
 
57
63
  ## ISO 10303 AP publications
58
64
 
@@ -81,10 +87,12 @@ per-publication parent section.
81
87
  - **`SchemaCategory`** — value object mapping `ExpressSchema::Type` to a register/export category (id, label, prefix, types, directory). Single source of truth for category identity across `SchemaExporter`, `SchemaNaming`, and `RegisterManifestGenerator`
82
88
  - **`Urn`** — value object encapsulating ISO URN semantics (wildcard stripping, base/alias split, leaf composition for `for_schema`/`for_term`/`for_entity`)
83
89
  - **`TermExtractor`** — extracts EXPRESS entity concepts from a schema manifest into Glossarist v3 YAML format. Generates definitions with URN cross-references, processes remarks into notes, and resolves EXPRESS xrefs to URN mentions. Assigns stable UUIDv5 identifiers
90
+ - **`TranslationIngester`** — T5 translation ingestion: overlays a YAML sequence of localized-concept entries (keyed by `concept_id` or localized UUID) onto extracted concepts through the Glossarist/lutaml-model framework. Entry fields win; omitted fields keep the source-language content; a new language is created with the same UUIDv5 convention as the linked localizations, and the persisted localization "id" is preserved (Glossarist parses it as `#id`, not `#uuid`)
84
91
  - **`RegisterManifestGenerator`** (file: `lib/suma/register_manifest_generator.rb`) — generates a Glossarist v3 `register.yaml` with hierarchical sections from a schema manifest. Classifies schemas via `ExpressSchema::Type` (DRY), delegates naming to `SchemaNaming` (OCP), orders resources before modules. Uses the Section model's `children` field for hierarchy
85
92
  - **`SchemaNaming`** — pure module that converts EXPRESS schema IDs to human-readable display names. Strips type suffixes (`_schema`/`_arm`/`_mim`/`_bom`), title-cases with acronym preservation (AIC, AEC, BREP, 2D, 3D…), lowercases function words, appends type labels (ARM/MIM)
86
- - **`SchemaTemplate::Plain` / `SchemaTemplate::Document`** — pure renderers for the AsciiDoc body fed to Metanorma (no I/O, no schema knowledge). `Document` adds cross-reference anchors and produces XML-only output
87
- - **`SchemaCompiler`** — orchestrates one schema's compilation: writes adoc + config, invokes `Metanorma::Compile`. Templates are injected via the constructor (composition over inheritance)
93
+ - **`SchemaTemplate::Plain` / `SchemaTemplate::Document`** — pure renderers for the AsciiDoc body fed to Metanorma (no I/O, no schema knowledge). Both render the schema through `formatted_hyperlinked_adoc` (live cross-references) under the shared `Anchors` block, which emits one `[[id]]` per linkable element — schema-qualified ids plus bare `entity.attribute` pairs, matching Expressir's two xref forms. `Plain` produces HTML+XML for top-level schemas; `Document` produces XML-only for schemas-only entries. `validate anchors` verifies zero dead links in rendered docs
94
+ - **`AnchorValidator`** — T4 dead-link gate: for every rendered `doc_*.adoc` + `doc_*.html`, each `<<target,...>>` xref must have an anchor in the HTML (unresolved xrefs are silently dropped by the renderer) and every in-page `href="#..."` must hit an id; cross-document xrefs are out of scope. `validate anchors SCHEMA_DOCS_PATH` exits 1 on violations
95
+ - **`SchemaCompiler`** — orchestrates one schema's compilation: writes adoc + config, invokes `Metanorma::Compile`. Templates are injected via the constructor (composition over inheritance). A render key over adoc, config, schema source, and `Provenance.render_toolchain` is stored in `.suma-render-key` beside the outputs; an unchanged key with the requested outputs present reuses them without invoking Metanorma
88
96
  - **`SchemaCollection`** — orchestrates processing of all schemas from a config; selects `Plain` for top-level schemas and `Document` for schemas-only entries
89
97
  - **`SchemaExporter`** — standalone export of schemas from manifest or `.exp` files to a directory, with optional ZIP
90
98
  - **`CollectionManifest`** — pure data model (lutaml-model attributes + YAML mappings) for one node of a Metanorma collection manifest. No imperative methods
@@ -97,9 +105,13 @@ per-publication parent section.
97
105
  - **`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
106
  - **`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
107
  - **`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
108
+ - **`SiteBuild`** — T3 site freshness: keys every source file under the site root (excluding generated trees, render copies, and the Relaton cache) plus the render toolchain, records the key and produced files in `_site/.suma-site-build.json`, and lets `Processor` skip the collection render when nothing changed. Generated per-document artifacts (`document*.xml*`, `*_index.html`, `*.err.html`, `*.log.txt`, temp files) are never treated as inputs
109
+ - **`OutputCanonicalizer`** — T3 determinism: Metanorma assigns random UUIDs to manifest nodes without identifiers and stamps rendered documents with a random 8-letter root type. This pass rewrites those to position-derived UUIDv5s and a fixed marker in the emitted manifest and root XML, while preserving UUIDs that occur in source YAML, so identical builds are byte-identical
100
110
  - **`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
111
  - **`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`)
112
+ - **`ArtifactServer`** — T2 development server for the resolved artifact tree (`serve DIRECTORY`): dependency-free TCP responder negotiating JSON vs HTML by the Accept header at URN-derived paths; production hosting maps the same paths statically
113
+ - **`GcrPackage`** — T2 packaging (`package-gcr`): bundles extracted concepts into a GCR archive for the Glossarist Vocabulary Browser — `metadata.yaml` (identity, statistics, schema_version), `register.yaml`, and `concepts/` in the browser's canonical flat format (`termid`, `term`, `{lang}` blocks). Standard variant only; `created_at` is injectable for byte-reproducible archives
114
+ - **`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 and reports unrecorded files under the recorded output roots (`_site`, `schema_docs`, `plain_schemas`); explicit exclusions cover build metadata such as the site state file and staged store. `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`). `Provenance.render_toolchain` keys rendered-document reuse
103
115
  - **`LinkValidator`** — validates EXPRESS cross-reference links against a `SchemaIndex`; returns `LinkValidationResult` structs for unresolved links
104
116
 
105
117
  ### CLI structure
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,19 @@ $ 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 ingest-translations CONCEPTS_DIR TRANSLATIONS_FILE [-o OUT] # Overlay translated content onto extracted concepts
53
+ suma generate-register SCHEMA_MANIFEST_FILE OUTPUT_PATH # Generate hierarchical register.yaml
54
+ suma generate-schemas METANORMA_MANIFEST_FILE SCHEMA_MANIFEST_FILE # Generate a schema manifest from a site manifest
46
55
  suma reformat EXPRESS_FILE_PATH # Reformat EXPRESS files
56
+ suma resolve [URN] --manifest=MANIFEST -o OUTPUT # Emit canonical JSON + HTML for URNs
57
+ suma package-gcr CONCEPTS_DIR --register=REGISTER -o OUTPUT # Package concepts into a .gcr archive
58
+ suma serve DIRECTORY # Serve resolved artifacts (Accept negotiation)
47
59
  suma validate SUBCOMMAND ...ARGS # Validate express documents
48
60
  ----
49
61
 
@@ -65,10 +77,31 @@ Options:
65
77
 
66
78
  `--[no-]compile`:: Compile or skip compilation of collection (default: true)
67
79
  `--schemas-all-path=PATH`, `-s PATH`:: Generate file that contains all schemas in the collection
80
+ `--[no-]staged`:: Memory-bounded build: compile each member in its own process, then reinflate
81
+ `--[no-]incremental`:: Re-render only changed schemas and their transitive dependents
82
+ `--state-file=PATH`:: Incremental build state file (default: `plain_schemas/.suma-build-state.json`)
83
+ `--jobs=N`:: Concurrent schema export workers (document compilation stays sequential; default: 1)
84
+ `--provenance=PATH`:: Write a provenance manifest (sha256 of inputs/outputs + toolchain versions)
85
+ `--sign=METHOD`:: Sign the provenance manifest: `minisign` or `sigstore`
86
+ `--sign-key=PATH`:: Signing key; minisign requires it, sigstore signs keylessly when omitted
68
87
 
69
88
  The generated `schemas-*.yaml` file name is derived from the input file name
70
89
  with the word `metanorma` replaced with `schemas`.
71
90
 
91
+ Repeated builds reuse work and are reproducible: a schema document is rendered
92
+ again only when its adoc, schema config, schema source, or the rendering
93
+ toolchain changed, and the collection render is skipped entirely when no site
94
+ input changed (state: `_site/.suma-site-build.json`). Metanorma's random
95
+ manifest identifiers and per-document type markers are canonicalized, so two
96
+ identical builds produce byte-identical output. A provenance manifest covers
97
+ `schema_docs`, `plain_schemas`, and `_site`, and verification reports files
98
+ that appeared under those roots without being recorded (build metadata such as
99
+ the site state file is excluded explicitly).
100
+
101
+ Top-level schema documents render with live cross-reference links: every
102
+ `schema.entity` and `entity.attribute` reference Expressir emits becomes an
103
+ in-page link, and `suma validate anchors` verifies that no link is dead.
104
+
72
105
  [example]
73
106
  ====
74
107
  .To compile SRL subset test collection
@@ -141,6 +174,7 @@ $ suma validate SUBCOMMAND [options]
141
174
  Subcommands:
142
175
 
143
176
  `links`:: Validate EXPRESS links
177
+ `closure`:: Validate referential closure over the whole corpus
144
178
 
145
179
  ==== Links subcommand
146
180
 
@@ -176,6 +210,36 @@ This command:
176
210
  * Writes validation results to the `OUTPUT_FILE`
177
211
  * Provides progress bars to track schema loading and link validation
178
212
 
213
+ ==== Closure subcommand
214
+
215
+ The `closure` subcommand extracts every EXPRESS-internal reference (attribute
216
+ domains, SELECT and aggregate branches, where-rule expressions, subtype
217
+ constraints, supertype/subtype lists, rule applies-to, and USE/REFERENCE
218
+ interfaces) and resolves it against the corpus graph. It exits non-zero when
219
+ any reference does not resolve, which makes it usable as a CI gate
220
+ (`rake closure` runs it on the fixture corpus).
221
+
222
+ [source,sh]
223
+ ----
224
+ $ suma validate closure SCHEMAS_MANIFEST [DOCUMENTS_PATH] [--format terminal|json|yaml]
225
+ ----
226
+
227
+ When `DOCUMENTS_PATH` is given, documentation-layer link failures are merged
228
+ into the same report.
229
+
230
+ ==== Anchors subcommand
231
+
232
+ The `anchors` subcommand checks rendered schema documents for dead links:
233
+ every Expressir cross-reference (`<<schema.entity,...>>`,
234
+ `<<entity.attribute,...>>`) emitted in a compiled `doc_*.adoc` must have a
235
+ matching anchor in its rendered `doc_*.html`, and every in-page `href` must
236
+ point at an existing id. It exits non-zero on violations.
237
+
238
+ [source,sh]
239
+ ----
240
+ $ suma validate anchors SCHEMA_DOCS_PATH
241
+ ----
242
+
179
243
 
180
244
  === Generate schemas command
181
245
 
@@ -225,7 +289,7 @@ used for other EXPRESS schema collections that require term extraction for
225
289
  glossary or dictionary applications.
226
290
 
227
291
  The `suma extract-terms` command extracts terms from EXPRESS schemas and
228
- generates a Glossarist v2 dataset in the output directory. This command processes
292
+ generates a Glossarist v3 dataset in the output directory. This command processes
229
293
  various types of STEP schemas and creates standardized terminology datasets
230
294
  suitable for glossary and dictionary applications.
231
295
 
@@ -239,12 +303,14 @@ Parameters:
239
303
  `SCHEMA_MANIFEST_FILE`:: Path to the schema manifest file that lists all schemas
240
304
  to process (e.g., "schemas-smrl-all.yml")
241
305
 
242
- `GLOSSARIST_OUTPUT_PATH`:: Path to the output directory where the Glossarist v2
306
+ `GLOSSARIST_OUTPUT_PATH`:: Path to the output directory where the Glossarist v3
243
307
  dataset will be generated
244
308
 
245
309
  Options:
246
310
 
247
311
  `--language_code`, `-l`:: Language code for the Glossarist dataset (default: "eng")
312
+ `--edition=N`:: Owning edition recorded on each concept
313
+ `--language-codes=a,b`:: Additional linked localization languages (the primary language is always included)
248
314
 
249
315
  ==== Supported schema types
250
316
 
@@ -275,7 +341,7 @@ information:
275
341
 
276
342
  ==== Output format
277
343
 
278
- The command generates a Glossarist v2 compliant dataset with:
344
+ The command generates a Glossarist v3 compliant dataset with:
279
345
 
280
346
  * `concept/` directory containing concept definition files in YAML
281
347
  * `localized_concept/` directory containing localized concept files in YAML
@@ -301,6 +367,37 @@ $ bundle exec suma extract-terms schemas-activity-modules.yml terms_output
301
367
  ----
302
368
  ====
303
369
 
370
+ === Ingest translations command
371
+
372
+ The `suma ingest-translations` command overlays translated content onto
373
+ extracted concepts. A translations file is a YAML sequence of
374
+ localized-concept documents, each keyed by concept id (or localized
375
+ UUID) and carrying the fields to apply:
376
+
377
+ [source,yaml]
378
+ ----
379
+ - concept_id: example_schema.part
380
+ data:
381
+ language_code: fra
382
+ terms:
383
+ - designation: pièce
384
+ type: expression
385
+ normative_status: preferred
386
+ definition:
387
+ - content: "Composant d'un assemblage."
388
+ ----
389
+
390
+ Every field the entry provides replaces that localization's field; every
391
+ field it omits keeps the source-language content (a language absent from
392
+ the extracted concepts is created with the same stable UUID convention
393
+ the linked localizations use). Updated concepts are written in place, or
394
+ to `--output` when given.
395
+
396
+ [source,sh]
397
+ ----
398
+ $ suma ingest-translations CONCEPTS_DIR TRANSLATIONS_FILE [-o OUTPUT_DIR]
399
+ ----
400
+
304
401
 
305
402
  === Generate register command
306
403
 
@@ -326,6 +423,13 @@ Options:
326
423
  The wildcard is stripped for the base URN and kept in `urnAliases`.
327
424
  *Required*.
328
425
 
426
+ `--year=N`:: Edition year recorded on the register
427
+
428
+ `--supersedes=URN`:: URN/ref of the superseded edition
429
+
430
+ `--edition=N`:: Edition number, tagged as `edition:N`; every register also
431
+ carries a `dataset:<id>` tag so sibling editions can be grouped.
432
+
329
433
  The URN is fully configurable — every component can be changed to match the
330
434
  dataset being exported. The general structure is:
331
435
 
@@ -521,6 +625,11 @@ Options:
521
625
  `--[no-]annotations`:: Include annotations (remarks/comments) in exported schemas (default: false)
522
626
 
523
627
  `--[no-]zip`:: Create ZIP archive of exported schemas (default: false)
628
+ `--profile=PROFILE`:: Schema profile: `iso10303` (default) or `generic`
629
+ `--publication=SCHEMA_ID`:: Export an AP publication bundle: the lead schema plus its transitive USE-FROM/REFERENCE closure
630
+ `--format=FORMAT`:: Output format: `express` (default) or `owl`
631
+ `--[no-]corpus`:: OWL only: emit one corpus ontology importing every schema ontology
632
+ `--urn=URN`:: OWL only: base URN for IRIs, before the `:tech` component
524
633
 
525
634
  ==== Behavior
526
635
 
@@ -639,6 +748,105 @@ express-files/
639
748
  ----
640
749
 
641
750
 
751
+ === Schema profiles and AP publications
752
+
753
+ Suma works with any schema collection. `Suma::SchemaProfile` declares how
754
+ schema identifiers and paths map to types, how identifiers become display
755
+ names, and the ordered register categories. Registered profiles:
756
+
757
+ `iso10303`:: Default; STEP conventions including `/applications/` classifying as an application protocol
758
+ `generic`:: No STEP conventions (one category for every schema)
759
+ `ifc`:: buildingSMART IFC editions: one standalone schema per edition (`ifc4`, `ifc4x3`, ...), edition names preserved uppercase (`IFC4`, `IFC4X3`), Ifc-prefixed declarations, one `Other Schemas` register category
760
+
761
+ An AP publication is a lead schema bundled with the module and resource
762
+ schemas it USEs. `--publication SCHEMA_ID` computes the transitive
763
+ USE-FROM/REFERENCE closure and writes the bundle flat under
764
+ `OUTPUT/<lead_id>/`. In the register, application protocols are grouped under
765
+ a per-publication parent section.
766
+
767
+ === OWL projection
768
+
769
+ `suma export --format owl` projects a parsed corpus into OWL/Turtle:
770
+
771
+ * entities become `owl:Class`; `SUBTYPE OF` becomes `rdfs:subClassOf`
772
+ * SELECT types become `owl:unionOf`; enumerations become `owl:oneOf` named individuals
773
+ * simple type aliases become `rdfs:Datatype`
774
+ * explicit, derived, and inverse attributes become object/datatype properties attached through named `owl:Restriction` nodes, with cardinality from optionality and aggregate bounds
775
+ * WHERE, DERIVE, and INVERSE clauses are preserved as annotated constraints
776
+
777
+ IRIs derive from the corpus URN scheme (`<base>:tech:<schema>.<element>`).
778
+ Per-schema ontologies declare `owl:imports` for their USE-FROM/REFERENCE
779
+ targets, and `--corpus` adds a corpus ontology importing every schema. The
780
+ projection is validated before writing (materialization, property
781
+ declarations, orphan classes, dangling superclasses, superclass cycles), and
782
+ contains no blank nodes, so it is deterministic and golden-comparable.
783
+
784
+ [source,sh]
785
+ ----
786
+ $ suma export --format owl --urn urn:iso:std:iso:10303:-2:ed-2:en \
787
+ -o OUTPUT schemas.yaml
788
+ $ suma export --format owl --corpus --urn urn:iso:std:iso:10303:-2:ed-2:en \
789
+ -o OUTPUT schemas.yaml
790
+ ----
791
+
792
+ === Package concepts for the Vocabulary Browser
793
+
794
+ The `package-gcr` command bundles extracted concepts into a GCR
795
+ (Glossarist Concept Repository) archive — the sealed distribution format
796
+ consumed by the Glossarist Vocabulary Browser:
797
+
798
+ [source,sh]
799
+ ----
800
+ $ suma package-gcr CONCEPTS_DIR --register register.yaml \
801
+ --shortname my-dataset --version 1.0.0 \
802
+ --title "My Dataset" --description "Concepts from my corpus" \
803
+ -o OUT
804
+ ----
805
+
806
+ The archive contains `metadata.yaml` (identity, statistics), the original
807
+ `register.yaml`, and `concepts/` in the browser's canonical flat format
808
+ (`termid`, `term`, one block per language). Both a versioned
809
+ (`{shortname}-{version}.gcr`) and an unversioned (`{shortname}.gcr`)
810
+ archive are written. Pass `--created-at` to pin the package timestamp for
811
+ byte-reproducible archives. Point the browser's `datasets.yml` at the
812
+ published archive (or at the concepts directory in development via
813
+ `localPath`).
814
+
815
+ === Serve resolved artifacts
816
+
817
+ `suma serve` serves a `resolve --all` artifact tree at its URN-derived
818
+ paths, returning JSON or HTML according to the request's `Accept`
819
+ header:
820
+
821
+ [source,sh]
822
+ ----
823
+ $ suma serve OUT --port 2113
824
+ $ curl -H "Accept: application/json" \
825
+ http://localhost:2113/urn/iso/std/iso/10303/-2/ed-2/en/tech/action_schema.action
826
+ ----
827
+
828
+ Production hosting maps the same paths statically; no application server
829
+ is required.
830
+
831
+ === Resolve URNs command
832
+
833
+ The `resolve` command turns URNs into static, dereferenceable artifacts: a
834
+ canonical JSON document (sorted keys) and an HTML page per URN, written under
835
+ a path derived from the URN itself. It resolves:
836
+
837
+ * `tech:` schema, declared-element, and attribute (OWL property) URNs from a schema manifest
838
+ * `term:` concept URNs from Glossarist concept documents
839
+ * the dataset URN from `register.yaml`
840
+
841
+ [source,sh]
842
+ ----
843
+ $ suma resolve URN --manifest SCHEMAS_YAML [--concepts DIR] [--register register.yaml] -o OUT
844
+ $ suma resolve --all --manifest SCHEMAS_YAML [--concepts DIR] [--register register.yaml] -o OUT
845
+ ----
846
+
847
+ `--all` also writes `index.html`, linking every artifact. No server is
848
+ required.
849
+
642
850
  === Compare schemas command
643
851
 
644
852
  ==== General
@@ -667,6 +875,10 @@ Options:
667
875
  `--trial-stepmod=PATH`:: Override auto-detected trial repository root
668
876
  `--reference-stepmod=PATH`:: Override auto-detected reference repository root
669
877
  `--verbose`:: Enable verbose output
878
+ `--typed-output=PATH`:: Write a typed change manifest (lutaml-model records with classification)
879
+ `--[no-]changelog`:: Render a markdown changelog alongside the change manifest
880
+ `--[no-]impact`:: Report affected schemas and AP publications using the corpus graph
881
+ `--manifest=SCHEMAS_YAML`:: Schema manifest used for impact analysis
670
882
 
671
883
  ==== Typical workflow
672
884
 
@@ -1163,3 +1375,17 @@ Code-style rules enforced throughout `lib/suma/`:
1163
1375
  == Copyright and license
1164
1376
 
1165
1377
  Copyright Ribose. BSD 2-clause license.
1378
+
1379
+ == Development and benchmarks
1380
+
1381
+ The default gate is `rake` (specs, RuboCop, referential closure).
1382
+
1383
+ [source,sh]
1384
+ ----
1385
+ $ rake benchmark
1386
+ ----
1387
+
1388
+ `rake benchmark` builds a synthetic 1,000-schema corpus and asserts wall-time
1389
+ and peak-RSS budgets; the same benchmark runs nightly in CI. Schema export can
1390
+ run in parallel with `build --jobs N`, while Metanorma document compilation
1391
+ stays sequential and composes with staged process isolation.
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Suma
4
+ # Validates that every cross-reference a rendered schema document emits
5
+ # resolves inside it. The Expressir hyperlinked source emits xref macros
6
+ # (`<<schema.entity,...>>`, `<<entity.attribute,...>>`); when a target is
7
+ # missing, the renderer silently drops the link, so the check is done on
8
+ # the source macros against the anchors present in the rendered HTML —
9
+ # plus the reverse check that every in-page href hits an existing id.
10
+ class AnchorValidator
11
+ XREF_PATTERN = /<<([^,>]+)(?:,[^>]*)?>>/
12
+ ID_PATTERN = /id="([^"]+)"/
13
+ FRAGMENT_PATTERN = /href="#([^"]+)"/
14
+
15
+ # @param directory [String] tree of compiled schema docs (doc_*.adoc
16
+ # with their rendered doc_*.html)
17
+ def initialize(directory)
18
+ @directory = directory
19
+ end
20
+
21
+ # @return [Array<LinkValidationResult>] dead links (empty = valid)
22
+ def validate
23
+ Dir.glob(File.join(@directory, "**", "doc_*.adoc"))
24
+ .flat_map { |adoc| validate_document(adoc) }
25
+ end
26
+
27
+ private
28
+
29
+ def validate_document(adoc)
30
+ html = adoc.sub(/\.adoc\z/, ".html")
31
+ return [missing_html(adoc, html)] unless File.file?(html)
32
+
33
+ content = File.read(adoc)
34
+ ids, fragments = rendered_digests(html)
35
+
36
+ xref_violations(adoc, content, ids, fragments) +
37
+ fragment_violations(html, ids)
38
+ end
39
+
40
+ def rendered_digests(html)
41
+ rendered = File.read(html)
42
+ [rendered.scan(ID_PATTERN).flatten.uniq,
43
+ rendered.scan(FRAGMENT_PATTERN).flatten.uniq]
44
+ end
45
+
46
+ def xref_violations(adoc, content, ids, fragments)
47
+ content.scan(XREF_PATTERN).flatten.uniq.filter_map do |target|
48
+ next if cross_document?(target) || resolved?(target, ids, fragments)
49
+
50
+ LinkValidationResult.new(
51
+ file: adoc, line: nil, link: target,
52
+ reason: "cross-reference has no anchor in the rendered document"
53
+ )
54
+ end
55
+ end
56
+
57
+ def fragment_violations(html, ids)
58
+ File.read(html).scan(FRAGMENT_PATTERN).flatten.uniq.filter_map do |frag|
59
+ next if ids.include?(frag)
60
+
61
+ LinkValidationResult.new(
62
+ file: html, line: nil, link: "##{frag}",
63
+ reason: "in-page link points at no anchor in the document"
64
+ )
65
+ end
66
+ end
67
+
68
+ # Xrefs that cross documents (Asciidoctor `path.adoc#anchor` syntax)
69
+ # resolve against other pages and are out of scope here.
70
+ def cross_document?(target)
71
+ target.include?("/") || target.include?("#")
72
+ end
73
+
74
+ def resolved?(target, ids, fragments)
75
+ ids.include?(target) || fragments.include?(target)
76
+ end
77
+
78
+ def missing_html(adoc, html)
79
+ LinkValidationResult.new(
80
+ file: adoc, line: nil, link: html,
81
+ reason: "no rendered HTML next to the adoc source"
82
+ )
83
+ end
84
+ end
85
+ end