edoxen 0.1.2 → 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 +4 -4
- data/CLAUDE.md +220 -0
- data/README.adoc +153 -498
- data/lib/edoxen/_metadata.rb.deprecated +57 -0
- data/lib/edoxen/action.rb +5 -20
- data/lib/edoxen/approval.rb +6 -10
- data/lib/edoxen/cli.rb +70 -87
- data/lib/edoxen/consideration.rb +5 -13
- data/lib/edoxen/enums.rb +43 -0
- data/lib/edoxen/error.rb +7 -0
- data/lib/edoxen/localization.rb +30 -0
- data/lib/edoxen/meeting_identifier.rb +2 -6
- data/lib/edoxen/resolution.rb +19 -32
- data/lib/edoxen/resolution_collection.rb +15 -0
- data/lib/edoxen/resolution_date.rb +7 -8
- data/lib/edoxen/resolution_metadata.rb +26 -0
- data/lib/edoxen/resolution_relation.rb +5 -39
- data/lib/edoxen/schema_validator.rb +160 -248
- data/lib/edoxen/source_url.rb +18 -0
- data/lib/edoxen/structured_identifier.rb +17 -0
- data/lib/edoxen/url.rb +2 -3
- data/lib/edoxen/version.rb +1 -1
- data/lib/edoxen.rb +36 -14
- data/schema/edoxen.yaml +300 -339
- metadata +11 -3
- data/lib/edoxen/metadata.rb +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2fe56ab6ddff9606df4af319cba2d372d7114bce66822968710cb15cf0886cd
|
|
4
|
+
data.tar.gz: 7e30d4153daaa03d9cab2d10170947b10c646526bcc6b156ef909a5b8557d61d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5665a5650d5ac075b1905095578a188d5ce4e1a94d3fac522133055d14bdc356a5f49382256f9ea3f9b7c159bc7c2d5dc5315b1fa7c5354ec8a016d2cd85f3c3
|
|
7
|
+
data.tar.gz: 6c269809b5e14f084d1a427a68a7e623466550dde648ffad9add85fae84ae5b1607de77adf70bfa5f0a90a6f04cfd4db842f0287c84d9e6120286a2eb4486d94
|
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).
|