edoxen 0.3.1 → 0.7.2
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 +10 -6
- data/Gemfile +4 -0
- data/README.adoc +181 -38
- 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/edoxen.gemspec +1 -0
- data/lib/edoxen/agenda.rb +26 -0
- data/lib/edoxen/agenda_item.rb +17 -0
- data/lib/edoxen/attendance.rb +23 -0
- data/lib/edoxen/cli.rb +97 -0
- data/lib/edoxen/date_range.rb +11 -0
- data/lib/edoxen/deadline.rb +10 -0
- data/lib/edoxen/enums.rb +31 -0
- data/lib/edoxen/host_ref.rb +14 -0
- data/lib/edoxen/link_checker.rb +85 -0
- data/lib/edoxen/location.rb +15 -0
- data/lib/edoxen/meeting.rb +82 -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/minutes.rb +31 -0
- data/lib/edoxen/minutes_section.rb +27 -0
- data/lib/edoxen/person.rb +14 -0
- data/lib/edoxen/reference.rb +13 -0
- data/lib/edoxen/reference_data.rb +95 -0
- data/lib/edoxen/resolution_metadata.rb +14 -0
- data/lib/edoxen/schedule_item.rb +27 -0
- data/lib/edoxen/schedule_item_localization.rb +21 -0
- data/lib/edoxen/source_url.rb +6 -2
- data/lib/edoxen/version.rb +1 -1
- data/lib/edoxen/vote_record.rb +25 -0
- data/lib/edoxen.rb +23 -0
- data/schema/edoxen.yaml +11 -3
- data/schema/meeting.yaml +480 -0
- metadata +48 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69a86986a86602665699b5598622d32a63658c302737587f68b7a9b095a471e1
|
|
4
|
+
data.tar.gz: 1201e014ac06c2e4accb00dfd00660f577f6f6464f5d7990553714b796af8314
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ec73b629af4dd2997fea614665347177f273569349d0221e414f9dc9b2dbb8f083b3d964675286bb9a76d449a5a4c7c2cf2981db028ebf5f0d2e54249abb77d
|
|
7
|
+
data.tar.gz: 52534618f6468c4da8effd9bcb23e978487e1857da1a9832fa8d1ed665e72324a13397657b0cb8f6c828be4ef45e9d669781a208b7135c09e97a1b833cc6c69e
|
data/CLAUDE.md
CHANGED
|
@@ -4,15 +4,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
4
4
|
|
|
5
5
|
## What this gem is
|
|
6
6
|
|
|
7
|
-
`edoxen` is an information model for formal
|
|
8
|
-
|
|
7
|
+
`edoxen` is an information model for formal proceedings — meetings,
|
|
8
|
+
agendas, minutes, attendance, votes, and the resolutions adopted by
|
|
9
|
+
standards bodies (ISO/TC 154, CIPM, OIML, etc.). It provides:
|
|
9
10
|
|
|
10
11
|
- A set of `Lutaml::Model::Serializable` subclasses that mirror the canonical
|
|
11
12
|
LutaML UML model in `../edoxen-model/models/*.lutaml`.
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
- Two JSON-Schemas — `schema/edoxen.yaml` for `ResolutionCollection`
|
|
14
|
+
and `schema/meeting.yaml` for `Meeting` / `MeetingCollection` — used
|
|
15
|
+
to validate real-world YAML files via `SchemaValidator`
|
|
16
|
+
(`json_schemer`).
|
|
17
|
+
- A Thor CLI that wraps both schemas and both model parsers:
|
|
18
|
+
`edoxen validate` / `edoxen normalize` for resolutions;
|
|
19
|
+
`edoxen validate-meetings` / `edoxen normalize-meetings` for meetings.
|
|
16
20
|
|
|
17
21
|
## Architecture
|
|
18
22
|
|
data/Gemfile
CHANGED
|
@@ -5,6 +5,10 @@ source "https://rubygems.org"
|
|
|
5
5
|
# Specify your gem's dependencies in edoxen.gemspec
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
|
+
# Pre-release sources. Move to RubyGems once published; the gemspec
|
|
9
|
+
# dep stays as `add_dependency "unlocode", "~> 0.1"` either way.
|
|
10
|
+
gem "unlocodes", github: "metanorma/unlocode"
|
|
11
|
+
|
|
8
12
|
gem "rake"
|
|
9
13
|
gem "rspec"
|
|
10
14
|
gem "rubocop"
|
data/README.adoc
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
= Edoxen
|
|
2
2
|
|
|
3
|
-
https://github.com/
|
|
4
|
-
image:https://img.shields.io/github/license/
|
|
5
|
-
image:https://img.shields.io/github/actions/workflow/status/
|
|
3
|
+
https://github.com/edoxen/edoxen[image:https://img.shields.io/github/stars/edoxen/edoxen.svg?style=social[GitHub Stars]]
|
|
4
|
+
image:https://img.shields.io/github/license/edoxen/edoxen.svg[License]
|
|
5
|
+
image:https://img.shields.io/github/actions/workflow/status/edoxen/edoxen/test.yml?branch=main[Build Status]
|
|
6
6
|
image:https://img.shields.io/gem/v/edoxen.svg[RubyGems Version]
|
|
7
7
|
|
|
8
8
|
== Purpose
|
|
9
9
|
|
|
10
10
|
Edoxen is a Ruby library for the canonical Edoxen information model of
|
|
11
|
-
formal
|
|
11
|
+
formal proceedings — meetings, agendas, minutes, attendance, votes,
|
|
12
|
+
and the resolutions adopted by standards bodies and governance
|
|
13
|
+
organisations. It is built on top of the
|
|
12
14
|
https://github.com/lutaml/lutaml[ lutaml-model] serialization framework.
|
|
13
15
|
|
|
14
16
|
The information model is defined in
|
|
15
|
-
https://github.com/
|
|
17
|
+
https://github.com/edoxen/edoxen-model/tree/main/models[ LutaML UML
|
|
16
18
|
files] (one `.lutaml` per concept). This gem mirrors that model exactly —
|
|
17
19
|
attribute declarations, enum values, and field shapes — so that anything
|
|
18
20
|
you can express in LutaML you can also construct, serialize, and validate
|
|
19
21
|
in Ruby.
|
|
20
22
|
|
|
21
23
|
The intended users are standards organizations and governance bodies that
|
|
22
|
-
need to publish structured records of formal
|
|
23
|
-
|
|
24
|
+
need to publish structured records of formal proceedings — the full
|
|
25
|
+
lifecycle from draft agenda through adopted resolution, in any ISO 639-3
|
|
26
|
+
language. Real-world samples (ISO/TC 154, CIPM, OIML CIML) ship in
|
|
27
|
+
`spec/fixtures/`.
|
|
24
28
|
|
|
25
29
|
== Installation
|
|
26
30
|
|
|
@@ -41,16 +45,23 @@ yaml = File.read('resolutions.yaml')
|
|
|
41
45
|
collection = Edoxen::ResolutionCollection.from_yaml(yaml)
|
|
42
46
|
|
|
43
47
|
collection.resolutions.each do |resolution|
|
|
44
|
-
|
|
45
|
-
number
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
# identifier is StructuredIdentifier[1..*] — a Resolution can carry
|
|
49
|
+
# its TC number, SC number, and any cross-cutting reference number.
|
|
50
|
+
id = resolution.identifier.first
|
|
51
|
+
puts "#{id.prefix}/#{id.number}"
|
|
52
|
+
|
|
53
|
+
# Canonical rendering — English when available, else the first
|
|
54
|
+
# declared localization. See "Lookup accessors" below.
|
|
55
|
+
loc = resolution.primary_localization
|
|
56
|
+
puts " [#{loc.language_code}/#{loc.script}] #{loc.title}"
|
|
57
|
+
|
|
58
|
+
loc.actions.each do |action|
|
|
59
|
+
puts " - #{action.type}: #{action.message}"
|
|
53
60
|
end
|
|
61
|
+
|
|
62
|
+
# Or look up a specific language explicitly:
|
|
63
|
+
fra = resolution.in_language("fra")
|
|
64
|
+
puts " FR: #{fra.title}" if fra
|
|
54
65
|
end
|
|
55
66
|
|
|
56
67
|
# Round-trip back to YAML
|
|
@@ -95,6 +106,25 @@ Every translatable field on a Resolution is wrapped inside one of its
|
|
|
95
106
|
(`identifier`, `doi`, `urn`, `agenda_item`, `dates`, `categories`,
|
|
96
107
|
`meeting`, `relations`, `urls`) live on the parent `Resolution`.
|
|
97
108
|
|
|
109
|
+
=== Lookup accessors
|
|
110
|
+
|
|
111
|
+
Direct iteration over `localizations[]` is discouraged — the
|
|
112
|
+
language-preference policy lives behind two accessors:
|
|
113
|
+
|
|
114
|
+
* `Resolution#in_language(code, fallback: false)` — exact match by
|
|
115
|
+
ISO 639-3 code, or `nil` (or the first declared localization when
|
|
116
|
+
`fallback: true`).
|
|
117
|
+
* `Resolution#primary_localization` — English when available, else
|
|
118
|
+
the first declared localization. Mirrors the glossarist
|
|
119
|
+
LocalizedConcept "preferred language" notion.
|
|
120
|
+
|
|
121
|
+
[source,ruby]
|
|
122
|
+
----
|
|
123
|
+
resolution.in_language("fra") # => Localization or nil
|
|
124
|
+
resolution.in_language("deu", fallback: true)
|
|
125
|
+
resolution.primary_localization # English-or-first
|
|
126
|
+
----
|
|
127
|
+
|
|
98
128
|
== Multilingual resolution sets
|
|
99
129
|
|
|
100
130
|
[source,yaml]
|
|
@@ -137,9 +167,10 @@ resolutions:
|
|
|
137
167
|
|
|
138
168
|
== Command-line interface
|
|
139
169
|
|
|
140
|
-
The `edoxen` executable exposes two
|
|
170
|
+
The `edoxen` executable exposes four commands — two for Resolution
|
|
171
|
+
collections, two for Meeting/Agenda documents.
|
|
141
172
|
|
|
142
|
-
=== `validate` — JSON-Schema validation + model parsing
|
|
173
|
+
=== `validate` — JSON-Schema validation + model parsing for Resolutions
|
|
143
174
|
|
|
144
175
|
[source,sh]
|
|
145
176
|
----
|
|
@@ -153,7 +184,7 @@ problems the schema can't express.
|
|
|
153
184
|
|
|
154
185
|
Exit code is non-zero if any file fails.
|
|
155
186
|
|
|
156
|
-
=== `normalize` — round-trip
|
|
187
|
+
=== `normalize` — round-trip Resolution YAML through the model
|
|
157
188
|
|
|
158
189
|
[source,sh]
|
|
159
190
|
----
|
|
@@ -162,28 +193,134 @@ $ edoxen normalize legacy.yaml --inplace
|
|
|
162
193
|
----
|
|
163
194
|
|
|
164
195
|
Loads each YAML file, parses it through the Ruby model, and writes the
|
|
165
|
-
result back.
|
|
166
|
-
|
|
196
|
+
result back. Either `--output DIR` or `--inplace` is required (mutually
|
|
197
|
+
exclusive).
|
|
198
|
+
|
|
199
|
+
=== `validate-meetings` — validate Meeting/Agenda YAML
|
|
200
|
+
|
|
201
|
+
[source,sh]
|
|
202
|
+
----
|
|
203
|
+
$ edoxen validate-meetings "spec/fixtures/meetings/*.yaml"
|
|
204
|
+
----
|
|
205
|
+
|
|
206
|
+
Validates against `schema/meeting.yaml` and parses through
|
|
207
|
+
`Edoxen::Meeting` (single-Meeting files) or `Edoxen::MeetingCollection`
|
|
208
|
+
(wrappers). The schema's root is `oneOf` — both shapes are accepted.
|
|
209
|
+
|
|
210
|
+
=== `normalize-meetings` — round-trip Meeting YAML through the model
|
|
211
|
+
|
|
212
|
+
[source,sh]
|
|
213
|
+
----
|
|
214
|
+
$ edoxen normalize-meetings "spec/fixtures/meetings/*.yaml" --output clean/
|
|
215
|
+
----
|
|
216
|
+
|
|
217
|
+
Same shape as `normalize`, but for Meeting/Agenda documents.
|
|
218
|
+
|
|
219
|
+
== Meeting & Agenda model
|
|
220
|
+
|
|
221
|
+
The Meeting model represents the *event* that produces Resolutions,
|
|
222
|
+
plus the agenda document that orders its business. Mirrors
|
|
223
|
+
`edoxen-model/models/meeting*.lutaml`.
|
|
224
|
+
|
|
225
|
+
[source]
|
|
226
|
+
----
|
|
227
|
+
MeetingCollection
|
|
228
|
+
├── metadata: MeetingCollectionMetadata
|
|
229
|
+
└── meetings: Meeting[]
|
|
230
|
+
├── identifier: StructuredIdentifier[1..*]
|
|
231
|
+
├── urn, ordinal, type (MeetingType), status (MeetingStatus), year
|
|
232
|
+
├── date_range: DateRange {start, end}
|
|
233
|
+
├── committee, committee_group
|
|
234
|
+
├── venues: Location[] (multi-venue supported)
|
|
235
|
+
├── general_area, city (UN/LOCODE, e.g. FRPAR), country_code (ISO 3166-1), virtual
|
|
236
|
+
├── chair, secretary: Person
|
|
237
|
+
├── host, hosts: HostRef[] (typed: national_body/liaison/associate/organizer)
|
|
238
|
+
├── source_urls: SourceUrl[] (with `kind` discriminator)
|
|
239
|
+
├── landing_url, registration_url
|
|
240
|
+
├── agenda: Agenda
|
|
241
|
+
│ ├── identifier, status (AgendaStatus), source_doc
|
|
242
|
+
│ ├── items: AgendaItem[]
|
|
243
|
+
│ │ ├── label, kind (numbered/unnumbered/header/opening/closing)
|
|
244
|
+
│ │ ├── title, description
|
|
245
|
+
│ │ ├── references: Reference[]
|
|
246
|
+
│ │ ├── outcome (discussed/resolved/deferred/adopted/withdrawn)
|
|
247
|
+
│ │ └── resolution_ref (URN link to a Resolution)
|
|
248
|
+
│ └── opening_session, closing_session: ScheduleItem
|
|
249
|
+
├── schedule: ScheduleItem[] (the time-bound timetable)
|
|
250
|
+
├── deadlines: Deadline[]
|
|
251
|
+
├── localizations: MeetingLocalization[] (per-language content)
|
|
252
|
+
├── relations: MeetingRelation[] (continues_from, joint_with, ...)
|
|
253
|
+
└── resolution_refs: String[] (URN links to ResolutionCollections)
|
|
254
|
+
----
|
|
255
|
+
|
|
256
|
+
=== Linking Meetings to ResolutionCollections
|
|
257
|
+
|
|
258
|
+
Meetings and ResolutionCollections are separate documents joined by URN:
|
|
259
|
+
|
|
260
|
+
- `Meeting.resolution_refs: [urn:...]`
|
|
261
|
+
- `ResolutionCollection.metadata.meeting_urn: urn:...`
|
|
262
|
+
|
|
263
|
+
The join is by URN because the two have different lifetimes — agendas
|
|
264
|
+
exist weeks before a meeting, resolutions only after adoption.
|
|
265
|
+
|
|
266
|
+
=== Lookup accessors (parallel to the Resolution side)
|
|
267
|
+
|
|
268
|
+
[source,ruby]
|
|
269
|
+
----
|
|
270
|
+
meeting = Edoxen::Meeting.from_yaml(File.read('meeting.yaml'))
|
|
271
|
+
|
|
272
|
+
meeting.in_language("fra") # => MeetingLocalization or nil
|
|
273
|
+
meeting.in_language("deu", fallback: true) # falls back to first
|
|
274
|
+
meeting.primary_localization # English-or-first
|
|
275
|
+
meeting.find_agenda_item("5.2") # AgendaItem by label
|
|
276
|
+
|
|
277
|
+
collection = Edoxen::MeetingCollection.from_yaml(...)
|
|
278
|
+
collection.find_by_urn("urn:oiml:ciml:meeting:ciml-56")
|
|
279
|
+
collection.find_by_identifier(prefix: "CIML", number: "56")
|
|
280
|
+
----
|
|
167
281
|
|
|
168
282
|
== Architecture
|
|
169
283
|
|
|
170
284
|
* `lib/edoxen.rb` is the single entry-point. It configures
|
|
171
285
|
`Lutaml::Model::Config` and `autoload`s every model class and service.
|
|
172
|
-
|
|
173
|
-
|
|
286
|
+
No `require_relative` in library code — cross-references resolve
|
|
287
|
+
through Ruby autoload.
|
|
288
|
+
* Each model lives in its own file under `lib/edoxen/`. Models declare
|
|
289
|
+
`attribute` only — `lutaml-model` auto-emits an identity map (wire
|
|
290
|
+
name = snake_case attribute name) when no explicit `key_value` block
|
|
291
|
+
is present. Add a `key_value do; map "wire", to: :attr; end` block
|
|
292
|
+
only when the wire name differs from the attribute name.
|
|
174
293
|
* `lib/edoxen/enums.rb` is the single source of truth for every enum
|
|
175
294
|
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
|
-
`
|
|
295
|
+
values: Enums::ACTION_TYPE`) and the schemas reference the
|
|
296
|
+
same constants.
|
|
297
|
+
* `lib/edoxen/error.rb` defines the unified `Edoxen::ValidationError`
|
|
298
|
+
— produced by both `SchemaValidator` (with `source: :schema` or
|
|
299
|
+
`:syntax`) and by model parse rescues in the CLI (with `source:
|
|
300
|
+
:model`). Carries `file`, `line`, `column`, `pointer`,
|
|
301
|
+
`message_text`, and `source`.
|
|
302
|
+
* `lib/edoxen/schema_validator.rb` is intentionally small: two
|
|
303
|
+
validate methods, a `LineMap` module for line-accurate error
|
|
304
|
+
reporting (longest-prefix match — no path-shape hardcoding), and
|
|
305
|
+
date coercion so `json_schemer` can validate `format: date` against
|
|
306
|
+
YAML-loaded `Date` instances.
|
|
307
|
+
* `lib/edoxen/cli.rb` exposes four Thor commands (`validate`,
|
|
308
|
+
`normalize`, `validate-meetings`, `normalize-meetings`) that
|
|
309
|
+
delegate their shared scaffolding
|
|
310
|
+
(expand/sort/empty/header/loop/tally/summary/exit) to the deep
|
|
311
|
+
`Edoxen::Cli::Batch` module.
|
|
312
|
+
|
|
313
|
+
=== Schema ↔ Ruby invariants
|
|
314
|
+
|
|
315
|
+
Two pairs of runtime specs guard each side of the schema ↔ Ruby
|
|
316
|
+
boundary:
|
|
317
|
+
|
|
318
|
+
* Resolution side: `schema_enum_sync_spec.rb` +
|
|
319
|
+
`schema_model_sync_spec.rb` against `schema/edoxen.yaml`.
|
|
320
|
+
* Meeting side: `schema_meeting_enum_sync_spec.rb` +
|
|
321
|
+
`schema_meeting_model_sync_spec.rb` against `schema/meeting.yaml`.
|
|
322
|
+
|
|
323
|
+
Drift fails CI immediately, not at fixture-validation time.
|
|
187
324
|
|
|
188
325
|
== Contributing
|
|
189
326
|
|
|
@@ -192,10 +329,16 @@ Follow the rules in `CLAUDE.md`:
|
|
|
192
329
|
* All public methods have specs.
|
|
193
330
|
* Specs use real model instances — never `double()`.
|
|
194
331
|
* 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
|
-
|
|
332
|
+
`to_h`, `from_h`, `to_yaml`, or `to_json` on a model class. Declare
|
|
333
|
+
`attribute`; the wire map is auto-emitted.
|
|
334
|
+
* Schema and Ruby must agree on enum values *and* on property shape.
|
|
335
|
+
Both `schema_enum_sync_spec` and `schema_model_sync_spec` catch
|
|
336
|
+
drift at CI time.
|
|
337
|
+
* Library code uses autoload (declared in `lib/edoxen.rb`), never
|
|
338
|
+
`require_relative`. No `send` to private methods, no
|
|
339
|
+
`instance_variable_set`/`get`, no `respond_to?` for type checks.
|
|
340
|
+
* All changes go through PRs. Never commit to `main`, never push tags,
|
|
341
|
+
never add AI attribution to commits.
|
|
199
342
|
|
|
200
343
|
== License
|
|
201
344
|
|
|
@@ -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 `edoxen/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.
|