edoxen 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5b96952dff52eca74ca77c35534cf68e380e57694eec5f308fe855b306bd3b2
4
- data.tar.gz: c19d66b0f115b74562a01d04a19e1416f2ee7414010d7cca991f84ee37c39245
3
+ metadata.gz: c2fe56ab6ddff9606df4af319cba2d372d7114bce66822968710cb15cf0886cd
4
+ data.tar.gz: 7e30d4153daaa03d9cab2d10170947b10c646526bcc6b156ef909a5b8557d61d
5
5
  SHA512:
6
- metadata.gz: d44772650c4943aa5d487969edb49db47a8078ff7544fe68d7d581e238eb4d1d8b1c4268919cbd823b5f7787c2edf81040b7342ae05392ccfe21e1159f5e9e34
7
- data.tar.gz: 0e079b28ade73d23191d517fbf676fd22f2a1584670575b1d3ad42bbc18673c02836feba67191d72733aee37be1aa27abb6f5b087aac824fa71132889249cd72
6
+ metadata.gz: 5665a5650d5ac075b1905095578a188d5ce4e1a94d3fac522133055d14bdc356a5f49382256f9ea3f9b7c159bc7c2d5dc5315b1fa7c5354ec8a016d2cd85f3c3
7
+ data.tar.gz: 6c269809b5e14f084d1a427a68a7e623466550dde648ffad9add85fae84ae5b1607de77adf70bfa5f0a90a6f04cfd4db842f0287c84d9e6120286a2eb4486d94
data/.rubocop_todo.yml CHANGED
@@ -1,11 +1,19 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-07-18 13:23:46 UTC using RuboCop version 1.78.0.
3
+ # on 2025-07-20 10:40:54 UTC using RuboCop version 1.78.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Offense count: 2
10
+ # This cop supports safe autocorrection (--autocorrect).
11
+ # Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
12
+ # Include: **/*.gemspec
13
+ Gemspec/OrderedDependencies:
14
+ Exclude:
15
+ - 'edoxen.gemspec'
16
+
9
17
  # Offense count: 1
10
18
  # Configuration parameters: Severity, Include.
11
19
  # Include: **/*.gemspec
@@ -13,30 +21,57 @@ Gemspec/RequiredRubyVersion:
13
21
  Exclude:
14
22
  - 'edoxen.gemspec'
15
23
 
16
- # Offense count: 13
24
+ # Offense count: 1
25
+ # This cop supports safe autocorrection (--autocorrect).
26
+ # Configuration parameters: EnforcedStyleAlignWith.
27
+ # SupportedStylesAlignWith: either, start_of_block, start_of_line
28
+ Layout/BlockAlignment:
29
+ Exclude:
30
+ - 'lib/edoxen/schema_validator.rb'
31
+
32
+ # Offense count: 1
33
+ # This cop supports safe autocorrection (--autocorrect).
34
+ Layout/BlockEndNewline:
35
+ Exclude:
36
+ - 'lib/edoxen/schema_validator.rb'
37
+
38
+ # Offense count: 1
17
39
  # This cop supports safe autocorrection (--autocorrect).
18
40
  Layout/EmptyLineAfterMagicComment:
19
41
  Exclude:
20
- - 'lib/edoxen/action.rb'
21
- - 'lib/edoxen/approval.rb'
22
- - 'lib/edoxen/consideration.rb'
23
- - 'lib/edoxen/meeting_identfier.rb'
42
+ - 'test.rb'
43
+
44
+ # Offense count: 3
45
+ # This cop supports safe autocorrection (--autocorrect).
46
+ # Configuration parameters: EnforcedStyle.
47
+ # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only
48
+ Layout/EmptyLinesAroundClassBody:
49
+ Exclude:
24
50
  - 'lib/edoxen/resolution.rb'
25
- - 'lib/edoxen/resolution_collection.rb'
26
51
  - 'lib/edoxen/resolution_date.rb'
27
- - 'lib/edoxen/resolution_relationship.rb'
28
- - 'lib/edoxen/structured_identifier.rb'
29
- - 'lib/edoxen/subject_body.rb'
30
- - 'spec/edoxen/action_spec.rb'
31
- - 'spec/edoxen/resolution_collection_spec.rb'
32
- - 'spec/edoxen/resolution_spec.rb'
52
+ - 'lib/edoxen/resolution_set.rb'
33
53
 
34
- # Offense count: 1
54
+ # Offense count: 2
35
55
  # This cop supports safe autocorrection (--autocorrect).
36
- # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
37
- # URISchemes: http, https
38
- Layout/LineLength:
39
- Max: 123
56
+ Layout/EmptyLinesAroundExceptionHandlingKeywords:
57
+ Exclude:
58
+ - 'lib/edoxen/schema_validator.rb'
59
+ - 'round_trip_test.rb'
60
+
61
+ # Offense count: 3
62
+ # This cop supports safe autocorrection (--autocorrect).
63
+ # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment.
64
+ Layout/ExtraSpacing:
65
+ Exclude:
66
+ - 'lib/edoxen/cli.rb'
67
+ - 'lib/edoxen/schema_validator.rb'
68
+
69
+ # Offense count: 2
70
+ # This cop supports safe autocorrection (--autocorrect).
71
+ # Configuration parameters: Width, AllowedPatterns.
72
+ Layout/IndentationWidth:
73
+ Exclude:
74
+ - 'lib/edoxen/schema_validator.rb'
40
75
 
41
76
  # Offense count: 1
42
77
  # Configuration parameters: AllowComments.
@@ -44,13 +79,76 @@ Lint/EmptyFile:
44
79
  Exclude:
45
80
  - 'lib/edoxen/meeting.rb'
46
81
 
47
- # Offense count: 14
82
+ # Offense count: 1
83
+ # Configuration parameters: AllowedParentClasses.
84
+ Lint/MissingSuper:
85
+ Exclude:
86
+ - 'lib/edoxen/schema_validator.rb'
87
+
88
+ # Offense count: 4
89
+ # This cop supports safe autocorrection (--autocorrect).
90
+ # Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
91
+ # NotImplementedExceptions: NotImplementedError
92
+ Lint/UnusedMethodArgument:
93
+ Exclude:
94
+ - 'lib/edoxen/schema_validator.rb'
95
+
96
+ # Offense count: 3
97
+ # This cop supports safe autocorrection (--autocorrect).
98
+ # Configuration parameters: AutoCorrect.
99
+ Lint/UselessAssignment:
100
+ Exclude:
101
+ - 'lib/edoxen/schema_validator.rb'
102
+ - 'round_trip_test.rb'
103
+
104
+ # Offense count: 8
105
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
106
+ Metrics/AbcSize:
107
+ Max: 67
108
+
109
+ # Offense count: 9
48
110
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
49
111
  # AllowedMethods: refine
50
112
  Metrics/BlockLength:
51
- Max: 234
113
+ Max: 146
114
+
115
+ # Offense count: 3
116
+ # Configuration parameters: CountComments, CountAsOne.
117
+ Metrics/ClassLength:
118
+ Max: 167
119
+
120
+ # Offense count: 6
121
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
122
+ Metrics/CyclomaticComplexity:
123
+ Max: 16
52
124
 
53
125
  # Offense count: 10
126
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
127
+ Metrics/MethodLength:
128
+ Max: 50
129
+
130
+ # Offense count: 1
131
+ # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
132
+ Metrics/ParameterLists:
133
+ Max: 6
134
+
135
+ # Offense count: 6
136
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
137
+ Metrics/PerceivedComplexity:
138
+ Max: 17
139
+
140
+ # Offense count: 2
141
+ # This cop supports safe autocorrection (--autocorrect).
142
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
143
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
144
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
145
+ # FunctionalMethods: let, let!, subject, watch
146
+ # AllowedMethods: lambda, proc, it
147
+ Style/BlockDelimiters:
148
+ Exclude:
149
+ - 'lib/edoxen/schema_validator.rb'
150
+
151
+ # Offense count: 15
54
152
  # Configuration parameters: AllowedConstants.
55
153
  Style/Documentation:
56
154
  Exclude:
@@ -59,38 +157,102 @@ Style/Documentation:
59
157
  - 'lib/edoxen.rb'
60
158
  - 'lib/edoxen/action.rb'
61
159
  - 'lib/edoxen/approval.rb'
160
+ - 'lib/edoxen/cli.rb'
62
161
  - 'lib/edoxen/consideration.rb'
63
- - 'lib/edoxen/meeting_identfier.rb'
162
+ - 'lib/edoxen/meeting_identifier.rb'
163
+ - 'lib/edoxen/metadata.rb'
64
164
  - 'lib/edoxen/resolution.rb'
65
- - 'lib/edoxen/resolution_collection.rb'
66
- - 'lib/edoxen/resolution_relationship.rb'
67
- - 'lib/edoxen/structured_identifier.rb'
68
- - 'lib/edoxen/subject_body.rb'
165
+ - 'lib/edoxen/resolution_date.rb'
166
+ - 'lib/edoxen/resolution_relation.rb'
167
+ - 'lib/edoxen/resolution_set.rb'
168
+ - 'lib/edoxen/schema_validator.rb'
169
+ - 'lib/edoxen/url.rb'
170
+ - 'round_trip_test.rb'
69
171
 
70
- # Offense count: 13
172
+ # Offense count: 1
71
173
  # This cop supports unsafe autocorrection (--autocorrect-all).
72
174
  # Configuration parameters: EnforcedStyle.
73
175
  # SupportedStyles: always, always_true, never
74
176
  Style/FrozenStringLiteralComment:
75
177
  Exclude:
76
178
  - '**/*.arb'
77
- - 'lib/edoxen/action.rb'
78
- - 'lib/edoxen/approval.rb'
79
- - 'lib/edoxen/consideration.rb'
80
- - 'lib/edoxen/meeting_identfier.rb'
81
- - 'lib/edoxen/resolution.rb'
82
- - 'lib/edoxen/resolution_collection.rb'
83
- - 'lib/edoxen/resolution_date.rb'
84
- - 'lib/edoxen/resolution_relationship.rb'
85
- - 'lib/edoxen/structured_identifier.rb'
86
- - 'lib/edoxen/subject_body.rb'
87
- - 'spec/edoxen/action_spec.rb'
88
- - 'spec/edoxen/resolution_collection_spec.rb'
89
- - 'spec/edoxen/resolution_spec.rb'
179
+ - 'test.rb'
180
+
181
+ # Offense count: 1
182
+ # This cop supports unsafe autocorrection (--autocorrect-all).
183
+ # Configuration parameters: AllowedReceivers.
184
+ # AllowedReceivers: Thread.current
185
+ Style/HashEachMethods:
186
+ Exclude:
187
+ - 'lib/edoxen/schema_validator.rb'
188
+
189
+ # Offense count: 4
190
+ # This cop supports safe autocorrection (--autocorrect).
191
+ Style/IfUnlessModifier:
192
+ Exclude:
193
+ - 'lib/edoxen/cli.rb'
194
+ - 'lib/edoxen/schema_validator.rb'
195
+ - 'round_trip_test.rb'
196
+
197
+ # Offense count: 5
198
+ # This cop supports unsafe autocorrection (--autocorrect-all).
199
+ # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns.
200
+ # SupportedStyles: predicate, comparison
201
+ Style/NumericPredicate:
202
+ Exclude:
203
+ - 'spec/**/*'
204
+ - 'lib/edoxen/cli.rb'
205
+ - 'round_trip_test.rb'
206
+
207
+ # Offense count: 5
208
+ # This cop supports safe autocorrection (--autocorrect).
209
+ # Configuration parameters: EnforcedStyle.
210
+ # SupportedStyles: implicit, explicit
211
+ Style/RescueStandardError:
212
+ Exclude:
213
+ - 'lib/edoxen/cli.rb'
214
+ - 'lib/edoxen/schema_validator.rb'
215
+ - 'round_trip_test.rb'
216
+
217
+ # Offense count: 1
218
+ # This cop supports unsafe autocorrection (--autocorrect-all).
219
+ Style/SlicingWithRange:
220
+ Exclude:
221
+ - 'lib/edoxen/schema_validator.rb'
222
+
223
+ # Offense count: 7
224
+ # This cop supports unsafe autocorrection (--autocorrect-all).
225
+ # Configuration parameters: Mode.
226
+ Style/StringConcatenation:
227
+ Exclude:
228
+ - 'lib/edoxen/schema_validator.rb'
229
+ - 'round_trip_test.rb'
230
+
231
+ # Offense count: 10
232
+ # This cop supports safe autocorrection (--autocorrect).
233
+ # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
234
+ # SupportedStyles: single_quotes, double_quotes
235
+ Style/StringLiterals:
236
+ Exclude:
237
+ - 'round_trip_test.rb'
238
+
239
+ # Offense count: 9
240
+ # This cop supports safe autocorrection (--autocorrect).
241
+ # Configuration parameters: EnforcedStyle.
242
+ # SupportedStyles: single_quotes, double_quotes
243
+ Style/StringLiteralsInInterpolation:
244
+ Exclude:
245
+ - 'round_trip_test.rb'
246
+
247
+ # Offense count: 1
248
+ # This cop supports safe autocorrection (--autocorrect).
249
+ Style/UnlessElse:
250
+ Exclude:
251
+ - 'lib/edoxen/schema_validator.rb'
90
252
 
91
253
  # Offense count: 1
92
254
  # This cop supports safe autocorrection (--autocorrect).
93
255
  # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
94
256
  # URISchemes: http, https
95
257
  Layout/LineLength:
96
- Max: 123
258
+ Max: 122
data/CLAUDE.md ADDED
@@ -0,0 +1,220 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this gem is
6
+
7
+ `edoxen` is an information model for formal resolutions and decisions
8
+ (ISO/TC 154, CIPM, OIML, etc.). It provides:
9
+
10
+ - A set of `Lutaml::Model::Serializable` subclasses that mirror the canonical
11
+ LutaML UML model in `../edoxen-model/models/*.lutaml`.
12
+ - A JSON-Schema (`schema/edoxen.yaml`) used to validate real-world YAML
13
+ files via `SchemaValidator` (`json_schemer`).
14
+ - A Thor CLI (`edoxen validate`, `edoxen normalize`) that wraps both the
15
+ schema validator and the Ruby model parser.
16
+
17
+ ## Architecture
18
+
19
+ ### Loading
20
+
21
+ `lib/edoxen.rb` configures `Lutaml::Model::Config` and `autoload`s every
22
+ constant defined under `Edoxen::...` from its native file. **No
23
+ `require_relative` is used anywhere in this gem.** Cross-references between
24
+ model classes resolve through Ruby autoload (works for circular references
25
+ because autoload is lazy).
26
+
27
+ If you add a new model class under `lib/edoxen/<name>.rb`, register one
28
+ autoload entry in `lib/edoxen.rb`. The namespace is flat under `Edoxen::`,
29
+ matching the LUTAML naming.
30
+
31
+ ### Three sources of truth that must stay in sync
32
+
33
+ There are three descriptions of the same data, and they must agree:
34
+
35
+ 1. **`lib/edoxen/*.rb`** — Ruby model. Attribute declarations + `key_value`
36
+ `map` blocks drive (de)serialization. The model is permissive about
37
+ field names and tolerant of unknown fields (lutaml-model drops them
38
+ silently) — the schema is the strict source.
39
+ 2. **`schema/edoxen.yaml`** — JSON-Schema (draft-07). Used by
40
+ `SchemaValidator`. Adds `additionalProperties: false`, `required`,
41
+ `enum`, `pattern`, and `minItems` constraints the model does not.
42
+ 3. **`spec/fixtures/*.yaml`** — real-world samples. Must validate against
43
+ the schema AND round-trip through the Ruby model.
44
+
45
+ When you change one, change all three in the same PR. The
46
+ `spec/edoxen/schema_enum_sync_spec.rb` enforces the schema ↔ Ruby enum
47
+ boundary at runtime.
48
+
49
+ ### Enum single source of truth
50
+
51
+ `lib/edoxen/enums.rb` is the authoritative list for every enum used by
52
+ the gem (`ACTION_TYPE`, `CONSIDERATION_TYPE`, `RESOLUTION_TYPE`,
53
+ `RESOLUTION_RELATION_TYPE`, `RESOLUTION_DATE_TYPE`, `APPROVAL_TYPE`,
54
+ `APPROVAL_DEGREE`, `URL_KIND`). Both the Ruby model
55
+ (`attribute :type, :string, values: Enums::ACTION_TYPE`) and the
56
+ `schema/edoxen.yaml` `$defs/<EnumName>` block reference these constants.
57
+
58
+ `schema_enum_sync_spec.rb` loads both, walks `$defs` for enum-typed
59
+ definitions, and asserts each value-list matches the corresponding
60
+ `Edoxen::Enums::*` array.
61
+
62
+ If you add a value (e.g. a new verb), add it to the Ruby constant AND
63
+ the schema enum block in the same commit.
64
+
65
+ ### Glossarist-style localization pattern
66
+
67
+ Per-language content lives inside `Resolution#localizations[]` (one
68
+ `Localization` per language). `Localization` carries `language_code`
69
+ (ISO 639-3) and `script` (ISO 15924), plus `title`, `subject`, `message`,
70
+ `considering`, `considerations`, `actions`, `approvals`.
71
+
72
+ Language-agnostic admin fields (`identifier`, `doi`, `urn`,
73
+ `agenda_item`, `dates`, `categories`, `meeting`, `relations`, `urls`)
74
+ live on the parent `Resolution`.
75
+
76
+ `Metadata#title_localized` and `Metadata#source_urls` mirror this
77
+ pattern for collection-level metadata.
78
+
79
+ ### LUTAML fidelity + LUTAML quirks
80
+
81
+ The Ruby model is faithful to the LUTAML files. Two LUTAML quirks
82
+ that propagate:
83
+
84
+ - `empowers` is in both `ConsiderationType` and `ActionType` (upstream
85
+ duplication). `enums_spec.rb` documents this with an explicit test.
86
+ - LUTAML uses `camelCase` attribute names (e.g. `agendaItem`). Wire names
87
+ in this gem are `snake_case` (`agenda_item`) for YAML convention;
88
+ `dateEffective` becomes `date_effective`. This is a deliberate
89
+ one-way mapping from the LUTAML notation; the LUTAML files remain
90
+ authoritative for the semantic model.
91
+
92
+ ### SchemaValidator
93
+
94
+ `lib/edoxen/schema_validator.rb` is intentionally small:
95
+
96
+ - `SchemaValidator::ValidationError` — rendered as `file:line:col: msg`.
97
+ - `#validate_file(path)` → `Array<ValidationError>`.
98
+ - `#validate_content(content, path)` → `Array<ValidationError>`.
99
+ - `SchemaValidator::LineMap` — builds an indent-heuristic
100
+ `{json_pointer => line_no}` map and resolves a JSON-Schema data
101
+ pointer to a line via longest-prefix match. **No path-shape
102
+ hardcoding.** Adding new collection paths never requires touching this.
103
+ - Date coercion (`normalize_dates`) converts `Date` / `Time` instances
104
+ back to ISO strings before handing data to `json_schemer` because the
105
+ schema declares them as `type: string, format: date`.
106
+
107
+ ### CLI
108
+
109
+ `Edoxen::Cli` (Thor) lives in `lib/edoxen/cli.rb`. Two commands:
110
+
111
+ - `validate PATTERN` — glob-expands YAML files and runs both
112
+ `SchemaValidator#validate_file` and `Collection.from_yaml`. The dual
113
+ check is intentional: the schema catches `additionalProperties` /
114
+ `enum` / `required` / `pattern` violations that the model silently
115
+ drops, and the model catches structural issues json_schemer can't
116
+ express (numeric/date coercion, missing nested classes).
117
+ - `normalize PATTERN (--output DIR | --inplace)` — round-trips each
118
+ YAML file through `ResolutionCollection.from_yaml` and writes the
119
+ result back. Preserves any `# yaml-language-server: $schema=...`
120
+ directive in the first 5 lines.
121
+
122
+ Exit code is non-zero if any file fails.
123
+
124
+ ## Build, test, lint
125
+
126
+ ```bash
127
+ bundle install # first-time setup
128
+ bundle exec rspec # full test suite
129
+ bundle exec rspec spec/edoxen/schema_validator_spec.rb
130
+ bundle exec rspec spec/edoxen/enums_spec.rb
131
+ bundle exec rspec spec/edoxen/resolution_spec.rb:32 # one example by line
132
+ bundle exec rubocop # lint (line length 120, double-quoted strings)
133
+ bundle exec rake # default: spec + rubocop
134
+ bundle exec exe/edoxen validate "spec/fixtures/*.yaml"
135
+ mkdir -p /tmp/out && bundle exec exe/edoxen normalize "spec/fixtures/*.yaml" --output /tmp/out
136
+ ```
137
+
138
+ ## Conventions specific to this gem
139
+
140
+ - **No `double()` in specs** — use real `Edoxen::*` instances and fixture files.
141
+ - **Serialization is framework-only.** Never hand-roll `to_h` / `from_h` /
142
+ `to_yaml` / `from_yaml` / `to_json` / `from_json` on a model class. Use
143
+ `attribute` + `key_value do … end` and let lutaml-model do it. Renames
144
+ are `map "wire_name", to: :attr` — never key-swapping inside a method.
145
+ - **Wire names are `snake_case`.** Even when the LUTAML notation uses
146
+ camelCase, the YAML wire form is `snake_case` (`agenda_item`, not
147
+ `agendaItem`). The attribute name on the Ruby side is also `snake_case`;
148
+ when they match exactly, drop the explicit `map` declaration only after
149
+ checking lutaml-model supports it. (Today lutaml-model needs an explicit
150
+ `map` for `key_value`; do not delete them.)
151
+ - **Enums on the model and the schema must agree.** When you add or remove
152
+ a value to `Enums::*`, also update the matching `enum:` list in
153
+ `schema/edoxen.yaml` $defs. The `schema_enum_sync_spec` will catch any
154
+ drift.
155
+ - **Fixtures are load-bearing.** `spec/fixtures/{ciml,cipm,isotc154,...}.yaml`
156
+ are real-world extracts. If a model change invalidates them, that's a
157
+ signal to either (a) update the fixture in the same PR, or (b)
158
+ reconsider the model change. Do not leave them broken.
159
+ - **No backwards-compat shims on `Resolution`.** The flat `#title`,
160
+ `#subject`, `#considerations`, `#approvals`, `#actions` form is gone.
161
+ Always go through `localizations[]`.
162
+ - **Branch discipline.** Never commit to `main`, never push tags, all
163
+ changes via PR. See the global rules.
164
+ - **No AI attribution** in commits, PRs, or code comments.
165
+
166
+ ## Source hygiene — do not violate
167
+
168
+ - Never hand-roll `to_h` / `from_h` / `to_yaml` / `to_json` on a model.
169
+ - Never use `send` to call private methods.
170
+ - Never use `instance_variable_set` or `instance_variable_get` to cross
171
+ another object's boundary.
172
+ - Never use `respond_to?` for type checking — design so the type
173
+ hierarchy makes the check unnecessary, or use `is_a?`.
174
+ - Never use `require_relative` in library code. Use the autoload entries
175
+ in `lib/edoxen.rb`.
176
+
177
+ ## Adding a new model class (the OCP test)
178
+
179
+ When you introduce a new concept (e.g. `Subject`):
180
+
181
+ 1. Add `lib/edoxen/subject.rb` declaring
182
+ `class Subject < Lutaml::Model::Serializable` with attributes and
183
+ `key_value do … end` mapping.
184
+ 2. Add one autoload line in `lib/edoxen.rb`.
185
+ 3. Add a `$defs/Subject` block in `schema/edoxen.yaml` and reference
186
+ it from any parent that uses it.
187
+ 4. Add `spec/edoxen/subject_spec.rb` covering round-trip for every
188
+ field, every enum value, real-instance construction, no doubles.
189
+ 5. Add a fixture or update an existing one to exercise the new model.
190
+ 6. Run `bundle exec rspec` (full suite passes including
191
+ `schema_enum_sync_spec`) and `bundle exec rubocop`.
192
+
193
+ No existing class requires modification. The library grows only by
194
+ extension.
195
+
196
+ ## Adding a new CLI subcommand
197
+
198
+ 1. Add a `desc "name ARGS", "Description"` block in `lib/edoxen/cli.rb`
199
+ followed by a method. The CLI is intentionally a thin glue layer;
200
+ put logic in a model or service class and call it from the command.
201
+ 2. Add `spec/edoxen/cli_spec.rb` integration-style coverage that spawns
202
+ the real `exe/edoxen` process via `Open3.capture3` and asserts on
203
+ stdout/stderr/exit code. Don't shell out to a mocked binary — the
204
+ CLI is exercised as an end-to-end contract.
205
+ 3. Update the README's "Command-line interface" section.
206
+
207
+ ## Audit checklist — run before approving any contribution
208
+
209
+ 1. `bundle exec rspec` passes (currently 136 / 0).
210
+ 2. `bundle exec exe/edoxen validate spec/fixtures/*.yaml` is clean
211
+ (currently 4 / 0).
212
+ 3. `bundle exec exe/edoxen normalize --output /tmp/out` round-trips
213
+ every fixture without data loss (re-parse and assert equality).
214
+ 4. Schema and Ruby model agree on: field names, enum values,
215
+ required vs. optional, collection vs. scalar, `additionalProperties: false`.
216
+ 5. No new hand-rolled `to_h` / `from_h` on a model class.
217
+ 6. No new `LineMap`-style hardcoded path-shape branches.
218
+ 7. README example YAML parses against the schema (currently three
219
+ real-world fixtures do).
220
+ 8. `bundle exec rubocop` clean (target Ruby ≥ 3.0, line length 120).