edoxen 0.3.1 → 0.5.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/README.adoc +170 -31
- data/TODO.meeting-agenda/01-design-decisions.md +132 -0
- data/TODO.meeting-agenda/02-lutaml-canonical.md +61 -0
- data/TODO.meeting-agenda/03-ruby-models.md +73 -0
- data/TODO.meeting-agenda/04-schema.md +34 -0
- data/TODO.meeting-agenda/05-fixtures.md +43 -0
- data/TODO.meeting-agenda/06-specs.md +52 -0
- data/TODO.meeting-agenda/07-cli.md +55 -0
- data/TODO.meeting-agenda/08-docs.md +35 -0
- data/TODO.meeting-agenda/09-verify-and-release.md +41 -0
- data/TODO.meeting-agenda/10-future-enhancements.md +72 -0
- data/TODO.meeting-agenda/README.md +30 -0
- data/lib/edoxen/agenda.rb +26 -0
- data/lib/edoxen/agenda_item.rb +17 -0
- data/lib/edoxen/cli.rb +79 -0
- data/lib/edoxen/date_range.rb +11 -0
- data/lib/edoxen/deadline.rb +10 -0
- data/lib/edoxen/enums.rb +24 -0
- data/lib/edoxen/host_ref.rb +14 -0
- data/lib/edoxen/location.rb +15 -0
- data/lib/edoxen/meeting.rb +68 -0
- data/lib/edoxen/meeting_collection.rb +26 -0
- data/lib/edoxen/meeting_collection_metadata.rb +11 -0
- data/lib/edoxen/meeting_localization.rb +15 -0
- data/lib/edoxen/meeting_relation.rb +12 -0
- data/lib/edoxen/person.rb +14 -0
- data/lib/edoxen/reference.rb +13 -0
- data/lib/edoxen/resolution_metadata.rb +6 -0
- data/lib/edoxen/schedule_item.rb +16 -0
- data/lib/edoxen/source_url.rb +6 -2
- data/lib/edoxen/version.rb +1 -1
- data/lib/edoxen.rb +16 -0
- data/schema/edoxen.yaml +7 -0
- data/schema/meeting.yaml +358 -0
- metadata +27 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f82e1428e5307aac53edfd1f1cdcdac401b7bae76b03ab102acf0a0a42da5382
|
|
4
|
+
data.tar.gz: 13a900902194b1420fb9fe0f9b4fd7382c30f0803770f399c58f3b5053bf763d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7e099bea2ef576a990544b0d68f527c52e99e617f8f16122f798d16638acc671468859f987d25ca2391fbd62da645e38a3e13c324127cb74a9287cf1e650cdd
|
|
7
|
+
data.tar.gz: b80c4a49ffc97adacf9871a49de120fad0725c871a2f4a4a4dd31ced9d68fa4ffb4f54c5bd6d5550eefa54b5cbbdd83f779dcc434c366a0cd9740ab6cba63376
|
data/README.adoc
CHANGED
|
@@ -41,16 +41,23 @@ yaml = File.read('resolutions.yaml')
|
|
|
41
41
|
collection = Edoxen::ResolutionCollection.from_yaml(yaml)
|
|
42
42
|
|
|
43
43
|
collection.resolutions.each do |resolution|
|
|
44
|
-
|
|
45
|
-
number
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
# identifier is StructuredIdentifier[1..*] — a Resolution can carry
|
|
45
|
+
# its TC number, SC number, and any cross-cutting reference number.
|
|
46
|
+
id = resolution.identifier.first
|
|
47
|
+
puts "#{id.prefix}/#{id.number}"
|
|
48
|
+
|
|
49
|
+
# Canonical rendering — English when available, else the first
|
|
50
|
+
# declared localization. See "Lookup accessors" below.
|
|
51
|
+
loc = resolution.primary_localization
|
|
52
|
+
puts " [#{loc.language_code}/#{loc.script}] #{loc.title}"
|
|
53
|
+
|
|
54
|
+
loc.actions.each do |action|
|
|
55
|
+
puts " - #{action.type}: #{action.message}"
|
|
53
56
|
end
|
|
57
|
+
|
|
58
|
+
# Or look up a specific language explicitly:
|
|
59
|
+
fra = resolution.in_language("fra")
|
|
60
|
+
puts " FR: #{fra.title}" if fra
|
|
54
61
|
end
|
|
55
62
|
|
|
56
63
|
# Round-trip back to YAML
|
|
@@ -95,6 +102,25 @@ Every translatable field on a Resolution is wrapped inside one of its
|
|
|
95
102
|
(`identifier`, `doi`, `urn`, `agenda_item`, `dates`, `categories`,
|
|
96
103
|
`meeting`, `relations`, `urls`) live on the parent `Resolution`.
|
|
97
104
|
|
|
105
|
+
=== Lookup accessors
|
|
106
|
+
|
|
107
|
+
Direct iteration over `localizations[]` is discouraged — the
|
|
108
|
+
language-preference policy lives behind two accessors:
|
|
109
|
+
|
|
110
|
+
* `Resolution#in_language(code, fallback: false)` — exact match by
|
|
111
|
+
ISO 639-3 code, or `nil` (or the first declared localization when
|
|
112
|
+
`fallback: true`).
|
|
113
|
+
* `Resolution#primary_localization` — English when available, else
|
|
114
|
+
the first declared localization. Mirrors the glossarist
|
|
115
|
+
LocalizedConcept "preferred language" notion.
|
|
116
|
+
|
|
117
|
+
[source,ruby]
|
|
118
|
+
----
|
|
119
|
+
resolution.in_language("fra") # => Localization or nil
|
|
120
|
+
resolution.in_language("deu", fallback: true)
|
|
121
|
+
resolution.primary_localization # English-or-first
|
|
122
|
+
----
|
|
123
|
+
|
|
98
124
|
== Multilingual resolution sets
|
|
99
125
|
|
|
100
126
|
[source,yaml]
|
|
@@ -137,9 +163,10 @@ resolutions:
|
|
|
137
163
|
|
|
138
164
|
== Command-line interface
|
|
139
165
|
|
|
140
|
-
The `edoxen` executable exposes two
|
|
166
|
+
The `edoxen` executable exposes four commands — two for Resolution
|
|
167
|
+
collections, two for Meeting/Agenda documents.
|
|
141
168
|
|
|
142
|
-
=== `validate` — JSON-Schema validation + model parsing
|
|
169
|
+
=== `validate` — JSON-Schema validation + model parsing for Resolutions
|
|
143
170
|
|
|
144
171
|
[source,sh]
|
|
145
172
|
----
|
|
@@ -153,7 +180,7 @@ problems the schema can't express.
|
|
|
153
180
|
|
|
154
181
|
Exit code is non-zero if any file fails.
|
|
155
182
|
|
|
156
|
-
=== `normalize` — round-trip
|
|
183
|
+
=== `normalize` — round-trip Resolution YAML through the model
|
|
157
184
|
|
|
158
185
|
[source,sh]
|
|
159
186
|
----
|
|
@@ -162,28 +189,134 @@ $ edoxen normalize legacy.yaml --inplace
|
|
|
162
189
|
----
|
|
163
190
|
|
|
164
191
|
Loads each YAML file, parses it through the Ruby model, and writes the
|
|
165
|
-
result back.
|
|
166
|
-
|
|
192
|
+
result back. Either `--output DIR` or `--inplace` is required (mutually
|
|
193
|
+
exclusive).
|
|
194
|
+
|
|
195
|
+
=== `validate-meetings` — validate Meeting/Agenda YAML
|
|
196
|
+
|
|
197
|
+
[source,sh]
|
|
198
|
+
----
|
|
199
|
+
$ edoxen validate-meetings "spec/fixtures/meetings/*.yaml"
|
|
200
|
+
----
|
|
201
|
+
|
|
202
|
+
Validates against `schema/meeting.yaml` and parses through
|
|
203
|
+
`Edoxen::Meeting` (single-Meeting files) or `Edoxen::MeetingCollection`
|
|
204
|
+
(wrappers). The schema's root is `oneOf` — both shapes are accepted.
|
|
205
|
+
|
|
206
|
+
=== `normalize-meetings` — round-trip Meeting YAML through the model
|
|
207
|
+
|
|
208
|
+
[source,sh]
|
|
209
|
+
----
|
|
210
|
+
$ edoxen normalize-meetings "spec/fixtures/meetings/*.yaml" --output clean/
|
|
211
|
+
----
|
|
212
|
+
|
|
213
|
+
Same shape as `normalize`, but for Meeting/Agenda documents.
|
|
214
|
+
|
|
215
|
+
== Meeting & Agenda model
|
|
216
|
+
|
|
217
|
+
The Meeting model represents the *event* that produces Resolutions,
|
|
218
|
+
plus the agenda document that orders its business. Mirrors
|
|
219
|
+
`edoxen-model/models/meeting*.lutaml`.
|
|
220
|
+
|
|
221
|
+
[source]
|
|
222
|
+
----
|
|
223
|
+
MeetingCollection
|
|
224
|
+
├── metadata: MeetingCollectionMetadata
|
|
225
|
+
└── meetings: Meeting[]
|
|
226
|
+
├── identifier: StructuredIdentifier[1..*]
|
|
227
|
+
├── urn, ordinal, type (MeetingType), status (MeetingStatus), year
|
|
228
|
+
├── date_range: DateRange {start, end}
|
|
229
|
+
├── committee, committee_group
|
|
230
|
+
├── venues: Location[] (multi-venue supported)
|
|
231
|
+
├── general_area, city (IATA), country_code (ISO 3166-1), virtual
|
|
232
|
+
├── chair, secretary: Person
|
|
233
|
+
├── host, hosts: HostRef[] (typed: national_body/liaison/associate/organizer)
|
|
234
|
+
├── source_urls: SourceUrl[] (with `kind` discriminator)
|
|
235
|
+
├── landing_url, registration_url
|
|
236
|
+
├── agenda: Agenda
|
|
237
|
+
│ ├── identifier, status (AgendaStatus), source_doc
|
|
238
|
+
│ ├── items: AgendaItem[]
|
|
239
|
+
│ │ ├── label, kind (numbered/unnumbered/header/opening/closing)
|
|
240
|
+
│ │ ├── title, description
|
|
241
|
+
│ │ ├── references: Reference[]
|
|
242
|
+
│ │ ├── outcome (discussed/resolved/deferred/adopted/withdrawn)
|
|
243
|
+
│ │ └── resolution_ref (URN link to a Resolution)
|
|
244
|
+
│ └── opening_session, closing_session: ScheduleItem
|
|
245
|
+
├── schedule: ScheduleItem[] (the time-bound timetable)
|
|
246
|
+
├── deadlines: Deadline[]
|
|
247
|
+
├── localizations: MeetingLocalization[] (per-language content)
|
|
248
|
+
├── relations: MeetingRelation[] (continues_from, joint_with, ...)
|
|
249
|
+
└── resolution_refs: String[] (URN links to ResolutionCollections)
|
|
250
|
+
----
|
|
251
|
+
|
|
252
|
+
=== Linking Meetings to ResolutionCollections
|
|
253
|
+
|
|
254
|
+
Meetings and ResolutionCollections are separate documents joined by URN:
|
|
255
|
+
|
|
256
|
+
- `Meeting.resolution_refs: [urn:...]`
|
|
257
|
+
- `ResolutionCollection.metadata.meeting_urn: urn:...`
|
|
258
|
+
|
|
259
|
+
The join is by URN because the two have different lifetimes — agendas
|
|
260
|
+
exist weeks before a meeting, resolutions only after adoption.
|
|
261
|
+
|
|
262
|
+
=== Lookup accessors (parallel to the Resolution side)
|
|
263
|
+
|
|
264
|
+
[source,ruby]
|
|
265
|
+
----
|
|
266
|
+
meeting = Edoxen::Meeting.from_yaml(File.read('meeting.yaml'))
|
|
267
|
+
|
|
268
|
+
meeting.in_language("fra") # => MeetingLocalization or nil
|
|
269
|
+
meeting.in_language("deu", fallback: true) # falls back to first
|
|
270
|
+
meeting.primary_localization # English-or-first
|
|
271
|
+
meeting.find_agenda_item("5.2") # AgendaItem by label
|
|
272
|
+
|
|
273
|
+
collection = Edoxen::MeetingCollection.from_yaml(...)
|
|
274
|
+
collection.find_by_urn("urn:oiml:ciml:meeting:ciml-56")
|
|
275
|
+
collection.find_by_identifier(prefix: "CIML", number: "56")
|
|
276
|
+
----
|
|
167
277
|
|
|
168
278
|
== Architecture
|
|
169
279
|
|
|
170
280
|
* `lib/edoxen.rb` is the single entry-point. It configures
|
|
171
281
|
`Lutaml::Model::Config` and `autoload`s every model class and service.
|
|
172
|
-
|
|
173
|
-
|
|
282
|
+
No `require_relative` in library code — cross-references resolve
|
|
283
|
+
through Ruby autoload.
|
|
284
|
+
* Each model lives in its own file under `lib/edoxen/`. Models declare
|
|
285
|
+
`attribute` only — `lutaml-model` auto-emits an identity map (wire
|
|
286
|
+
name = snake_case attribute name) when no explicit `key_value` block
|
|
287
|
+
is present. Add a `key_value do; map "wire", to: :attr; end` block
|
|
288
|
+
only when the wire name differs from the attribute name.
|
|
174
289
|
* `lib/edoxen/enums.rb` is the single source of truth for every enum
|
|
175
290
|
value used by the gem. Both the Ruby model (`attribute :type, :string,
|
|
176
|
-
values: Enums::ACTION_TYPE`) and
|
|
177
|
-
same
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
`
|
|
181
|
-
`
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
line
|
|
185
|
-
|
|
186
|
-
`
|
|
291
|
+
values: Enums::ACTION_TYPE`) and the schemas reference the
|
|
292
|
+
same constants.
|
|
293
|
+
* `lib/edoxen/error.rb` defines the unified `Edoxen::ValidationError`
|
|
294
|
+
— produced by both `SchemaValidator` (with `source: :schema` or
|
|
295
|
+
`:syntax`) and by model parse rescues in the CLI (with `source:
|
|
296
|
+
:model`). Carries `file`, `line`, `column`, `pointer`,
|
|
297
|
+
`message_text`, and `source`.
|
|
298
|
+
* `lib/edoxen/schema_validator.rb` is intentionally small: two
|
|
299
|
+
validate methods, a `LineMap` module for line-accurate error
|
|
300
|
+
reporting (longest-prefix match — no path-shape hardcoding), and
|
|
301
|
+
date coercion so `json_schemer` can validate `format: date` against
|
|
302
|
+
YAML-loaded `Date` instances.
|
|
303
|
+
* `lib/edoxen/cli.rb` exposes four Thor commands (`validate`,
|
|
304
|
+
`normalize`, `validate-meetings`, `normalize-meetings`) that
|
|
305
|
+
delegate their shared scaffolding
|
|
306
|
+
(expand/sort/empty/header/loop/tally/summary/exit) to the deep
|
|
307
|
+
`Edoxen::Cli::Batch` module.
|
|
308
|
+
|
|
309
|
+
=== Schema ↔ Ruby invariants
|
|
310
|
+
|
|
311
|
+
Two pairs of runtime specs guard each side of the schema ↔ Ruby
|
|
312
|
+
boundary:
|
|
313
|
+
|
|
314
|
+
* Resolution side: `schema_enum_sync_spec.rb` +
|
|
315
|
+
`schema_model_sync_spec.rb` against `schema/edoxen.yaml`.
|
|
316
|
+
* Meeting side: `schema_meeting_enum_sync_spec.rb` +
|
|
317
|
+
`schema_meeting_model_sync_spec.rb` against `schema/meeting.yaml`.
|
|
318
|
+
|
|
319
|
+
Drift fails CI immediately, not at fixture-validation time.
|
|
187
320
|
|
|
188
321
|
== Contributing
|
|
189
322
|
|
|
@@ -192,10 +325,16 @@ Follow the rules in `CLAUDE.md`:
|
|
|
192
325
|
* All public methods have specs.
|
|
193
326
|
* Specs use real model instances — never `double()`.
|
|
194
327
|
* Serialization goes through `lutaml-model` only — no hand-rolled
|
|
195
|
-
`to_h`, `from_h`, `to_yaml`, or `to_json` on a model class.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
328
|
+
`to_h`, `from_h`, `to_yaml`, or `to_json` on a model class. Declare
|
|
329
|
+
`attribute`; the wire map is auto-emitted.
|
|
330
|
+
* Schema and Ruby must agree on enum values *and* on property shape.
|
|
331
|
+
Both `schema_enum_sync_spec` and `schema_model_sync_spec` catch
|
|
332
|
+
drift at CI time.
|
|
333
|
+
* Library code uses autoload (declared in `lib/edoxen.rb`), never
|
|
334
|
+
`require_relative`. No `send` to private methods, no
|
|
335
|
+
`instance_variable_set`/`get`, no `respond_to?` for type checks.
|
|
336
|
+
* All changes go through PRs. Never commit to `main`, never push tags,
|
|
337
|
+
never add AI attribution to commits.
|
|
199
338
|
|
|
200
339
|
== License
|
|
201
340
|
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Phase 1 — Design decisions
|
|
2
|
+
|
|
3
|
+
Priority: **P0** (canonical — blocks every later phase)
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Lock down the conceptual model before any code lands. Every later
|
|
8
|
+
phase references these decisions; if they shift, downstream work
|
|
9
|
+
reopens.
|
|
10
|
+
|
|
11
|
+
## Decisions
|
|
12
|
+
|
|
13
|
+
### D1 — Meeting is the event; Agenda is a document
|
|
14
|
+
|
|
15
|
+
The previous draft conflated these. Split:
|
|
16
|
+
|
|
17
|
+
- **Meeting** — the event itself. Identity, time, venue, officers,
|
|
18
|
+
schedule, deadlines, links to Resolutions.
|
|
19
|
+
- **Agenda** — the business order document produced for the Meeting.
|
|
20
|
+
Carries items, opening/closing sessions, status (draft/final), and a
|
|
21
|
+
pointer to the source PDF.
|
|
22
|
+
|
|
23
|
+
A Meeting *has* an Agenda (`Meeting.agenda: Agenda`). An Agenda is
|
|
24
|
+
meaningless without a Meeting but may be versioned independently
|
|
25
|
+
(draft → final → amended).
|
|
26
|
+
|
|
27
|
+
### D2 — Schedule ≠ Agenda
|
|
28
|
+
|
|
29
|
+
- **Schedule** — the time-bound timetable: date, time slot, room,
|
|
30
|
+
event title. Lives on `Meeting.schedule: ScheduleItem[]`.
|
|
31
|
+
- **Agenda** — the topic-bound business order: numbered/unnumbered
|
|
32
|
+
items, opening session, closing session. Lives on `Meeting.agenda`.
|
|
33
|
+
|
|
34
|
+
ISO/TC 154's data already separates these (`schedule[]` and
|
|
35
|
+
`agenda.{opening_session, closing_session, wg_meetings}`). Don't
|
|
36
|
+
collapse them.
|
|
37
|
+
|
|
38
|
+
### D3 — Linking to Resolutions is by URN, not embedding
|
|
39
|
+
|
|
40
|
+
- `Meeting.resolution_refs: String[]` — URNs of resolution
|
|
41
|
+
collections produced by this meeting.
|
|
42
|
+
- `ResolutionCollection.metadata.meeting_urn: String` — back-reference.
|
|
43
|
+
|
|
44
|
+
Rationale: meetings and resolutions have different lifetimes (an
|
|
45
|
+
agenda exists weeks before the meeting; resolutions exist only after
|
|
46
|
+
adoption). Embedding forces re-serialization of the whole meeting when
|
|
47
|
+
one resolution is amended. URN linking matches how both repos already
|
|
48
|
+
work (ISO: `_data/events/` separate from `_data/resolutions/`; OIML:
|
|
49
|
+
the metadata block of a ResolutionCollection already comments its
|
|
50
|
+
meeting URN).
|
|
51
|
+
|
|
52
|
+
### D4 — Localization parallels the Resolution pattern
|
|
53
|
+
|
|
54
|
+
`Meeting.localizations: MeetingLocalization[]` carries per-language
|
|
55
|
+
`title`, `general_area`, `practical_info`. ISO 639-3 + ISO 15924,
|
|
56
|
+
identical to `Localization` for Resolutions.
|
|
57
|
+
|
|
58
|
+
The schedule itself stays language-agnostic at the structural level
|
|
59
|
+
(date, time, room); localized `event` and `description` text live
|
|
60
|
+
inside `MeetingLocalization`. **v1 decision:** keep schedule
|
|
61
|
+
monolingual on `ScheduleItem` (event/description as plain strings)
|
|
62
|
+
and only localize at the Meeting level. If a future caller needs
|
|
63
|
+
per-schedule-item localization, add `ScheduleItemLocalization`
|
|
64
|
+
then — don't pre-bake it.
|
|
65
|
+
|
|
66
|
+
### D5 — Reuse existing edoxen types
|
|
67
|
+
|
|
68
|
+
| Concept | Reuse |
|
|
69
|
+
|---|---|
|
|
70
|
+
| Identifier | `StructuredIdentifier` |
|
|
71
|
+
| Source URL | `SourceUrl` (extend with optional `kind` in this work) |
|
|
72
|
+
| Validation error | `Edoxen::ValidationError` |
|
|
73
|
+
| CLI scaffold | `Edoxen::Cli::Batch` |
|
|
74
|
+
|
|
75
|
+
### D6 — Meeting/Agenda have their own top-level containers
|
|
76
|
+
|
|
77
|
+
- `MeetingCollection` — top-level wrapper for a YAML containing many
|
|
78
|
+
meetings (parallel to `ResolutionCollection`).
|
|
79
|
+
- Single-meeting files are also valid (`Meeting` directly under root,
|
|
80
|
+
detected by `oneOf` in JSON-Schema).
|
|
81
|
+
|
|
82
|
+
### D7 — Wire-name are snake_case
|
|
83
|
+
|
|
84
|
+
LUTAML uses camelCase notation; YAML wire is snake_case. Same
|
|
85
|
+
convention as the rest of edoxen.
|
|
86
|
+
|
|
87
|
+
### D8 — All enums closed and synced
|
|
88
|
+
|
|
89
|
+
New enums land in `Edoxen::Enums` and the schema's `$defs/<EnumName>`
|
|
90
|
+
blocks. Both `schema_enum_sync_spec` and `schema_model_sync_spec`
|
|
91
|
+
extend to cover them.
|
|
92
|
+
|
|
93
|
+
## Type inventory (MECE check)
|
|
94
|
+
|
|
95
|
+
| Concept | Owns |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `Meeting` | the event (identity, time, venue, officers, agenda, schedule) |
|
|
98
|
+
| `MeetingLocalization` | per-language content for a Meeting |
|
|
99
|
+
| `MeetingCollection` | top-level wrapper for many Meetings |
|
|
100
|
+
| `MeetingRelation` | directed link between two Meetings |
|
|
101
|
+
| `Agenda` | the business order document |
|
|
102
|
+
| `AgendaItem` | one entry on an Agenda |
|
|
103
|
+
| `Location` | venue geography |
|
|
104
|
+
| `Person` | identity + contact + affiliation |
|
|
105
|
+
| `HostRef` | typed reference to an organization |
|
|
106
|
+
| `ScheduleItem` | one entry in the timetable |
|
|
107
|
+
| `Deadline` | a time-bound requirement |
|
|
108
|
+
| `DateRange` | start + end pair |
|
|
109
|
+
| `Reference` | generic document reference (used by AgendaItem) |
|
|
110
|
+
|
|
111
|
+
13 new classes. No overlaps.
|
|
112
|
+
|
|
113
|
+
## Enums
|
|
114
|
+
|
|
115
|
+
| Enum | Values |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `MEETING_TYPE` | plenary, working_group, task_group, ad_hoc, joint, conference_session |
|
|
118
|
+
| `MEETING_STATUS` | upcoming, completed, cancelled |
|
|
119
|
+
| `AGENDA_STATUS` | draft, final, amended, cancelled, superseded |
|
|
120
|
+
| `AGENDA_ITEM_KIND` | numbered, unnumbered, header, opening, closing |
|
|
121
|
+
| `AGENDA_ITEM_OUTCOME` | discussed, resolved, deferred, adopted, withdrawn |
|
|
122
|
+
| `HOST_TYPE` | national_body, liaison, associate, organizer |
|
|
123
|
+
| `MEETING_RELATION_TYPE` | continues_from, continues_to, joint_with, supersedes, supersedes_by, rescheduled_from, rescheduled_to |
|
|
124
|
+
| `SOURCE_URL_KIND` | agenda_pdf, minutes_pdf, resolutions_pdf, report_pdf, register_url, landing_page |
|
|
125
|
+
|
|
126
|
+
8 new enums.
|
|
127
|
+
|
|
128
|
+
## Acceptance criteria
|
|
129
|
+
|
|
130
|
+
- [x] D1–D8 written
|
|
131
|
+
- [x] Type inventory listed
|
|
132
|
+
- [x] Enum inventory listed
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Phase 2 — Canonical LUTAML in edoxen-model
|
|
2
|
+
|
|
3
|
+
Priority: **P0**
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Land the canonical LutaML UML definitions in
|
|
8
|
+
`/Users/mulgogi/src/mn/edoxen-model/models/`. The Ruby gem mirrors
|
|
9
|
+
these files 1:1.
|
|
10
|
+
|
|
11
|
+
## Deliverables — one `.lutaml` per concept
|
|
12
|
+
|
|
13
|
+
### Top-level
|
|
14
|
+
|
|
15
|
+
- `meeting_collection.lutaml` — top-level wrapper.
|
|
16
|
+
- `meeting.lutaml` — the event class.
|
|
17
|
+
- `meeting_localization.lutaml` — per-language content.
|
|
18
|
+
- `meeting_relation.lutaml` — cross-meeting link.
|
|
19
|
+
|
|
20
|
+
### Sub-structures
|
|
21
|
+
|
|
22
|
+
- `agenda.lutaml`
|
|
23
|
+
- `agenda_item.lutaml`
|
|
24
|
+
- `schedule_item.lutaml`
|
|
25
|
+
- `deadline.lutaml`
|
|
26
|
+
- `date_range.lutaml`
|
|
27
|
+
- `location.lutaml`
|
|
28
|
+
- `person.lutaml`
|
|
29
|
+
- `host_ref.lutaml`
|
|
30
|
+
- `reference.lutaml`
|
|
31
|
+
|
|
32
|
+
### Enums (one per file, mirroring existing convention)
|
|
33
|
+
|
|
34
|
+
- `meeting_type.lutaml`
|
|
35
|
+
- `meeting_status.lutaml`
|
|
36
|
+
- `agenda_status.lutaml`
|
|
37
|
+
- `agenda_item_kind.lutaml`
|
|
38
|
+
- `agenda_item_outcome.lutaml`
|
|
39
|
+
- `host_type.lutaml`
|
|
40
|
+
- `meeting_relation_type.lutaml`
|
|
41
|
+
- `source_url_kind.lutaml`
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
45
|
+
- `meeting-model.adoc` — superseded; the LUTAML files are now
|
|
46
|
+
authoritative. Move to `meeting-model.adoc.deprecated` rather than
|
|
47
|
+
delete (history preservation).
|
|
48
|
+
|
|
49
|
+
## Attribute shape per class
|
|
50
|
+
|
|
51
|
+
(See Phase 1 for the canonical field list. Each `.lutaml` declares
|
|
52
|
+
attributes exactly matching the Ruby model in Phase 3 — types are
|
|
53
|
+
LUTAML notation, wire names are snake_case per D7.)
|
|
54
|
+
|
|
55
|
+
## Acceptance criteria
|
|
56
|
+
|
|
57
|
+
- [ ] 21 new `.lutaml` files in `models/`.
|
|
58
|
+
- [ ] `README.adoc` updated: new "Meeting model" section, file index
|
|
59
|
+
extended.
|
|
60
|
+
- [ ] Old `meeting-model.adoc` renamed to `.deprecated`.
|
|
61
|
+
- [ ] Commit + PR to `metanorma/edoxen-model`.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Phase 3 — Ruby model mirror in edoxen
|
|
2
|
+
|
|
3
|
+
Priority: **P0**
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
One Ruby class per LUTAML file. autoload entries in `lib/edoxen.rb`.
|
|
8
|
+
No `require_relative` in library code. No `send` to private, no
|
|
9
|
+
`instance_variable_set`/`get`, no `respond_to?` for typing.
|
|
10
|
+
|
|
11
|
+
## Deliverables — `lib/edoxen/*.rb`
|
|
12
|
+
|
|
13
|
+
### New files
|
|
14
|
+
|
|
15
|
+
- `meeting_collection.rb` — top-level wrapper.
|
|
16
|
+
- `meeting.rb` — the event class.
|
|
17
|
+
- `meeting_localization.rb`
|
|
18
|
+
- `meeting_relation.rb`
|
|
19
|
+
- `agenda.rb`
|
|
20
|
+
- `agenda_item.rb`
|
|
21
|
+
- `schedule_item.rb`
|
|
22
|
+
- `deadline.rb`
|
|
23
|
+
- `date_range.rb`
|
|
24
|
+
- `location.rb`
|
|
25
|
+
- `person.rb`
|
|
26
|
+
- `host_ref.rb`
|
|
27
|
+
- `reference.rb`
|
|
28
|
+
|
|
29
|
+
### Modified files
|
|
30
|
+
|
|
31
|
+
- `lib/edoxen.rb` — add 13 autoload entries.
|
|
32
|
+
- `lib/edoxen/enums.rb` — add 8 new enum constants.
|
|
33
|
+
- `lib/edoxen/resolution_metadata.rb` — add `meeting_urn: String`
|
|
34
|
+
field (back-reference to a Meeting).
|
|
35
|
+
- `lib/edoxen/source_url.rb` — add optional `kind: String, values:
|
|
36
|
+
Enums::SOURCE_URL_KIND` field (orthogonal addition; existing
|
|
37
|
+
fixtures without it still parse).
|
|
38
|
+
|
|
39
|
+
### Class shape
|
|
40
|
+
|
|
41
|
+
Each class is `Lutaml::Model::Serializable` with:
|
|
42
|
+
|
|
43
|
+
- `attribute :name, Type, **opts` declarations only.
|
|
44
|
+
- No explicit `key_value do; map ...; end` block — lutaml-model
|
|
45
|
+
auto-emits identity maps (established in v0.3.1 refactor).
|
|
46
|
+
- Enum-typed attributes use `values: Enums::FOO`.
|
|
47
|
+
- Convenience accessors added where the lookup is non-trivial
|
|
48
|
+
(parallel to `Resolution#in_language`).
|
|
49
|
+
|
|
50
|
+
### Convenience accessors
|
|
51
|
+
|
|
52
|
+
- `Meeting#in_language(code, fallback: false)` — exact-match lookup
|
|
53
|
+
on `localizations`.
|
|
54
|
+
- `Meeting#primary_localization` — English-or-first.
|
|
55
|
+
- `Meeting#find_agenda_item(label)` — by label string.
|
|
56
|
+
- `MeetingCollection#find_by_urn(urn)` — URN lookup.
|
|
57
|
+
- `MeetingCollection#find_by_identifier(prefix:, number:)` —
|
|
58
|
+
structured-identifier lookup.
|
|
59
|
+
|
|
60
|
+
### Invariants
|
|
61
|
+
|
|
62
|
+
- `Meeting.identifier` is `StructuredIdentifier[1..*]` (required by
|
|
63
|
+
schema; permissive in model).
|
|
64
|
+
- `Meeting.localizations[*].language_code` matches `^[a-z]{3}$`
|
|
65
|
+
(schema-enforced).
|
|
66
|
+
- `Meeting.date_range` is a `DateRange` with optional `start`/`end`.
|
|
67
|
+
|
|
68
|
+
## Acceptance criteria
|
|
69
|
+
|
|
70
|
+
- [ ] 13 new Ruby files, each ≤40 lines.
|
|
71
|
+
- [ ] No `require_relative` in any new file.
|
|
72
|
+
- [ ] `bundle exec rspec` passes (existing 197 examples).
|
|
73
|
+
- [ ] `bundle exec rubocop` clean.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Phase 4 — JSON-Schema
|
|
2
|
+
|
|
3
|
+
Priority: **P0**
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
A separate `schema/meeting.yaml` for Meeting/Agenda validation. The
|
|
8
|
+
existing `schema/edoxen.yaml` (Resolutions) is untouched.
|
|
9
|
+
|
|
10
|
+
## Why a separate schema file
|
|
11
|
+
|
|
12
|
+
- Resolutions and Meetings are different document types with
|
|
13
|
+
different lifecycles.
|
|
14
|
+
- Different CLI subcommands validate each.
|
|
15
|
+
- Avoids touching the existing schema (which is referenced by 1,241
|
|
16
|
+
OIML fixtures via `yaml-language-server: $schema=`).
|
|
17
|
+
|
|
18
|
+
## Deliverables
|
|
19
|
+
|
|
20
|
+
- `schema/meeting.yaml` — full JSON-Schema (draft-07).
|
|
21
|
+
- Root: `oneOf` — `MeetingCollection` OR `Meeting`.
|
|
22
|
+
- `$defs` for every class + every enum.
|
|
23
|
+
- `additionalProperties: false` everywhere.
|
|
24
|
+
- `required` on identifier + type for Meeting.
|
|
25
|
+
- `pattern` on `language_code` (`^[a-z]{3}$`), `script`
|
|
26
|
+
(`^[A-Z][a-z]{3}$`), `country_code` (`^[A-Z]{2}$`).
|
|
27
|
+
- `minItems: 1` on `Meeting.identifier`.
|
|
28
|
+
|
|
29
|
+
## Acceptance criteria
|
|
30
|
+
|
|
31
|
+
- [ ] `schema/meeting.yaml` exists and is valid JSON-Schema draft-07.
|
|
32
|
+
- [ ] `Edoxen::SchemaValidator.new("path/to/meeting.yaml")` loads it.
|
|
33
|
+
- [ ] All Phase 5 fixtures validate cleanly.
|
|
34
|
+
- [ ] All Phase 6 specs (including schema enum sync) pass.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Phase 5 — Fixtures
|
|
2
|
+
|
|
3
|
+
Priority: **P0** for canonical, **P1** for richer ones.
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Real-world fixture YAMLs in `spec/fixtures/meetings/` that exercise
|
|
8
|
+
every shape the model supports.
|
|
9
|
+
|
|
10
|
+
## Deliverables
|
|
11
|
+
|
|
12
|
+
### P0 — required for canonical
|
|
13
|
+
|
|
14
|
+
- `spec/fixtures/meetings/simple-meeting.yaml` — minimal valid
|
|
15
|
+
Meeting (identifier, type, date_range, one venue, one localization,
|
|
16
|
+
one agenda item).
|
|
17
|
+
- `spec/fixtures/meetings/ciml-56-meeting.yaml` — OIML CIML 56th
|
|
18
|
+
meeting: bilingual (EN/FR), structured identifier, source URLs with
|
|
19
|
+
kind, resolution_ref URN linking to a ResolutionCollection.
|
|
20
|
+
- `spec/fixtures/meetings/isotc154-plenary-42-meeting.yaml` — ISO/TC
|
|
21
|
+
154 42nd plenary: multi-day date_range, multi-venue, typed hosts,
|
|
22
|
+
full schedule with 9 entries, deadlines, chair/secretary.
|
|
23
|
+
|
|
24
|
+
### P1 — useful coverage
|
|
25
|
+
|
|
26
|
+
- `spec/fixtures/meetings/virtual-meeting.yaml` — virtual meeting
|
|
27
|
+
(no venues, country_code empty).
|
|
28
|
+
- `spec/fixtures/meetings/multi-collection.yaml` — a
|
|
29
|
+
MeetingCollection wrapping multiple meetings.
|
|
30
|
+
|
|
31
|
+
## Fixture invariants
|
|
32
|
+
|
|
33
|
+
- All fixtures MUST validate against `schema/meeting.yaml`.
|
|
34
|
+
- All fixtures MUST round-trip through the Ruby model
|
|
35
|
+
(`Meeting.from_yaml(...).to_yaml` re-parses identically).
|
|
36
|
+
- Real-world fixtures (ciml-56, isotc154-42) MUST reflect actual
|
|
37
|
+
published data — no fabricated committee names or dates.
|
|
38
|
+
|
|
39
|
+
## Acceptance criteria
|
|
40
|
+
|
|
41
|
+
- [ ] At least the three P0 fixtures exist.
|
|
42
|
+
- [ ] `bundle exec exe/edoxen validate-meetings "spec/fixtures/meetings/*.yaml"` passes on all of them.
|
|
43
|
+
- [ ] Each fixture is referenced by at least one round-trip spec.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Phase 6 — Specs
|
|
2
|
+
|
|
3
|
+
Priority: **P0**
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Comprehensive spec coverage. One spec file per new model class, plus
|
|
8
|
+
CLI and schema-sync coverage. Real model instances only — no
|
|
9
|
+
`double()`.
|
|
10
|
+
|
|
11
|
+
## Deliverables — `spec/edoxen/meeting_*_spec.rb` (one per class)
|
|
12
|
+
|
|
13
|
+
For each of the 13 new classes:
|
|
14
|
+
|
|
15
|
+
- Construction with explicit attrs.
|
|
16
|
+
- Round-trip YAML serialization.
|
|
17
|
+
- Enum coverage where applicable (one example per enum value).
|
|
18
|
+
- Real-instance types (e.g., `Meeting#agenda` returns an `Edoxen::Agenda`).
|
|
19
|
+
|
|
20
|
+
## Deliverables — additional specs
|
|
21
|
+
|
|
22
|
+
- `spec/edoxen/meeting_collection_spec.rb` — top-level container,
|
|
23
|
+
round-trips, lookup helpers (`find_by_urn`, `find_by_identifier`).
|
|
24
|
+
- `spec/edoxen/meeting_lookup_spec.rb` — `Meeting#in_language`,
|
|
25
|
+
`Meeting#primary_localization`, `Meeting#find_agenda_item`.
|
|
26
|
+
- `spec/edoxen/schema_meeting_enum_sync_spec.rb` — every enum in
|
|
27
|
+
`schema/meeting.yaml` `$defs` matches the corresponding
|
|
28
|
+
`Edoxen::Enums::*` frozen array.
|
|
29
|
+
- `spec/edoxen/schema_meeting_model_sync_spec.rb` — every Ruby
|
|
30
|
+
Meeting-related class has matching properties/collection flags in
|
|
31
|
+
`schema/meeting.yaml` `$defs`.
|
|
32
|
+
- `spec/edoxen/cli_meetings_spec.rb` — `validate-meetings` and
|
|
33
|
+
`normalize-meetings` subcommands, spawned via `Open3.capture3`
|
|
34
|
+
against the new fixtures.
|
|
35
|
+
- `spec/edoxen/schema_validator_meeting_spec.rb` — SchemaValidator
|
|
36
|
+
constructed with the meeting schema path; happy path + each error
|
|
37
|
+
type (additionalProperties, required, enum, pattern, type).
|
|
38
|
+
|
|
39
|
+
## Style rules
|
|
40
|
+
|
|
41
|
+
- Real model instances only — no `double()`.
|
|
42
|
+
- Each example ≤ 10 lines.
|
|
43
|
+
- Round-trip examples cover every fixture.
|
|
44
|
+
|
|
45
|
+
## Acceptance criteria
|
|
46
|
+
|
|
47
|
+
- [ ] One spec file per new class (13 files).
|
|
48
|
+
- [ ] +5 additional specs (collection, lookup, schema enum sync,
|
|
49
|
+
schema model sync, CLI).
|
|
50
|
+
- [ ] +1 schema_validator spec for the meeting schema path.
|
|
51
|
+
- [ ] `bundle exec rspec` — 0 failures (existing 197 + new ≈ 80+).
|
|
52
|
+
- [ ] `bundle exec rubocop` clean.
|