edoxen 0.7.2 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +17 -1
  3. data/.rubocop_todo.yml +1 -1
  4. data/CHANGELOG.md +246 -0
  5. data/README.adoc +308 -153
  6. data/edoxen.gemspec +10 -10
  7. data/lib/edoxen/action.rb +1 -1
  8. data/lib/edoxen/agenda.rb +4 -7
  9. data/lib/edoxen/agenda_item.rb +10 -3
  10. data/lib/edoxen/approval.rb +1 -1
  11. data/lib/edoxen/attendance.rb +16 -10
  12. data/lib/edoxen/body_vocabulary_entry.rb +24 -0
  13. data/lib/edoxen/body_vocabulary_host.rb +29 -0
  14. data/lib/edoxen/cli.rb +117 -120
  15. data/lib/edoxen/component_localization.rb +11 -0
  16. data/lib/edoxen/consideration.rb +2 -2
  17. data/lib/edoxen/contact.rb +24 -0
  18. data/lib/edoxen/contact_identifier.rb +13 -0
  19. data/lib/edoxen/contact_method.rb +16 -0
  20. data/lib/edoxen/decision.rb +69 -0
  21. data/lib/edoxen/decision_collection.rb +10 -0
  22. data/lib/edoxen/decision_date.rb +9 -0
  23. data/lib/edoxen/{resolution_metadata.rb → decision_metadata.rb} +5 -6
  24. data/lib/edoxen/decision_relation.rb +11 -0
  25. data/lib/edoxen/entity_ref.rb +74 -0
  26. data/lib/edoxen/enums.rb +117 -9
  27. data/lib/edoxen/error.rb +1 -1
  28. data/lib/edoxen/extension_attribute.rb +78 -0
  29. data/lib/edoxen/host_ref.rb +1 -1
  30. data/lib/edoxen/link_checker.rb +8 -8
  31. data/lib/edoxen/localization.rb +2 -2
  32. data/lib/edoxen/localization_host.rb +23 -0
  33. data/lib/edoxen/meeting.rb +63 -39
  34. data/lib/edoxen/meeting_collection.rb +1 -2
  35. data/lib/edoxen/meeting_collection_metadata.rb +3 -1
  36. data/lib/edoxen/meeting_component.rb +41 -0
  37. data/lib/edoxen/meeting_extension.rb +25 -0
  38. data/lib/edoxen/meeting_identifier.rb +1 -1
  39. data/lib/edoxen/meeting_localization.rb +6 -1
  40. data/lib/edoxen/meeting_series.rb +21 -0
  41. data/lib/edoxen/minutes.rb +2 -2
  42. data/lib/edoxen/minutes_section.rb +8 -11
  43. data/lib/edoxen/motion.rb +51 -0
  44. data/lib/edoxen/name.rb +29 -0
  45. data/lib/edoxen/officer.rb +19 -0
  46. data/lib/edoxen/officers_host.rb +19 -0
  47. data/lib/edoxen/person.rb +5 -9
  48. data/lib/edoxen/physical_venue.rb +15 -0
  49. data/lib/edoxen/recurrence.rb +27 -0
  50. data/lib/edoxen/recurrence_by_day.rb +10 -0
  51. data/lib/edoxen/reference_data.rb +35 -41
  52. data/lib/edoxen/schema_validator.rb +2 -3
  53. data/lib/edoxen/source_url.rb +3 -3
  54. data/lib/edoxen/structured_identifier.rb +2 -2
  55. data/lib/edoxen/topic.rb +37 -0
  56. data/lib/edoxen/topic_asset.rb +14 -0
  57. data/lib/edoxen/topic_document.rb +16 -0
  58. data/lib/edoxen/venue.rb +80 -0
  59. data/lib/edoxen/venue_validator.rb +56 -0
  60. data/lib/edoxen/version.rb +1 -1
  61. data/lib/edoxen/virtual_venue.rb +15 -0
  62. data/lib/edoxen/vote_record.rb +17 -13
  63. data/lib/edoxen/voting.rb +45 -0
  64. data/lib/edoxen/voting_counts.rb +23 -0
  65. data/lib/edoxen.rb +40 -13
  66. data/schema/edoxen.yaml +792 -145
  67. data/schema/meeting.yaml +600 -142
  68. data/sig/edoxen.rbs +581 -1
  69. metadata +57 -20
  70. data/lib/edoxen/_metadata.rb.deprecated +0 -57
  71. data/lib/edoxen/location.rb +0 -15
  72. data/lib/edoxen/resolution.rb +0 -43
  73. data/lib/edoxen/resolution_collection.rb +0 -10
  74. data/lib/edoxen/resolution_date.rb +0 -11
  75. data/lib/edoxen/resolution_relation.rb +0 -11
  76. data/lib/edoxen/resolution_set.rb +0 -17
  77. data/lib/edoxen/schedule_item.rb +0 -27
  78. data/lib/edoxen/schedule_item_localization.rb +0 -21
data/schema/edoxen.yaml CHANGED
@@ -1,19 +1,17 @@
1
1
  ---
2
2
  $schema: "http://json-schema.org/draft-07/schema#"
3
3
  $id: "https://github.com/edoxen/edoxen/schema/edoxen.yaml"
4
- title: "Edoxen Resolution Collection Schema"
4
+ title: "Edoxen Decision Collection Schema"
5
5
  description: |
6
- Schema for validating Edoxen ResolutionCollection YAML files.
6
+ Schema for validating Edoxen DecisionCollection YAML files.
7
7
 
8
8
  Mirrors the canonical LutaML information model in
9
9
  https://github.com/edoxen/edoxen-model/tree/main/models .
10
10
 
11
- The enum constants declared under `$defs` (ActionType,
12
- ConsiderationType, ApprovalType, ApprovalDegree, ResolutionType,
13
- ResolutionDateType, ResolutionRelationType, UrlKind) are the
14
- authoritative wire values. `spec/edoxen/schema_enum_sync_spec.rb`
15
- asserts each one is character-for-character identical to the
16
- corresponding `Edoxen::Enums::*` frozen array.
11
+ The enum constants declared under `$defs` are the authoritative wire
12
+ values. `spec/edoxen/schema_enum_sync_spec.rb` asserts each one is
13
+ character-for-character identical to the corresponding `Edoxen::Enums::*`
14
+ frozen array.
17
15
 
18
16
  Trade-off: json_schemer's `valid_values` is not populated when an
19
17
  `enum` is reached via `$ref`, so the validator's enum-violation
@@ -24,14 +22,14 @@ additionalProperties: false
24
22
 
25
23
  properties:
26
24
  metadata:
27
- $ref: "#/$defs/ResolutionMetadata"
28
- resolutions:
25
+ $ref: "#/$defs/DecisionMetadata"
26
+ decisions:
29
27
  type: array
30
28
  items:
31
- $ref: "#/$defs/Resolution"
29
+ $ref: "#/$defs/Decision"
32
30
 
33
31
  required:
34
- - resolutions
32
+ - decisions
35
33
 
36
34
  $defs:
37
35
 
@@ -41,29 +39,40 @@ $defs:
41
39
 
42
40
  StructuredIdentifier:
43
41
  type: object
44
- description: "A resolution identifier (prefix + number). A Resolution may carry 1..* of these."
42
+ description: "An identifier (prefix + number). A Decision carries 1..* of these."
45
43
  additionalProperties: false
46
44
  required: [prefix, number]
47
45
  properties:
48
46
  prefix:
49
47
  type: string
50
- description: "Authority prefix, e.g. 'ISO', 'OIML', 'CIML'."
51
48
  number:
52
49
  type: string
53
- description: "Resolution number issued under that prefix."
50
+
51
+ EntityRef:
52
+ type: object
53
+ description: |
54
+ Typed cross-reference between entities (v2.1, TODO.refactor/44).
55
+ Exactly one of `urn`, `identifier`, or `local_ref` should be set;
56
+ the gem's `EntityRef#valid?` enforces this in Ruby.
57
+ additionalProperties: false
58
+ properties:
59
+ urn: { type: string }
60
+ identifier: { $ref: "#/$defs/StructuredIdentifier" }
61
+ local_ref: { type: string }
62
+ kind: { type: string }
63
+ role: { type: string }
64
+ note: { type: string }
54
65
 
55
66
  MeetingIdentifier:
56
67
  type: object
57
- description: "Identifies the meeting a Resolution belongs to."
68
+ description: "Identifies the meeting a Decision belongs to."
58
69
  additionalProperties: false
59
70
  properties:
60
71
  venue:
61
72
  type: string
62
- description: "Meeting venue name (free text)."
63
73
  date:
64
74
  type: string
65
75
  format: date
66
- description: "Meeting date."
67
76
 
68
77
  Url:
69
78
  type: object
@@ -78,9 +87,9 @@ $defs:
78
87
  format:
79
88
  type: string
80
89
 
81
- ResolutionRelation:
90
+ DecisionRelation:
82
91
  type: object
83
- description: "Directed relation between two resolutions identified by their StructuredIdentifier."
92
+ description: "Directed relation between two decisions identified by their StructuredIdentifier."
84
93
  additionalProperties: false
85
94
  required: [source, destination, type]
86
95
  properties:
@@ -89,13 +98,13 @@ $defs:
89
98
  destination:
90
99
  $ref: "#/$defs/StructuredIdentifier"
91
100
  type:
92
- $ref: "#/$defs/ResolutionRelationType"
101
+ $ref: "#/$defs/DecisionRelationType"
93
102
 
94
103
  # ====================================================================
95
104
  # Date carriers.
96
105
  # ====================================================================
97
106
 
98
- ResolutionDate:
107
+ DecisionDate:
99
108
  type: object
100
109
  description: "Date with semantic kind (adoption / drafted / discussed)."
101
110
  additionalProperties: false
@@ -104,13 +113,11 @@ $defs:
104
113
  date:
105
114
  type: string
106
115
  format: date
107
- description: "The date itself."
108
116
  type:
109
- $ref: "#/$defs/ResolutionDateType"
117
+ $ref: "#/$defs/DecisionDateType"
110
118
 
111
119
  # ====================================================================
112
- # Per-localization sub-structures. Each is owned by exactly one
113
- # Language; the language_code discriminator lives on Localization.
120
+ # Per-localization sub-structures.
114
121
  # ====================================================================
115
122
 
116
123
  Action:
@@ -122,7 +129,7 @@ $defs:
122
129
  type:
123
130
  $ref: "#/$defs/ActionType"
124
131
  date_effective:
125
- $ref: "#/$defs/ResolutionDate"
132
+ $ref: "#/$defs/DecisionDate"
126
133
  message:
127
134
  type: string
128
135
 
@@ -137,48 +144,42 @@ $defs:
137
144
  degree:
138
145
  $ref: "#/$defs/ApprovalDegree"
139
146
  date:
140
- $ref: "#/$defs/ResolutionDate"
147
+ $ref: "#/$defs/DecisionDate"
141
148
  message:
142
149
  type: string
143
150
 
144
151
  Consideration:
145
152
  type: object
146
- description: "The basis for a Resolution: a verb + one effective date + reasoning."
153
+ description: "The basis for a Decision: a verb + one effective date + reasoning."
147
154
  additionalProperties: false
148
155
  required: [type, message, date_effective]
149
156
  properties:
150
157
  type:
151
158
  $ref: "#/$defs/ConsiderationType"
152
159
  date_effective:
153
- $ref: "#/$defs/ResolutionDate"
160
+ $ref: "#/$defs/DecisionDate"
154
161
  message:
155
162
  type: string
156
163
 
157
164
  Localization:
158
165
  type: object
159
166
  description: |
160
- A monolingual rendering of a Resolution. Mirrors the glossarist
167
+ A monolingual rendering of a Decision. Mirrors the glossarist
161
168
  LocalizedConcept pattern: language-agnostic fields live on the
162
- parent Resolution; per-language content lives here.
169
+ parent Decision; per-language content lives here.
163
170
  additionalProperties: false
164
171
  required: [language_code]
165
172
  properties:
166
173
  language_code:
167
174
  type: string
168
175
  pattern: "^[a-z]{3}$"
169
- description: "ISO 639-3 three-letter language code (eng, fra, deu, spa, jpn, ...)."
170
176
  script:
171
177
  type: string
172
178
  pattern: "^[A-Z][a-z]{3}$"
173
- description: "ISO 15924 four-letter script code (Latn, Cyrl, Hant, Arab)."
174
- title:
175
- type: string
176
- subject:
177
- type: string
178
- message:
179
- type: string
180
- considering:
181
- type: string
179
+ title: { type: string }
180
+ subject: { type: string }
181
+ message: { type: string }
182
+ considering: { type: string }
182
183
  considerations:
183
184
  type: array
184
185
  items:
@@ -196,32 +197,25 @@ $defs:
196
197
  type: object
197
198
  description: "Per-language canonical source URL (e.g. one PDF per language)."
198
199
  additionalProperties: false
199
- required: [ref, format, language_code]
200
+ required: [ref]
200
201
  properties:
201
- ref:
202
- type: string
203
- description: "The URL string."
204
- format:
205
- type: string
206
- description: "Document format, e.g. 'pdf', 'html'."
202
+ ref: { type: string }
203
+ format: { type: string }
207
204
  language_code:
208
205
  type: string
209
206
  pattern: "^[a-z]{3}$"
210
207
  kind:
211
- type: string
212
- description: "Optional discriminator (agenda_pdf, minutes_pdf, ...)."
213
- enum: [agenda_pdf, minutes_pdf, resolutions_pdf, report_pdf, register_url, landing_page]
208
+ $ref: "#/$defs/SourceUrlKind"
214
209
 
215
210
  # ====================================================================
216
211
  # Top-level structures.
217
212
  # ====================================================================
218
213
 
219
- Resolution:
214
+ Decision:
220
215
  type: object
221
216
  description: |
222
- A formal Resolution. Language-agnostic admin fields live here;
223
- every translatable field is wrapped inside `localizations[]`
224
- (one entry per available language; at least one is required).
217
+ A formal Decision. Language-agnostic admin fields live here;
218
+ every translatable field is wrapped inside `localizations[]`.
225
219
  additionalProperties: false
226
220
  required: [identifier, localizations]
227
221
  properties:
@@ -230,161 +224,814 @@ $defs:
230
224
  minItems: 1
231
225
  items:
232
226
  $ref: "#/$defs/StructuredIdentifier"
233
- type:
234
- $ref: "#/$defs/ResolutionType"
235
- doi:
236
- type: string
237
- urn:
238
- type: string
239
- agenda_item:
240
- type: string
227
+ kind:
228
+ $ref: "#/$defs/DecisionKind"
229
+ status:
230
+ $ref: "#/$defs/DecisionStatus"
231
+ body_type: { type: string }
232
+ doi: { type: string }
233
+ urn: { type: string }
234
+ agenda_item: { type: string }
241
235
  dates:
242
236
  type: array
243
237
  items:
244
- $ref: "#/$defs/ResolutionDate"
238
+ $ref: "#/$defs/DecisionDate"
245
239
  categories:
246
240
  type: array
247
- items:
248
- type: string
241
+ items: { type: string }
249
242
  meeting:
250
243
  $ref: "#/$defs/MeetingIdentifier"
251
244
  relations:
252
245
  type: array
253
246
  items:
254
- $ref: "#/$defs/ResolutionRelation"
247
+ $ref: "#/$defs/DecisionRelation"
255
248
  urls:
256
249
  type: array
257
250
  items:
258
251
  $ref: "#/$defs/Url"
252
+ brought_by_motions:
253
+ type: array
254
+ items: { type: string }
255
+ about_topics:
256
+ type: array
257
+ items: { type: string }
258
+ made_in_component: { type: string }
259
259
  localizations:
260
260
  type: array
261
261
  minItems: 1
262
262
  items:
263
263
  $ref: "#/$defs/Localization"
264
+ extensions:
265
+ type: array
266
+ items:
267
+ $ref: "#/$defs/MeetingExtension"
264
268
 
265
- ResolutionMetadata:
269
+ DecisionMetadata:
266
270
  type: object
267
271
  description: "Collection-level metadata (title, meeting date, source, source URLs, host venue)."
268
272
  additionalProperties: false
269
273
  properties:
270
- title:
271
- type: string
272
- description: "Default title (no language). For multilingual, use `title_localized[]`."
274
+ title: { type: string }
273
275
  title_localized:
274
276
  type: array
275
277
  items:
276
278
  $ref: "#/$defs/Localization"
277
- description: "Per-language title records (parallel pattern to Resolution#localizations)."
278
279
  date:
279
280
  type: string
280
281
  format: date
281
- description: "Meeting date."
282
- source:
283
- type: string
284
- description: "Source organization / secretariat that issued the collection."
282
+ source: { type: string }
285
283
  source_urls:
286
284
  type: array
287
285
  items:
288
286
  $ref: "#/$defs/SourceUrl"
289
- description: "Canonical URLs to the source PDFs in each available language."
290
287
  city:
291
288
  type: string
292
289
  pattern: "^[A-Z]{2}[A-Z0-9]{3}$"
293
- description: "UN/LOCODE of the host city (5-char: 2-letter ISO 3166-1 country + 3-char location, e.g. FRPAR, CNSHA, HKHKG). Supersedes the IATA city code."
294
290
  country_code:
295
291
  type: string
296
- description: "ISO 3166-1 alpha-2 country code (2-letter, e.g. DE, FR, JP) of the host country."
297
- meeting_urn:
292
+ pattern: "^[A-Z]{2}$"
293
+ meeting_urn: { type: string }
294
+ body_vocabulary:
295
+ type: array
296
+ items:
297
+ $ref: "#/$defs/BodyVocabularyEntry"
298
+ extensions:
299
+ type: array
300
+ items:
301
+ $ref: "#/$defs/MeetingExtension"
302
+
303
+ # ====================================================================
304
+ # Body vocabulary (v2.1, TODO.refactor/46).
305
+ # ====================================================================
306
+
307
+ BodyVocabularyEntry:
308
+ type: object
309
+ description: |
310
+ One entry in a per-dataset body_vocabulary list. Maps a free-form
311
+ body_type to a short canonical value. SSOT for body_type ->
312
+ canonical_type resolution within the declaring collection.
313
+ additionalProperties: false
314
+ properties:
315
+ body_type: { type: string }
316
+ canonical_type: { type: string }
317
+ definition: { type: string }
318
+
319
+ # ====================================================================
320
+ # Procedural: Motion, Voting, VotingCounts.
321
+ # ====================================================================
322
+
323
+ Motion:
324
+ type: object
325
+ description: "A procedural act that brings a Decision."
326
+ additionalProperties: false
327
+ properties:
328
+ identifier: { type: string }
329
+ urn: { type: string }
330
+ text: { type: string }
331
+ mover: { $ref: "#/$defs/Person" }
332
+ seconders:
333
+ type: array
334
+ items:
335
+ $ref: "#/$defs/Person"
336
+ status: { $ref: "#/$defs/MotionStatus" }
337
+ introduced_at: { type: string, format: date-time }
338
+ proposed_decision: { type: string }
339
+ resulting_decision: { type: string }
340
+ # Pilot EntityRef field (v2.1, TODO.refactor/44). Parallel to
341
+ # `resulting_decision`. Prefer in new code; bare String removed v3.0.
342
+ resulting_decision_ref: { $ref: "#/$defs/EntityRef" }
343
+ votings:
344
+ type: array
345
+ items: { type: string }
346
+ extensions:
347
+ type: array
348
+ items:
349
+ $ref: "#/$defs/MeetingExtension"
350
+
351
+ VotingCounts:
352
+ type: object
353
+ description: "Tally for a Voting instance."
354
+ additionalProperties: false
355
+ properties:
356
+ ayes: { type: integer }
357
+ noes: { type: integer }
358
+ abstentions: { type: integer }
359
+ absent: { type: integer }
360
+
361
+ Voting:
362
+ type: object
363
+ description: "State machine for a single vote on a Motion."
364
+ additionalProperties: false
365
+ properties:
366
+ identifier: { type: string }
367
+ urn: { type: string }
368
+ on_motion: { type: string }
369
+ status: { $ref: "#/$defs/VotingStatus" }
370
+ voting_method: { $ref: "#/$defs/VotingMethod" }
371
+ called_by: { $ref: "#/$defs/Person" }
372
+ called_at: { type: string, format: date-time }
373
+ result_declared_at: { type: string, format: date-time }
374
+ result: { $ref: "#/$defs/VotingOutcome" }
375
+ counts: { $ref: "#/$defs/VotingCounts" }
376
+ casting_vote: { $ref: "#/$defs/VoteRecord" }
377
+ vote_records:
378
+ type: array
379
+ items:
380
+ $ref: "#/$defs/VoteRecord"
381
+ extensions:
382
+ type: array
383
+ items:
384
+ $ref: "#/$defs/MeetingExtension"
385
+
386
+ # ====================================================================
387
+ # Topic and friends.
388
+ # ====================================================================
389
+
390
+ TopicDocument:
391
+ type: object
392
+ description: "Text-bearing document about a Topic."
393
+ additionalProperties: false
394
+ properties:
395
+ identifier: { type: string }
396
+ title: { type: string }
397
+ version: { type: string }
398
+ status: { type: string }
399
+ url: { type: string }
400
+ format: { type: string }
401
+ language_code:
402
+ type: string
403
+ pattern: "^[a-z]{3}$"
404
+ extensions:
405
+ type: array
406
+ items:
407
+ $ref: "#/$defs/MeetingExtension"
408
+
409
+ TopicAsset:
410
+ type: object
411
+ description: "Non-text resource about a Topic."
412
+ additionalProperties: false
413
+ properties:
414
+ identifier: { type: string }
415
+ title: { type: string }
416
+ kind: { type: string }
417
+ url: { type: string }
418
+ format: { type: string }
419
+ extensions:
420
+ type: array
421
+ items:
422
+ $ref: "#/$defs/MeetingExtension"
423
+
424
+ Topic:
425
+ type: object
426
+ description: "The subject of discussion at a Meeting."
427
+ additionalProperties: false
428
+ properties:
429
+ identifier: { type: string }
430
+ urn: { type: string }
431
+ title: { type: string }
432
+ description: { type: string }
433
+ status: { $ref: "#/$defs/TopicStatus" }
434
+ resumption_of: { type: string }
435
+ documents:
436
+ type: array
437
+ items:
438
+ $ref: "#/$defs/TopicDocument"
439
+ assets:
440
+ type: array
441
+ items:
442
+ $ref: "#/$defs/TopicAsset"
443
+ references:
444
+ type: array
445
+ items:
446
+ $ref: "#/$defs/Reference"
447
+ motions:
448
+ type: array
449
+ items: { type: string }
450
+ decisions:
451
+ type: array
452
+ items: { type: string }
453
+ extensions:
454
+ type: array
455
+ items:
456
+ $ref: "#/$defs/MeetingExtension"
457
+
458
+ # ====================================================================
459
+ # Polymorphic Venue (flat wire shape; kind discriminates subtype).
460
+ # ====================================================================
461
+
462
+ Venue:
463
+ type: object
464
+ description: |
465
+ Polymorphic place where a Meeting happens. `kind` discriminates
466
+ physical vs virtual; all fields from both subtypes live here as
467
+ optional siblings. Validators (Edoxen::VenueValidator) enforce
468
+ that fields match `kind`.
469
+ additionalProperties: false
470
+ properties:
471
+ kind: { $ref: "#/$defs/VenueKind" }
472
+ name: { type: string }
473
+ label: { type: string }
474
+ description: { type: string }
475
+ capacity: { type: integer }
476
+ url: { type: string }
477
+ contact_methods:
478
+ type: array
479
+ items:
480
+ $ref: "#/$defs/ContactMethod"
481
+
482
+ # Physical-venue fields.
483
+ unlocode:
484
+ type: string
485
+ pattern: "^[A-Z]{2}[A-Z0-9]{3}$"
486
+ iata_code:
487
+ type: string
488
+ pattern: "^[A-Z]{3}$"
489
+ address: { type: string }
490
+ city: { type: string }
491
+ country_code:
492
+ type: string
493
+ pattern: "^[A-Z]{2}$"
494
+ lat: { type: number }
495
+ lon: { type: number }
496
+ building: { type: string }
497
+ floor: { type: string }
498
+ room: { type: string }
499
+ access_notes: { type: string }
500
+
501
+ # Virtual-venue fields.
502
+ uri: { type: string }
503
+ features:
504
+ type: array
505
+ items:
506
+ $ref: "#/$defs/VirtualFeature"
507
+ passcode: { type: string }
508
+ meeting_id: { type: string }
509
+ dial_in_numbers:
510
+ type: array
511
+ items: { type: string }
512
+ waiting_room: { type: boolean }
513
+ registration_required: { type: boolean }
514
+
515
+ extensions:
516
+ type: array
517
+ items:
518
+ $ref: "#/$defs/MeetingExtension"
519
+
520
+ PhysicalVenue:
521
+ description: "Marker — Venue with kind=physical. Wire shape is identical to Venue."
522
+ allOf:
523
+ - $ref: "#/$defs/Venue"
524
+
525
+ VirtualVenue:
526
+ description: "Marker — Venue with kind=virtual. Wire shape is identical to Venue."
527
+ allOf:
528
+ - $ref: "#/$defs/Venue"
529
+
530
+ # ====================================================================
531
+ # Recurrence (ISO 8601-2 §13 structured form).
532
+ # ====================================================================
533
+
534
+ RecurrenceByDay:
535
+ type: object
536
+ description: "BYDAY part of a recurrence. `ordinal` is null for 'every', +1 for first, -1 for last."
537
+ additionalProperties: false
538
+ properties:
539
+ ordinal: { type: integer }
540
+ weekday: { type: string }
541
+
542
+ Recurrence:
543
+ type: object
544
+ description: "Structured ISO 8601-2 §13 recurrence."
545
+ additionalProperties: false
546
+ properties:
547
+ freq: { $ref: "#/$defs/RecurrenceFreq" }
548
+ interval: { type: integer }
549
+ count: { type: integer }
550
+ until: { type: string, format: date-time }
551
+ by_day:
552
+ type: array
553
+ items:
554
+ $ref: "#/$defs/RecurrenceByDay"
555
+ by_month_day:
556
+ type: array
557
+ items: { type: integer }
558
+ by_month:
559
+ type: array
560
+ items: { type: integer }
561
+ by_week_no:
562
+ type: array
563
+ items: { type: integer }
564
+ by_year_day:
565
+ type: array
566
+ items: { type: integer }
567
+ by_hour:
568
+ type: array
569
+ items: { type: integer }
570
+ by_minute:
571
+ type: array
572
+ items: { type: integer }
573
+ by_second:
574
+ type: array
575
+ items: { type: integer }
576
+ by_set_pos:
577
+ type: array
578
+ items: { type: integer }
579
+ week_start: { type: string }
580
+ extensions:
581
+ type: array
582
+ items:
583
+ $ref: "#/$defs/MeetingExtension"
584
+
585
+ # ====================================================================
586
+ # Profile mechanism (ISO 8601-2 §15).
587
+ # ====================================================================
588
+
589
+ ExtensionAttribute:
590
+ type: object
591
+ description: |
592
+ One typed key/value pair within a MeetingExtension. Polymorphic on
593
+ value type so consumers don't re-parse strings back into Int/Float/
594
+ Bool/Date (v2.1 tighten, TODO.refactor/47).
595
+ additionalProperties: false
596
+ properties:
597
+ key: { type: string }
598
+ type: { type: string, enum: [string, integer, float, boolean, date, datetime] }
599
+
600
+ # String variant — wire name `value` (v2.0 back-compat).
601
+ value: { type: string }
602
+
603
+ # Typed variants (v2.1) — snake_case wire names (the gem uses
604
+ # snake_case Ruby attr names; lutaml-model emits them as-is).
605
+ integer_value: { type: integer }
606
+ float_value: { type: number }
607
+ boolean_value: { type: boolean }
608
+ date_value: { type: string, format: date }
609
+ date_time_value: { type: string, format: date-time }
610
+
611
+ MeetingExtension:
612
+ type: object
613
+ description: |
614
+ Profile-specific extension. Adopters register their namespace via
615
+ `profile` and discriminate via `kind`. Field semantics tightened
616
+ v2.1 (TODO.refactor/47): `kind` is the in-profile discriminator,
617
+ `ref` is the URN of an external profile document, and the
618
+ recursive `extensions[]` slot was removed (YAGNI — use dotted
619
+ keys in `attributes[]` for nesting).
620
+ additionalProperties: false
621
+ properties:
622
+ profile: { type: string }
623
+ kind: { type: string }
624
+ ref: { type: string }
625
+ attributes:
626
+ type: array
627
+ items:
628
+ $ref: "#/$defs/ExtensionAttribute"
629
+
630
+ # ====================================================================
631
+ # Shared structural types.
632
+ # ====================================================================
633
+
634
+ ContactMethodKind:
635
+ type: string
636
+ description: "Polymorphic communication channel kind."
637
+ enum: [phone, mobile, fax, email, url, mail, pager, message, other]
638
+
639
+ ContactMethod:
640
+ type: object
641
+ description: |
642
+ One polymorphic communication channel — phone, email, fax, url,
643
+ mail, etc. `kind` discriminates the channel; `value` carries the
644
+ address/number; `label` is a free-form display hint ("Office",
645
+ "Front desk"). OCP: new channel kinds are added via the
646
+ ContactMethodKind enum (or `other` + extensions).
647
+ additionalProperties: false
648
+ properties:
649
+ kind: { $ref: "#/$defs/ContactMethodKind" }
650
+ value: { type: string }
651
+ label: { type: string }
652
+ primary: { type: boolean }
653
+ extensions:
654
+ type: array
655
+ items:
656
+ $ref: "#/$defs/MeetingExtension"
657
+
658
+ ContactIdentifierKind:
659
+ type: string
660
+ description: "Polymorphic external identifier scheme for a Contact."
661
+ enum: [orcid, isni, wikidata, ror, ringgold, github, other]
662
+
663
+ ContactIdentifier:
664
+ type: object
665
+ description: |
666
+ One polymorphic external identifier — ORCID, ISNI, Wikidata QID,
667
+ ROR, Ringgold, GitHub handle, etc. Replaces the hard-coded `orcid`
668
+ field. OCP: new identifier schemes are added via the
669
+ ContactIdentifierKind enum (or `other` + extensions).
670
+ additionalProperties: false
671
+ properties:
672
+ kind: { $ref: "#/$defs/ContactIdentifierKind" }
673
+ value: { type: string }
674
+ extensions:
675
+ type: array
676
+ items:
677
+ $ref: "#/$defs/MeetingExtension"
678
+
679
+ Name:
680
+ type: object
681
+ description: |
682
+ Structured personal/organisational name (VCARD RFC 6350
683
+ conventions). `formatted` is the pre-built display string (FN);
684
+ the structured components (N) are stored separately for sorting,
685
+ indexing, or locale-aware rendering.
686
+ additionalProperties: false
687
+ properties:
688
+ formatted: { type: string }
689
+ family: { type: string }
690
+ given: { type: string }
691
+ additional: { type: string }
692
+ prefix: { type: string }
693
+ suffix: { type: string }
694
+ extensions:
695
+ type: array
696
+ items:
697
+ $ref: "#/$defs/MeetingExtension"
698
+
699
+ Contact:
700
+ type: object
701
+ description: |
702
+ VCARD-like abstract contact. Generalises Person for cases where
703
+ the contact may be a person, an organisation, a department, a
704
+ role ("Secretariat"), or any other entity with a name and one or
705
+ more communication channels.
706
+ additionalProperties: false
707
+ properties:
708
+ name: { $ref: "#/$defs/Name" }
709
+ kind: { type: string }
710
+ role: { type: string }
711
+ title: { type: string }
712
+ affiliation: { type: string }
713
+ contact_methods:
714
+ type: array
715
+ items:
716
+ $ref: "#/$defs/ContactMethod"
717
+ identifiers:
718
+ type: array
719
+ items:
720
+ $ref: "#/$defs/ContactIdentifier"
721
+ address: { type: string }
722
+ extensions:
723
+ type: array
724
+ items:
725
+ $ref: "#/$defs/MeetingExtension"
726
+
727
+ Person:
728
+ type: object
729
+ description: |
730
+ A Contact that is specifically an individual human. Inherits all
731
+ Contact fields. The old `email`, `phone`, and `orcid` fields are
732
+ replaced by entries in `contact_methods` (kind=email / kind=phone)
733
+ and `identifiers` (kind=orcid).
734
+ additionalProperties: false
735
+ properties:
736
+ name: { $ref: "#/$defs/Name" }
737
+ kind: { type: string }
738
+ role: { type: string }
739
+ title: { type: string }
740
+ affiliation: { type: string }
741
+ contact_methods:
742
+ type: array
743
+ items:
744
+ $ref: "#/$defs/ContactMethod"
745
+ identifiers:
746
+ type: array
747
+ items:
748
+ $ref: "#/$defs/ContactIdentifier"
749
+ address: { type: string }
750
+ extensions:
751
+ type: array
752
+ items:
753
+ $ref: "#/$defs/MeetingExtension"
754
+
755
+ Reference:
756
+ type: object
757
+ description: "Generic document reference."
758
+ additionalProperties: false
759
+ properties:
760
+ ref: { type: string }
761
+ kind: { type: string }
762
+ title: { type: string }
763
+
764
+ VoteRecord:
765
+ type: object
766
+ description: "One vote by one person on one decision/voting."
767
+ additionalProperties: false
768
+ required: [person, vote]
769
+ properties:
770
+ decision_ref: { type: string }
771
+ voting_ref: { type: string }
772
+ person: { $ref: "#/$defs/Person" }
773
+ affiliation: { type: string }
774
+ vote: { $ref: "#/$defs/VoteType" }
775
+ role: { type: string }
776
+ notes: { type: string }
777
+ extensions:
778
+ type: array
779
+ items:
780
+ $ref: "#/$defs/MeetingExtension"
781
+
782
+ Officer:
783
+ type: object
784
+ description: "A person holding a structural role in a Meeting."
785
+ additionalProperties: false
786
+ properties:
787
+ role: { $ref: "#/$defs/OfficerRole" }
788
+ person: { $ref: "#/$defs/Person" }
789
+ term_start: { type: string, format: date }
790
+ term_end: { type: string, format: date }
791
+ extensions:
792
+ type: array
793
+ items:
794
+ $ref: "#/$defs/MeetingExtension"
795
+
796
+ ComponentLocalization:
797
+ type: object
798
+ description: "Per-language content for a MeetingComponent."
799
+ additionalProperties: false
800
+ required: [language_code]
801
+ properties:
802
+ language_code:
803
+ type: string
804
+ pattern: "^[a-z]{3}$"
805
+ script:
298
806
  type: string
299
- description: "URN back-reference to the Meeting that produced this collection."
807
+ pattern: "^[A-Z][a-z]{3}$"
808
+ title: { type: string }
809
+ description: { type: string }
810
+
811
+ MeetingComponent:
812
+ type: object
813
+ description: "Flat sub-event of a Meeting."
814
+ additionalProperties: false
815
+ properties:
816
+ identifier: { type: string }
817
+ urn: { type: string }
818
+ kind: { $ref: "#/$defs/ComponentKind" }
819
+ body_type: { type: string }
820
+ title: { type: string }
821
+ description: { type: string }
822
+ starts_at: { type: string, format: date-time }
823
+ ends_at: { type: string, format: date-time }
824
+ time_label: { type: string }
825
+ venue_refs:
826
+ type: array
827
+ items: { type: string }
828
+ officers:
829
+ type: array
830
+ items:
831
+ $ref: "#/$defs/Officer"
832
+ agenda_ref: { type: string }
833
+ minutes_ref: { type: string }
834
+ attendance_refs:
835
+ type: array
836
+ items: { type: string }
837
+ localizations:
838
+ type: array
839
+ items:
840
+ $ref: "#/$defs/ComponentLocalization"
841
+ extensions:
842
+ type: array
843
+ items:
844
+ $ref: "#/$defs/MeetingExtension"
845
+
846
+ MeetingSeries:
847
+ type: object
848
+ description: "Parent of recurring Meeting instances."
849
+ additionalProperties: false
850
+ properties:
851
+ identifier:
852
+ type: array
853
+ items:
854
+ $ref: "#/$defs/StructuredIdentifier"
855
+ urn: { type: string }
856
+ name: { type: string }
857
+ description: { type: string }
858
+ recurrence: { $ref: "#/$defs/Recurrence" }
859
+ term: { type: string }
860
+ contact: { $ref: "#/$defs/Contact" }
861
+ hosts:
862
+ type: array
863
+ items:
864
+ $ref: "#/$defs/HostRef"
865
+ kind: { type: string }
866
+ meeting_refs:
867
+ type: array
868
+ items: { type: string }
869
+ extensions:
870
+ type: array
871
+ items:
872
+ $ref: "#/$defs/MeetingExtension"
873
+
874
+ HostRef:
875
+ type: object
876
+ description: "Typed reference to a hosting organization."
877
+ additionalProperties: false
878
+ required: [ref]
879
+ properties:
880
+ ref: { type: string }
881
+ type: { $ref: "#/$defs/HostType" }
882
+ role: { type: string }
883
+ contact: { $ref: "#/$defs/Contact" }
300
884
 
301
885
  # ====================================================================
302
- # Enums. Each is referenced by its $ref name above. The schema
303
- # <-> Ruby enum sync spec asserts each value-list matches the
304
- # corresponding Edoxen::Enums::* frozen array.
886
+ # Enums. Each value-list must equal the matching Edoxen::Enums::*
887
+ # frozen array; spec/edoxen/schema_enum_sync_spec.rb enforces.
305
888
  # ====================================================================
306
889
 
307
890
  ActionType:
308
891
  type: string
309
- description: "Edoxen::Enums::ACTION_TYPE — LUTAML action.lutaml (deduped)."
310
- enum:
311
- - adopts
312
- - thanks
313
- - approves
314
- - decides
315
- - declares
316
- - asks
317
- - invites
318
- - resolves
319
- - confirms
320
- - welcomes
321
- - recommends
322
- - requests
323
- - congratulates
324
- - instructs
325
- - urges
326
- - appoints
327
- - calls-upon
328
- - encourages
329
- - affirms
330
- - elects
331
- - authorizes
332
- - charges
333
- - states
334
- - remarks
335
- - judges
336
- - sanctions
337
- - abrogates
338
- - empowers
892
+ description: |
893
+ Action verb. Edoxen::Enums::ACTION_TYPE lists the canonical set
894
+ (28 values). The schema is permissive — adopters may use
895
+ body-specific verbs (e.g. "scopes", "directs", "establishes")
896
+ outside the canonical set. The canonical set is advisory; the
897
+ body_vocabulary mechanism (v2.1, TODO.refactor/46) will
898
+ eventually map body-specific verbs to canonical types.
339
899
 
900
+ ConsiderationType:
340
901
  ConsiderationType:
341
902
  type: string
342
- description: "Edoxen::Enums::CONSIDERATION_TYPE — LUTAML consideration.lutaml (deduped)."
343
- enum:
344
- - having
345
- - noting
346
- - recognizing
347
- - acknowledging
348
- - recalling
349
- - reaffirming
350
- - considering
351
- - taking-into-account
352
- - pursuant-to
353
- - bearing-in-mind
354
- - emphasizing
355
- - concerned
356
- - accepts
357
- - observing
358
- - referring
359
- - acting
360
- - empowers
903
+ description: |
904
+ Consideration verb. Edoxen::Enums::CONSIDERATION_TYPE lists the
905
+ canonical set. The schema is permissive — adopters may use
906
+ body-specific verbs outside the canonical set (same rationale as
907
+ ActionType).
361
908
 
362
909
  ApprovalType:
363
910
  type: string
364
- description: "Edoxen::Enums::APPROVAL_TYPE — LUTAML approval_type.lutaml."
911
+ description: "Edoxen::Enums::APPROVAL_TYPE."
365
912
  enum: [affirmative, negative]
366
913
 
367
914
  ApprovalDegree:
368
915
  type: string
369
- description: "Edoxen::Enums::APPROVAL_DEGREE — LUTAML approval_degree.lutaml."
916
+ description: "Edoxen::Enums::APPROVAL_DEGREE."
370
917
  enum: [unanimous, majority, minority]
371
918
 
372
- ResolutionType:
919
+ DecisionKind:
373
920
  type: string
374
- description: "Edoxen::Enums::RESOLUTION_TYPE — LUTAML resolution_type.lutaml."
375
- enum: [resolution, recommendation, decision, declaration]
921
+ description: "Edoxen::Enums::DECISION_KIND."
922
+ enum: [resolution, order, ruling, determination, recommendation, statement, finding, opinion, other]
376
923
 
377
- ResolutionDateType:
924
+ DecisionKindCanonical:
378
925
  type: string
379
- description: "Edoxen::Enums::RESOLUTION_DATE_TYPE — LUTAML resolution_date_type.lutaml."
380
- enum: [adoption, drafted, discussed]
926
+ description: |
927
+ Edoxen::Enums::DECISION_KIND_CANONICAL short abstract set (v2.1,
928
+ TODO.refactor/46). Bodies extend via `body_type` + per-dataset
929
+ `body_vocabulary[]`.
930
+ enum: [decision, recommendation, statement, finding, other]
381
931
 
382
- ResolutionRelationType:
932
+ DecisionStatus:
383
933
  type: string
384
- description: "Edoxen::Enums::RESOLUTION_RELATION_TYPE — LUTAML resolution_relation_type.lutaml."
385
- enum: [annexOf, hasAnnex, updates, refines, replaces, considers]
934
+ description: "Edoxen::Enums::DECISION_STATUS."
935
+ enum: [draft, proposed, under_consideration, decided, negatived, withdrawn, deferred]
936
+
937
+ DecisionDateType:
938
+ type: string
939
+ description: "Edoxen::Enums::DECISION_DATE_TYPE."
940
+ enum: [adoption, drafted, discussed, proposed, decided, negatived, withdrawn, published, effective]
941
+
942
+ DecisionRelationType:
943
+ type: string
944
+ description: "Edoxen::Enums::DECISION_RELATION_TYPE."
945
+ enum: [annex_of, has_annex, updates, refines, replaces, considers, cites]
946
+
947
+ MotionStatus:
948
+ type: string
949
+ description: "Edoxen::Enums::MOTION_STATUS."
950
+ enum: [introduced, seconded, debating, question_put, voting, carried, negatived, withdrawn, lapsed]
951
+
952
+ VotingStatus:
953
+ type: string
954
+ description: "Edoxen::Enums::VOTING_STATUS."
955
+ enum: [called, in_progress, decided, withdrawn, deferred]
956
+
957
+ VotingMethod:
958
+ type: string
959
+ description: "Edoxen::Enums::VOTING_METHOD."
960
+ enum: [voice, division, show_of_hands, roll_call, electronic, secret_ballot, unanimous_consent, consensus]
961
+
962
+ VotingOutcome:
963
+ type: string
964
+ description: "Edoxen::Enums::VOTING_OUTCOME."
965
+ enum: [passed, negatived, tied, withdrawn]
966
+
967
+ TopicStatus:
968
+ type: string
969
+ description: "Edoxen::Enums::TOPIC_STATUS."
970
+ enum: [open, under_discussion, decided, deferred, withdrawn]
971
+
972
+ VenueKind:
973
+ type: string
974
+ description: "Edoxen::Enums::VENUE_KIND."
975
+ enum: [physical, virtual]
976
+
977
+ VirtualFeature:
978
+ type: string
979
+ description: "Edoxen::Enums::VIRTUAL_FEATURE."
980
+ enum: [audio, video, chat, phone, screen, feed]
981
+
982
+ Visibility:
983
+ type: string
984
+ description: "Edoxen::Enums::VISIBILITY."
985
+ enum: [public, private, confidential]
986
+
987
+ AttendanceRole:
988
+ type: string
989
+ description: "Edoxen::Enums::ATTENDANCE_ROLE."
990
+ enum: [chair, required, optional, non_participant]
991
+
992
+ AttendanceResponse:
993
+ type: string
994
+ description: "Edoxen::Enums::ATTENDANCE_RESPONSE."
995
+ enum: [pending, confirmed, declined, tentative, delegated]
996
+
997
+ ComponentKind:
998
+ type: string
999
+ description: "Edoxen::Enums::COMPONENT_KIND."
1000
+ enum: [track, session, debate, breakout, bof, plenary_session, working_group_session, committee_of_the_whole, keynote, address, statement, question_time, opening, closing, break, reception, registration, networking, other]
1001
+
1002
+ ComponentKindCanonical:
1003
+ type: string
1004
+ description: |
1005
+ Edoxen::Enums::COMPONENT_KIND_CANONICAL — short abstract set
1006
+ (v2.1, TODO.refactor/46).
1007
+ enum: [deliberative, working, ceremonial, break, other]
1008
+
1009
+ OfficerRole:
1010
+ type: string
1011
+ description: "Edoxen::Enums::OFFICER_ROLE."
1012
+ enum: [chair, vice_chair, deputy_chair, secretary, treasurer, parliamentarian, presiding_officer, sergeant_at_arms, other]
1013
+
1014
+ RecurrenceFreq:
1015
+ type: string
1016
+ description: "Edoxen::Enums::RECURRENCE_FREQ."
1017
+ enum: [secondly, minutely, hourly, daily, weekly, monthly, yearly]
386
1018
 
387
1019
  UrlKind:
388
1020
  type: string
389
1021
  description: "Edoxen::Enums::URL_KIND."
390
1022
  enum: [access, report]
1023
+
1024
+ SourceUrlKind:
1025
+ type: string
1026
+ description: "Edoxen::Enums::SOURCE_URL_KIND."
1027
+ enum: [agenda_pdf, minutes_pdf, decisions_pdf, report_pdf, register_url, landing_page]
1028
+
1029
+ HostType:
1030
+ type: string
1031
+ description: "Edoxen::Enums::HOST_TYPE."
1032
+ enum: [national_body, liaison, associate, organizer]
1033
+
1034
+ VoteType:
1035
+ type: string
1036
+ description: "Edoxen::Enums::VOTE_TYPE."
1037
+ enum: [affirmative, negative, abstain, absent, not_applicable]