edoxen 0.1.2 → 0.3.1

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.
data/README.adoc CHANGED
@@ -1,547 +1,202 @@
1
1
  = Edoxen
2
2
 
3
3
  https://github.com/metanorma/edoxen[image:https://img.shields.io/github/stars/metanorma/edoxen.svg?style=social[GitHub Stars]]
4
- https://github.com/metanorma/edoxen[image:https://img.shields.io/github/forks/metanorma/edoxen.svg?style=social[GitHub Forks]]
5
4
  image:https://img.shields.io/github/license/metanorma/edoxen.svg[License]
6
5
  image:https://img.shields.io/github/actions/workflow/status/metanorma/edoxen/test.yml?branch=main[Build Status]
7
6
  image:https://img.shields.io/gem/v/edoxen.svg[RubyGems Version]
8
7
 
9
8
  == Purpose
10
9
 
11
- Edoxen is a set of information models used for representing resolution and
12
- decision information. They are designed to provide a structured way to model
13
- formal resolutions, including their metadata, actions, considerations, and
14
- approvals.
10
+ Edoxen is a Ruby library for the canonical Edoxen information model of
11
+ formal resolutions and decisions. It is built on top of the
12
+ https://github.com/lutaml/lutaml[ lutaml-model] serialization framework.
15
13
 
16
- The `edoxen` gem provides a Ruby library for working with these models,
17
- allowing users to create, manipulate, and serialize resolution data in a
18
- structured format. It is built on top of the `lutaml-model` serialization
19
- framework, which provides a flexible and extensible way to define data models
20
- and serialize them to YAML or JSON formats.
14
+ The information model is defined in
15
+ https://github.com/metanorma/edoxen-model/tree/main/models[ LutaML UML
16
+ files] (one `.lutaml` per concept). This gem mirrors that model exactly —
17
+ attribute declarations, enum values, and field shapes so that anything
18
+ you can express in LutaML you can also construct, serialize, and validate
19
+ in Ruby.
21
20
 
22
- This library is particularly useful for standards organizations, committees,
23
- and governance bodies that need to maintain structured records of their
24
- decision-making processes.
25
-
26
- == Origin
27
-
28
- "Edoxen" is how all resolutions of Ancient Athens started.
29
-
30
- ____
31
- "It was the opinion of... (the people and city that...)"
32
- ____
33
-
34
- The word "edoxen" originates from the Ancient Greek word edokeō (ἔδοξεν),
35
- meaning "it was the opinion of" or "it seemed good to". This term was used in
36
- the context of formal resolutions and decisions made by the Athenian assembly,
37
- reflecting the collective will and judgment of the citizens.
38
-
39
- == Features
40
-
41
- * Classes for modeling resolutions, actions, considerations, and approvals
42
- * Support for resolution collections with rich metadata
43
- * Structured date handling with semantic meaning (meeting, decision, effective dates)
44
- * YAML and JSON serialization with round-trip compatibility
45
- * Structured identifiers and meeting information
46
- * Resolution relationships and dependencies
47
- * Integration with the `lutaml-model` serialization framework
48
- * Comprehensive YAML schema for validation
49
- * Command-line interface for validation and processing
50
- * Real-world compatibility with real-world resolutions (e.g., ISO/TCs, CIPM)
21
+ The intended users are standards organizations and governance bodies that
22
+ need to publish structured records of formal decision-making. Real-world
23
+ samples (ISO/TC 154, CIPM, OIML CIML) ship in `spec/fixtures/`.
51
24
 
52
25
  == Installation
53
26
 
54
- Add this line to your application's Gemfile:
55
-
56
27
  [source,ruby]
57
28
  ----
58
29
  gem 'edoxen'
59
30
  ----
60
31
 
61
- And then execute:
62
-
63
- [source,sh]
64
- ----
65
- $ bundle install
66
- ----
67
-
68
- Or install it yourself as:
69
-
70
- [source,sh]
71
- ----
72
- $ gem install edoxen
73
- ----
74
-
75
- == Command Line Interface
76
-
77
- The `edoxen` command provides utilities for working with resolution data files.
78
-
79
- === Available Commands
80
-
81
- Get help on available commands:
82
-
83
- [source,sh]
84
- ----
85
- $ edoxen help
86
- Commands:
87
- edoxen help [COMMAND] # Describe available commands or one specific command
88
- edoxen normalize YAML_FILE_PATTERN # Normalize YAML files using Edoxen schema
89
- edoxen validate YAML_FILE_PATTERN # Validate YAML files against Edoxen schema
90
- ----
91
-
92
- === Validation
93
-
94
- Validate resolution data files against the Edoxen schema:
95
-
96
- [source,sh]
97
- ----
98
- # Validate a single file
99
- $ edoxen validate resolutions.yaml
100
-
101
- # Validate multiple files
102
- $ edoxen validate file1.yaml file2.yaml file3.yaml
103
-
104
- # Validate files using patterns
105
- $ edoxen validate "*.yaml"
106
- $ edoxen validate "resolutions/*.yml"
107
-
108
- # Example output
109
- 🔍 Validating 3 file(s)...
110
- resolutions.yaml... ✅ VALID
111
- file1.yaml... ✅ VALID
112
- file2.yaml... ❌ INVALID
113
- - Line 15: Invalid action type 'invalid_type'
114
-
115
- 📊 Validation Summary:
116
- Valid files: 2
117
- Invalid files: 1
118
- Success rate: 66.7%
119
- ----
120
-
121
- Get detailed help for the validate command:
122
-
123
- [source,sh]
124
- ----
125
- $ edoxen help validate
126
- Usage:
127
- edoxen validate YAML_FILE_PATTERN
128
-
129
- Description:
130
- Validate YAML files against the Edoxen schema. Supports file patterns and multiple files.
131
-
132
- Examples:
133
- edoxen validate resolutions.yaml
134
- edoxen validate *.yaml
135
- edoxen validate "data/*.yml"
136
- ----
137
-
138
- === Normalization
139
-
140
- Normalize YAML files to ensure they conform to the Edoxen schema format.
141
-
142
- This command loads the YAML files, parses them through the Edoxen models, and
143
- outputs clean, properly formatted YAML.
144
-
145
- [source,sh]
146
- ----
147
- # Normalize files to a specific output directory
148
- $ edoxen normalize resolutions.yaml --output /path/to/output
149
-
150
- # Normalize multiple files
151
- $ edoxen normalize "*.yaml" --output normalized/
152
-
153
- # Normalize files in place (overwrites original files)
154
- $ edoxen normalize resolutions.yaml --inplace
155
-
156
- # Example output
157
- 🔄 Normalizing 2 file(s)...
158
- resolutions.yaml... ✅ NORMALIZED → /path/to/output/resolutions.yaml
159
- meeting-notes.yaml... ✅ NORMALIZED → /path/to/output/meeting-notes.yaml
160
-
161
- 📊 Normalization Summary:
162
- Successful: 2
163
- Failed: 0
164
- Success rate: 100.0%
165
- Output directory: /path/to/output
166
- ----
167
-
168
- Get detailed help for the normalize command:
169
-
170
- [source,sh]
171
- ----
172
- $ edoxen help normalize
173
- Usage:
174
- edoxen normalize YAML_FILE_PATTERN
175
-
176
- Options:
177
- [--output=OUTPUT] # Output directory for normalized files
178
- [--inplace], [--no-inplace], [--skip-inplace] # Modify files in place (no backup)
179
-
180
- Description:
181
- Normalize YAML files using Edoxen schema. This ensures consistent formatting
182
- and structure according to the Edoxen data models.
183
-
184
- Examples:
185
- edoxen normalize resolutions.yaml --output clean/
186
- edoxen normalize "*.yaml" --inplace
187
- edoxen normalize "data/*.yml" --output normalized/
188
- ----
189
-
190
- === Common use cases
191
-
192
- ==== Validating resolution collections
193
-
194
- [source,sh]
195
- ----
196
- # Validate all YAML files in a directory
197
- $ edoxen validate "resolutions/*.yaml"
198
-
199
- # Validate specific meeting files
200
- $ edoxen validate "plenary-*.yaml" "ballots-*.yaml"
201
- ----
202
-
203
- ==== Cleaning up legacy data
204
-
205
- [source,sh]
206
- ----
207
- # Normalize legacy files to new schema format
208
- $ edoxen normalize "legacy/*.yaml" --output clean/
209
-
210
- # Update files in place after backup
211
- $ cp -r data/ data-backup/
212
- $ edoxen normalize "data/*.yaml" --inplace
213
- ----
214
-
215
- ==== Batch processing
216
-
217
- [source,sh]
218
- ----
219
- # Validate and normalize in sequence
220
- $ edoxen validate "input/*.yaml" && edoxen normalize "input/*.yaml" --output output/
221
- ----
222
-
223
- == Usage
32
+ Then `bundle install`, or `gem install edoxen` standalone.
224
33
 
225
- === Parsing resolution data
226
-
227
- ==== From YAML
228
-
229
- [source,yaml]
230
- ----
231
- metadata:
232
- title: "Resolutions of the 42nd plenary meeting of ISO/TC 154"
233
- dates:
234
- - kind: meeting
235
- start: 2024-01-15
236
- end: 2024-01-17
237
- source: "ISO/TC 154 Secretariat"
238
- urls:
239
- - href: "https://example.com/meeting"
240
- title: "Meeting Information"
241
-
242
- resolutions:
243
- - identifier: "2024-01"
244
- title: "Adoption of new standard"
245
- category: "Technical resolutions"
246
- dates:
247
- - kind: decision
248
- start: 2024-01-16
249
- subject: "ISO/TC 154"
250
-
251
- considerations:
252
- - type: "having"
253
- message: "reviewed the technical specifications"
254
-
255
- actions:
256
- - type: "resolves"
257
- dates:
258
- - kind: effective
259
- start: 2024-01-16
260
- message: "to adopt ISO 12345 as a new standard"
261
-
262
- approvals:
263
- - type: "affirmative"
264
- degree: "unanimous"
265
- message: "UNANIMOUS"
266
- ----
34
+ == Quick start
267
35
 
268
36
  [source,ruby]
269
37
  ----
270
38
  require 'edoxen'
271
39
 
272
- # Parse from YAML string
273
- yaml_content = File.read('resolutions.yaml')
274
- resolution_set = Edoxen::ResolutionSet.from_yaml(yaml_content)
275
-
276
- # Access metadata
277
- puts "Title: #{resolution_set.metadata.title}"
278
- puts "Meeting dates: #{resolution_set.metadata.dates.first.start} to #{resolution_set.metadata.dates.first.end}"
279
- puts "Source: #{resolution_set.metadata.source}"
40
+ yaml = File.read('resolutions.yaml')
41
+ collection = Edoxen::ResolutionCollection.from_yaml(yaml)
280
42
 
281
- # Access resolutions
282
- resolution_set.resolutions.each do |resolution|
283
- puts "Resolution: #{resolution.identifier} - #{resolution.title}"
284
- puts "Category: #{resolution.category}"
285
- puts "Subject: #{resolution.subject}"
43
+ collection.resolutions.each do |resolution|
44
+ prefix = resolution.identifier.first.prefix
45
+ number = resolution.identifier.first.number
46
+ puts "#{prefix}/#{number}"
286
47
 
287
- # Access structured dates
288
- resolution.dates.each do |date|
289
- puts " #{date.kind.capitalize} date: #{date.start}"
290
- end
291
-
292
- # Access considerations
293
- resolution.considerations.each do |consideration|
294
- puts " Consideration (#{consideration.type}): #{consideration.message}"
295
- end
296
-
297
- # Access actions
298
- resolution.actions.each do |action|
299
- puts " Action (#{action.type}): #{action.message}"
300
- action.dates.each do |date|
301
- puts " #{date.kind.capitalize} date: #{date.start}"
48
+ resolution.localizations.each do |loc|
49
+ puts " [#{loc.language_code}/#{loc.script}] #{loc.title}"
50
+ loc.actions.each do |action|
51
+ puts " - #{action.type}: #{action.message}"
302
52
  end
303
53
  end
304
-
305
- # Access approvals
306
- resolution.approvals.each do |approval|
307
- puts " Approval: #{approval.type} (#{approval.degree})"
308
- end
309
54
  end
310
- ----
311
-
312
- ==== From JSON
313
-
314
- [source,ruby]
315
- ----
316
- require 'edoxen'
317
55
 
318
- # Parse from JSON string
319
- json_content = File.read('resolutions.json')
320
- resolution_set = Edoxen::ResolutionSet.from_json(json_content)
321
-
322
- # Access data (same as with YAML)
56
+ # Round-trip back to YAML
57
+ puts collection.to_yaml
323
58
  ----
324
59
 
325
- === Creating resolution data
326
-
327
- ==== Basic resolution
60
+ == Data model
328
61
 
329
- [source,ruby]
330
- ----
331
- require 'edoxen'
62
+ The full model is in `lib/edoxen/*.rb` (Ruby) and `schema/edoxen.yaml`
63
+ (JSON-Schema). Both are kept in lockstep by
64
+ `spec/edoxen/schema_enum_sync_spec.rb`.
332
65
 
333
- # Create structured dates
334
- meeting_date = Edoxen::ResolutionDate.new(
335
- kind: "meeting",
336
- start: Date.new(2024, 1, 15),
337
- end: Date.new(2024, 1, 17)
338
- )
339
-
340
- decision_date = Edoxen::ResolutionDate.new(
341
- kind: "decision",
342
- start: Date.new(2024, 1, 16)
343
- )
344
-
345
- effective_date = Edoxen::ResolutionDate.new(
346
- kind: "effective",
347
- start: Date.new(2024, 1, 16)
348
- )
349
-
350
- # Create metadata
351
- metadata = Edoxen::Metadata.new(
352
- title: "Resolutions of the 42nd plenary meeting of ISO/TC 154",
353
- dates: [meeting_date],
354
- source: "ISO/TC 154 Secretariat",
355
- urls: [
356
- Edoxen::Url.new(
357
- href: "https://example.com/meeting",
358
- title: "Meeting Information"
359
- )
360
- ]
361
- )
362
-
363
- # Create a resolution
364
- resolution = Edoxen::Resolution.new(
365
- identifier: "2024-01",
366
- title: "Adoption of new standard",
367
- category: "Technical resolutions",
368
- subject: "ISO/TC 154",
369
- dates: [decision_date]
370
- )
371
-
372
- # Add considerations
373
- consideration = Edoxen::Consideration.new(
374
- type: "having",
375
- message: "reviewed the technical specifications"
376
- )
377
- resolution.considerations = [consideration]
378
-
379
- # Add actions
380
- action = Edoxen::Action.new(
381
- type: "resolves",
382
- dates: [effective_date],
383
- message: "to adopt ISO 12345 as a new standard"
384
- )
385
- resolution.actions = [action]
386
-
387
- # Add approvals
388
- approval = Edoxen::Approval.new(
389
- type: "affirmative",
390
- degree: "unanimous",
391
- message: "UNANIMOUS"
392
- )
393
- resolution.approvals = [approval]
394
-
395
- # Create resolution set
396
- resolution_set = Edoxen::ResolutionSet.new(
397
- metadata: metadata,
398
- resolutions: [resolution]
399
- )
66
+ [source]
400
67
  ----
68
+ ResolutionCollection
69
+ ├── metadata: ResolutionMetadata
70
+ │ ├── title / title_localized[]
71
+ │ ├── date
72
+ │ ├── source
73
+ │ ├── source_urls: SourceUrl[]
74
+ │ ├── city, country_code
75
+ └── resolutions: Resolution[]
76
+ ├── identifier: StructuredIdentifier[1..*]
77
+ ├── type: ResolutionType (resolution | recommendation | decision | declaration)
78
+ ├── doi, urn, agenda_item
79
+ ├── dates: ResolutionDate[]
80
+ ├── categories: String[]
81
+ ├── meeting: MeetingIdentifier
82
+ ├── relations: ResolutionRelation[]
83
+ ├── urls: Url[]
84
+ └── localizations: Localization[1..*]
85
+ ├── language_code (ISO 639-3)
86
+ ├── script (ISO 15924)
87
+ ├── title, subject, message, considering
88
+ ├── considerations: Consideration[]
89
+ ├── approvals: Approval[]
90
+ └── actions: Action[]
91
+ ----
92
+
93
+ Every translatable field on a Resolution is wrapped inside one of its
94
+ `localizations[]` entries. Language-agnostic admin fields
95
+ (`identifier`, `doi`, `urn`, `agenda_item`, `dates`, `categories`,
96
+ `meeting`, `relations`, `urls`) live on the parent `Resolution`.
97
+
98
+ == Multilingual resolution sets
401
99
 
402
- ==== Multiple action types
403
-
404
- [source,ruby]
405
- ----
406
- # Actions can have multiple types
407
- action = Edoxen::Action.new(
408
- type: ["resolves", "requests"],
409
- dates: [effective_date],
410
- message: "to adopt the standard and request implementation guidance"
411
- )
100
+ [source,yaml]
412
101
  ----
102
+ resolutions:
103
+ - identifier:
104
+ - prefix: CIML
105
+ number: "2025-44"
106
+ doi: 10.63493/resolutions/ciml202544
107
+ agenda_item: "16.2"
108
+ dates:
109
+ - date: 2025-10-13
110
+ type: discussed
111
+ localizations:
112
+ - language_code: eng
113
+ script: Latn
114
+ title: Decision on the renewal of the contract of Mr Anthony Donnellan
115
+ subject: CIML
116
+ actions:
117
+ - type: decides
118
+ date_effective:
119
+ date: 2025-10-13
120
+ type: adoption
121
+ message: |
122
+ The Committee decides to renew the contract of
123
+ Mr Anthony Donnellan as BIML Director.
124
+ - language_code: fra
125
+ script: Latn
126
+ title: Décision sur le renouvellement du contrat de M. Anthony Donnellan
127
+ subject: CIML
128
+ actions:
129
+ - type: decides
130
+ date_effective:
131
+ date: 2025-10-13
132
+ type: adoption
133
+ message: |
134
+ Le Comité décide de renouveler le contrat de
135
+ M. Anthony Donnellan en tant que Directeur du BIML.
136
+ ----
137
+
138
+ == Command-line interface
139
+
140
+ The `edoxen` executable exposes two commands.
141
+
142
+ === `validate` — JSON-Schema validation + model parsing
413
143
 
414
- === Serializing resolution data
415
-
416
- ==== To YAML
417
-
418
- [source,ruby]
144
+ [source,sh]
419
145
  ----
420
- # Serialize to YAML
421
- yaml_content = resolution_set.to_yaml
422
- File.write('resolutions.yaml', yaml_content)
146
+ $ edoxen validate "spec/fixtures/*.yaml"
423
147
  ----
424
148
 
425
- ==== To JSON
149
+ Runs both `Edoxen::SchemaValidator` and `Edoxen::ResolutionCollection.from_yaml`
150
+ against each matching file. The schema catches `additionalProperties`,
151
+ `required`, enum, and pattern violations; the model catches structural
152
+ problems the schema can't express.
426
153
 
427
- [source,ruby]
428
- ----
429
- # Serialize to JSON
430
- json_content = resolution_set.to_json
431
- File.write('resolutions.json', json_content)
432
- ----
154
+ Exit code is non-zero if any file fails.
433
155
 
434
- == Data model
435
-
436
- The Edoxen gem provides the following models:
156
+ === `normalize` — round-trip every fixture through the model
437
157
 
438
- [source]
439
- ----
440
- ┌─────────────────────────┐
441
- │ ResolutionSet │
442
- │ │
443
- │ ◊metadata │
444
- │ ◊resolutions │
445
- └───────────┬─────────────┘
446
- │ 1..*
447
- ┌───────────────┴────────────────┐
448
- ┌───────────▼─────────────┐ ┌────────────▼───────────┐
449
- │ Resolution │ │ Metadata │
450
- │ │ │ │
451
- │ •identifier │ │ •title │
452
- │ •title │ │ ◊dates │
453
- │ •category │ │ •source │
454
- │ •subject │ │ ◊urls │
455
- │ ◊dates │ └────────────────────────┘
456
- │ ◊considerations │
457
- │ ◊actions │
458
- │ ◊approvals │
459
- └────────────┬────────────┘
460
- ├────────────────────┬────────────────────┐
461
- │ 1..* │ 1..* │ 1..*
462
- ┌────────▼────────┐ ┌────────▼────────┐ ┌────────▼────────┐
463
- │ Consideration │ │ Action │ │ Approval │
464
- │ │ │ │ │ │
465
- │ •type │ │ •type │ │ •type │
466
- │ •message │ │ •message │ │ •degree │
467
- │ •date_effective │ │ •subject │ │ •message │
468
- └─────────────────┘ └─────────────────┘ └─────────────────┘
158
+ [source,sh]
469
159
  ----
470
-
471
- === ResolutionSet
472
-
473
- The main container for resolution collections with metadata.
474
-
475
- `metadata`:: A `Metadata` object containing collection information
476
- `resolutions`:: A collection of `Resolution` objects
477
-
478
- === Metadata
479
-
480
- Contains metadata about the resolution collection.
481
-
482
- `title`:: The collection title as a string
483
- `dates`:: A collection of `ResolutionDate` objects for meeting dates
484
- `source`:: The source organization as a string
485
- `urls`:: A collection of `Url` objects for reference links
486
-
487
- === ResolutionDate
488
-
489
- Represents structured date information with semantic meaning.
490
-
491
- `kind`:: The type of date as a string
492
- `start`:: The start date as a Date object
493
- `end`:: The end date as a Date object (optional, for date ranges)
494
-
495
- === Url
496
-
497
- Represents a URL reference with optional title.
498
-
499
- `href`:: The URL as a string
500
- `title`:: The link title as a string (optional)
501
-
502
- === Resolution
503
-
504
- Represents a single formal resolution.
505
-
506
- `identifier`:: The resolution identifier as a string
507
- `title`:: The resolution title as a string
508
- `category`:: The resolution category as a string (optional)
509
- `subject`:: The subject body as a string (optional)
510
- `dates`:: A collection of `ResolutionDate` objects
511
- `considerations`:: A collection of `Consideration` objects
512
- `actions`:: A collection of `Action` objects
513
- `approvals`:: A collection of `Approval` objects
514
-
515
- === Action
516
-
517
- Represents actions taken within a resolution.
518
-
519
- `type`:: The action type(s). Can be a string or array of strings
520
- `dates`:: A collection of `ResolutionDate` objects
521
- `message`:: The action description as a string
522
- `subject`:: The action subject as a string (optional)
523
-
524
- === Consideration
525
-
526
- Represents considerations that lead to a resolution.
527
-
528
- `type`:: The consideration type as a string
529
- `message`:: The consideration description as a string
530
- `date_effective`:: The effective date as a Date object (optional)
531
-
532
- === Approval
533
-
534
- Represents approval information for a resolution.
535
-
536
- `type`:: The approval type as a string
537
- `degree`:: The degree of approval as a string (optional)
538
- `message`:: The approval message as a string (optional)
539
-
540
- == Copyright
541
-
542
- Copyright https://www.ribose.com[Ribose].
160
+ $ edoxen normalize "spec/fixtures/*.yaml" --output clean/
161
+ $ edoxen normalize legacy.yaml --inplace
162
+ ----
163
+
164
+ Loads each YAML file, parses it through the Ruby model, and writes the
165
+ result back. Used to migrate between model versions. Either `--output DIR`
166
+ or `--inplace` is required (mutually exclusive).
167
+
168
+ == Architecture
169
+
170
+ * `lib/edoxen.rb` is the single entry-point. It configures
171
+ `Lutaml::Model::Config` and `autoload`s every model class and service.
172
+ * Each model lives in its own file under `lib/edoxen/`. No `require_relative`
173
+ all cross-references resolve through Ruby autoload.
174
+ * `lib/edoxen/enums.rb` is the single source of truth for every enum
175
+ value used by the gem. Both the Ruby model (`attribute :type, :string,
176
+ values: Enums::ACTION_TYPE`) and `schema/edoxen.yaml` refer to the
177
+ same constant; `spec/edoxen/schema_enum_sync_spec.rb` asserts equality
178
+ at runtime.
179
+ * `lib/edoxen/schema_validator.rb` is intentionally small: a
180
+ `SchemaValidator::ValidationError`, a `validate_file` method, a
181
+ `validate_content` method, and a `LineMap` module for line-accurate
182
+ error reporting. Adding new collection paths never requires touching
183
+ it the lookup is longest-prefix match against an indent-heuristic
184
+ line map.
185
+ * `lib/edoxen/cli.rb` is the Thor surface that glues SchemaValidator and
186
+ `ResolutionCollection.from_yaml` together for the two CLI commands.
187
+
188
+ == Contributing
189
+
190
+ Follow the rules in `CLAUDE.md`:
191
+
192
+ * All public methods have specs.
193
+ * Specs use real model instances — never `double()`.
194
+ * Serialization goes through `lutaml-model` only — no hand-rolled
195
+ `to_h`, `from_h`, `to_yaml`, or `to_json` on a model class.
196
+ * Schema and Ruby enum values must agree; the schema_sync_spec catches
197
+ drift.
198
+ * All changes go through PRs. Never commit to `main`, never push tags.
543
199
 
544
200
  == License
545
201
 
546
- The gem is available as open source under the terms of the
547
- https://opensource.org/licenses/BSD-2-Clause[2-Clause BSD License].
202
+ BSD-2-Clause. Copyright Ribose Inc.