edoxen 0.7.2 → 0.8.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +26 -1
  3. data/.rubocop_todo.yml +1 -1
  4. data/CHANGELOG.md +133 -0
  5. data/README.adoc +414 -182
  6. data/edoxen.gemspec +10 -10
  7. data/lib/edoxen/action.rb +5 -4
  8. data/lib/edoxen/agenda.rb +4 -7
  9. data/lib/edoxen/agenda_item.rb +18 -5
  10. data/lib/edoxen/approval.rb +2 -2
  11. data/lib/edoxen/attendance.rb +16 -10
  12. data/lib/edoxen/body.rb +26 -0
  13. data/lib/edoxen/body_register.rb +17 -0
  14. data/lib/edoxen/body_vocabulary_entry.rb +24 -0
  15. data/lib/edoxen/body_vocabulary_host.rb +40 -0
  16. data/lib/edoxen/cli.rb +175 -115
  17. data/lib/edoxen/consideration.rb +6 -4
  18. data/lib/edoxen/contact.rb +37 -0
  19. data/lib/edoxen/contact_identifier.rb +13 -0
  20. data/lib/edoxen/contact_method.rb +16 -0
  21. data/lib/edoxen/contact_register.rb +26 -0
  22. data/lib/edoxen/date_time_range.rb +16 -0
  23. data/lib/edoxen/deadline.rb +1 -1
  24. data/lib/edoxen/decision.rb +57 -0
  25. data/lib/edoxen/decision_collection.rb +10 -0
  26. data/lib/edoxen/decision_date.rb +9 -0
  27. data/lib/edoxen/decision_metadata.rb +27 -0
  28. data/lib/edoxen/decision_relation.rb +11 -0
  29. data/lib/edoxen/declaration.rb +25 -0
  30. data/lib/edoxen/entity_ref.rb +74 -0
  31. data/lib/edoxen/entity_resolver.rb +68 -0
  32. data/lib/edoxen/enums.rb +123 -9
  33. data/lib/edoxen/error.rb +1 -1
  34. data/lib/edoxen/extension_attribute.rb +78 -0
  35. data/lib/edoxen/host_ref.rb +1 -1
  36. data/lib/edoxen/link_checker.rb +143 -34
  37. data/lib/edoxen/localized_name.rb +16 -0
  38. data/lib/edoxen/localized_string.rb +22 -0
  39. data/lib/edoxen/meeting.rb +36 -42
  40. data/lib/edoxen/meeting_collection.rb +1 -2
  41. data/lib/edoxen/meeting_collection_metadata.rb +4 -2
  42. data/lib/edoxen/meeting_component.rb +37 -0
  43. data/lib/edoxen/meeting_extension.rb +25 -0
  44. data/lib/edoxen/meeting_identifier.rb +1 -1
  45. data/lib/edoxen/meeting_series.rb +26 -0
  46. data/lib/edoxen/minutes.rb +3 -4
  47. data/lib/edoxen/minutes_section.rb +12 -13
  48. data/lib/edoxen/motion.rb +51 -0
  49. data/lib/edoxen/name.rb +29 -0
  50. data/lib/edoxen/officer.rb +19 -0
  51. data/lib/edoxen/officers_host.rb +19 -0
  52. data/lib/edoxen/person.rb +5 -9
  53. data/lib/edoxen/physical_venue.rb +15 -0
  54. data/lib/edoxen/recurrence.rb +27 -0
  55. data/lib/edoxen/recurrence_by_day.rb +10 -0
  56. data/lib/edoxen/reference.rb +1 -1
  57. data/lib/edoxen/reference_data.rb +34 -44
  58. data/lib/edoxen/schema_validator.rb +3 -4
  59. data/lib/edoxen/source_url.rb +4 -4
  60. data/lib/edoxen/statement.rb +21 -0
  61. data/lib/edoxen/structured_identifier.rb +2 -2
  62. data/lib/edoxen/topic.rb +39 -0
  63. data/lib/edoxen/topic_asset.rb +14 -0
  64. data/lib/edoxen/topic_document.rb +16 -0
  65. data/lib/edoxen/urn_for.rb +68 -0
  66. data/lib/edoxen/venue.rb +81 -0
  67. data/lib/edoxen/venue_register.rb +18 -0
  68. data/lib/edoxen/venue_validator.rb +74 -0
  69. data/lib/edoxen/version.rb +1 -1
  70. data/lib/edoxen/virtual_venue.rb +15 -0
  71. data/lib/edoxen/vote_record.rb +17 -13
  72. data/lib/edoxen/voting.rb +45 -0
  73. data/lib/edoxen/voting_counts.rb +23 -0
  74. data/lib/edoxen.rb +51 -15
  75. data/schema/edoxen.yaml +1427 -246
  76. data/schema/meeting.yaml +1311 -308
  77. data/sig/edoxen.rbs +581 -1
  78. metadata +66 -22
  79. data/lib/edoxen/_metadata.rb.deprecated +0 -57
  80. data/lib/edoxen/localization.rb +0 -18
  81. data/lib/edoxen/location.rb +0 -15
  82. data/lib/edoxen/meeting_localization.rb +0 -15
  83. data/lib/edoxen/resolution.rb +0 -43
  84. data/lib/edoxen/resolution_collection.rb +0 -10
  85. data/lib/edoxen/resolution_date.rb +0 -11
  86. data/lib/edoxen/resolution_metadata.rb +0 -30
  87. data/lib/edoxen/resolution_relation.rb +0 -11
  88. data/lib/edoxen/resolution_set.rb +0 -17
  89. data/lib/edoxen/schedule_item.rb +0 -27
  90. data/lib/edoxen/schedule_item_localization.rb +0 -21
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Edoxen
4
- # Basis for a resolution: a verb (having, noting, considering, ...) plus
5
- # one effective date and the elaborated reasoning.
4
+ # Basis for a Decision: a verb (having, noting, considering, ...) plus
5
+ # one effective date and the per-field Localized reasoning. Each
6
+ # `Consideration` belongs to a `Decision`; the message field carries
7
+ # one `LocalizedString` per ISO 24229 spelling.
6
8
  class Consideration < Lutaml::Model::Serializable
7
9
  attribute :type, :string, values: Enums::CONSIDERATION_TYPE
8
- attribute :date_effective, ResolutionDate
9
- attribute :message, :string
10
+ attribute :date_effective, DecisionDate
11
+ attribute :message, LocalizedString, collection: true
10
12
  end
11
13
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # VCARD-like abstract contact. Generalises Person for cases where the
5
+ # contact may be a person, an organisation, a department, a role
6
+ # ("Secretariat"), or any other entity with a name and one or more
7
+ # communication channels.
8
+ #
9
+ # 1.0 (per-field localization, ISO 24229):
10
+ # - All translatable fields are Localized<String/Name>[0..*]
11
+ # (one entry per spelling/conversion system code).
12
+ # - Added: `urn` for registry storage; `ref` for reference-by-URN.
13
+ #
14
+ # Reference vs inline: when `ref` is set, treat this Contact as a
15
+ # reference (other fields ignored). When `ref` is unset, the Contact
16
+ # is inline (full data). Both patterns are valid on every field that
17
+ # accepts a Contact.
18
+ class Contact < Lutaml::Model::Serializable
19
+ attribute :ref, :string
20
+ attribute :local_ref, :string
21
+ attribute :urn, :string
22
+ attribute :name, LocalizedName, collection: true
23
+ attribute :kind, :string
24
+ attribute :role, :string
25
+ attribute :title, LocalizedString, collection: true
26
+ attribute :affiliation, LocalizedString, collection: true
27
+ attribute :contact_methods, ContactMethod, collection: true
28
+ attribute :identifiers, ContactIdentifier, collection: true
29
+ attribute :address, LocalizedString, collection: true
30
+ attribute :extensions, MeetingExtension, collection: true
31
+
32
+ def reference?
33
+ (!ref.nil? && !ref.to_s.empty?) ||
34
+ (!local_ref.nil? && !local_ref.to_s.empty?)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # One polymorphic external identifier for a Contact — ORCID, ISNI,
5
+ # Wikidata QID, ROR, Ringgold, GitHub handle, etc. Replaces the hard-
6
+ # coded `orcid` field. OCP: new identifier schemes are added via the
7
+ # ContactIdentifierKind enum (or `other` + extensions).
8
+ class ContactIdentifier < Lutaml::Model::Serializable
9
+ attribute :kind, :string, values: Enums::CONTACT_IDENTIFIER_KIND
10
+ attribute :value, :string
11
+ attribute :extensions, MeetingExtension, collection: true
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # One polymorphic communication channel — phone, email, fax, url, mail,
5
+ # etc. Replaces the hard-coded `email` / `phone` fields that used to
6
+ # live on Person and Venue. New channel kinds are added via the
7
+ # ContactMethodKind enum (or `other` + extensions for adopter-specific
8
+ # channels); the model itself never needs to change (OCP).
9
+ class ContactMethod < Lutaml::Model::Serializable
10
+ attribute :kind, :string, values: Enums::CONTACT_METHOD_KIND
11
+ attribute :value, :string
12
+ attribute :label, :string
13
+ attribute :primary, :boolean
14
+ attribute :extensions, MeetingExtension, collection: true
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Registry of Contacts indexed by scoped URN. Members carry
5
+ # `urn: urn:edoxen:contact:{scope}:{local-id}`; the collection's
6
+ # `scope` MUST match the scope segment in member URNs.
7
+ #
8
+ # Other documents (Meeting, MeetingComponent, HostRef, etc.) reference
9
+ # contacts via `ref: urn:edoxen:contact:{scope}:{local-id}` and
10
+ # resolve against the matching ContactRegister.
11
+ #
12
+ # Storage: single YAML file (typical) or YAML Stream (one Contact per
13
+ # document, for large registries that need partial updates). Stream
14
+ # loading is a service-layer concern (file-system I/O); the model
15
+ # owns only the (de)serialisation of one collection.
16
+ class ContactRegister < Lutaml::Model::Serializable
17
+ attribute :scope, :string
18
+ attribute :title, LocalizedString, collection: true
19
+ attribute :contacts, Contact, collection: true
20
+ attribute :extensions, MeetingExtension, collection: true
21
+
22
+ def find_by_urn(urn)
23
+ contacts&.find { |c| c.urn == urn }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Start + end pair with sub-day precision. Parallel to DateRange;
5
+ # use when day granularity is insufficient — e.g.
6
+ # `Meeting#occurred_date_range` (a meeting that ran 09:00–11:30).
7
+ #
8
+ # The two are intentionally separate types so the granularity is
9
+ # visible at the type level: a caller who sees DateRange knows
10
+ # it is day-granularity; a caller who sees DateTimeRange knows
11
+ # sub-day precision is available.
12
+ class DateTimeRange < Lutaml::Model::Serializable
13
+ attribute :start, :date_time
14
+ attribute :end, :date_time
15
+ end
16
+ end
@@ -5,6 +5,6 @@ module Edoxen
5
5
  # registration cutoff, report-submission deadline, etc.
6
6
  class Deadline < Lutaml::Model::Serializable
7
7
  attribute :date, :date
8
- attribute :description, :string
8
+ attribute :description, LocalizedString, collection: true
9
9
  end
10
10
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # A formal Decision — the base type for any outcome adopted by a Meeting.
5
+ # Was `Resolution` in legacy; renamed in 1.0 because the same formal concept
6
+ # has many names across bodies (resolution, order, ruling, determination,
7
+ # finding, opinion). `Resolution` is one `kind` value, not a class name.
8
+ #
9
+ # 1.0 (per-field localization, ISO 24229):
10
+ # - Removed `localizations[]` collection.
11
+ # - Per-field LocalizedString for title, subject, message, considering.
12
+ class Decision < Lutaml::Model::Serializable
13
+ attribute :identifier, StructuredIdentifier, collection: true
14
+ attribute :kind, :string, values: Enums::DECISION_KIND
15
+ attribute :status, :string, values: Enums::DECISION_STATUS
16
+ attribute :doi, :string
17
+ attribute :urn, :string
18
+ attribute :agenda_item, :string
19
+ attribute :dates, DecisionDate, collection: true
20
+ attribute :categories, :string, collection: true
21
+ attribute :meeting, MeetingIdentifier
22
+ attribute :relations, DecisionRelation, collection: true
23
+ attribute :urls, Url, collection: true
24
+ attribute :brought_by_motions, :string, collection: true
25
+ attribute :about_topics, :string, collection: true
26
+ attribute :made_in_component, :string
27
+ attribute :body_type, :string
28
+ attribute :title, LocalizedString, collection: true
29
+ attribute :subject, LocalizedString, collection: true
30
+ attribute :message, LocalizedString, collection: true
31
+ attribute :considering, LocalizedString, collection: true
32
+ attribute :considerations, Consideration, collection: true
33
+ attribute :approvals, Approval, collection: true
34
+ attribute :actions, Action, collection: true
35
+ attribute :extensions, MeetingExtension, collection: true
36
+
37
+ # --- 1.0 derivation accessors (1.0 design review) -------------------
38
+
39
+ def brought_by_motions_in(meeting:)
40
+ return [] unless meeting && urn
41
+
42
+ meeting.motions.select do |motion|
43
+ motion.resulting_decision == urn ||
44
+ motion.resulting_decision_ref&.urn == urn
45
+ end
46
+ end
47
+
48
+ def component_in(meeting:)
49
+ return nil unless meeting && made_in_component
50
+
51
+ meeting.components&.find do |component|
52
+ component.identifier == made_in_component ||
53
+ component.urn == made_in_component
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Top-level container for a published decision collection: metadata
5
+ # plus the list of decisions. Was ResolutionCollection in legacy.
6
+ class DecisionCollection < Lutaml::Model::Serializable
7
+ attribute :metadata, DecisionMetadata
8
+ attribute :decisions, Decision, collection: true
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Date with semantic kind. Was ResolutionDate in legacy.
5
+ class DecisionDate < Lutaml::Model::Serializable
6
+ attribute :date, :date
7
+ attribute :type, :string, values: Enums::DECISION_DATE_TYPE
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Collection-level metadata: the localized title, the meeting date,
5
+ # the source secretariat, per-language source PDFs, and the host venue.
6
+ class DecisionMetadata < Lutaml::Model::Serializable
7
+ include BodyVocabularyHost
8
+
9
+ attribute :title, LocalizedString, collection: true
10
+ attribute :date, :date
11
+ attribute :source, :string
12
+ attribute :source_urls, SourceUrl, collection: true
13
+ attribute :city, :string
14
+ attribute :country_code, :string
15
+
16
+ # URN back-reference to the Meeting that produced this collection.
17
+ attribute :meeting_urn, :string
18
+
19
+ attribute :extensions, MeetingExtension, collection: true
20
+
21
+ def city_entry
22
+ return nil if city.nil? || city.to_s.empty?
23
+
24
+ Edoxen::ReferenceData.find_unlocode(city)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # Directed relation between two decisions, identified by their
5
+ # StructuredIdentifier (prefix + number).
6
+ class DecisionRelation < Lutaml::Model::Serializable
7
+ attribute :source, StructuredIdentifier
8
+ attribute :destination, StructuredIdentifier
9
+ attribute :type, :string, values: Enums::DECISION_RELATION_TYPE
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # A formal declaration made by one or more meeting members.
5
+ # Covers the two BS 0:2006 declaration types via the `kind`
6
+ # discriminator: `conflict_of_interest` and `ipr`.
7
+ #
8
+ # The IPR-specific fields (`ipr_subject_ref`, `ipr_target_ref`)
9
+ # are populated only when `kind == "ipr"`. This mirrors edoxen's
10
+ # "flat class, kind discriminates which subset is populated"
11
+ # pattern (cf. Venue: physical + virtual fields on one flat
12
+ # class).
13
+ #
14
+ # Attachment points: `Meeting#declarations[]` (per-meeting) and
15
+ # `Topic#declarations[]` (standing position that travels with
16
+ # the topic).
17
+ class Declaration < Lutaml::Model::Serializable
18
+ attribute :kind, :string, values: Enums::DECLARATION_KIND
19
+ attribute :description, LocalizedString, collection: true
20
+ attribute :party, Person, collection: true
21
+ attribute :ipr_subject_ref, EntityRef
22
+ attribute :ipr_target_ref, EntityRef
23
+ attribute :extensions, MeetingExtension, collection: true
24
+ end
25
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # EntityRef — a typed cross-reference between entities (1.0, 1.0 design review).
5
+ #
6
+ # Single identity: exactly one of `urn`, `identifier`, or `local_ref`
7
+ # must be set. Optional metadata: `kind`, `role`, `note`.
8
+ #
9
+ # Encapsulation:
10
+ # - Read the identity via `#resolved_identity` (returns the canonical
11
+ # form — URN string, StructuredIdentifier, or local key).
12
+ # - Validate via `#valid?` (true when exactly one identity field is
13
+ # set; `#multiple_identities?` flags the rare ambiguous case).
14
+ #
15
+ # Pilot: `Motion.resulting_decision_ref` (parallel to the existing
16
+ # `resulting_decision: String`). 1.0 will remove the String form.
17
+ class EntityRef < Lutaml::Model::Serializable
18
+ attribute :urn, :string
19
+ attribute :identifier, StructuredIdentifier
20
+ attribute :local_ref, :string
21
+
22
+ attribute :kind, :string
23
+ attribute :role, :string
24
+ attribute :note, :string
25
+
26
+ # True when exactly one identity field is set. The wire contract
27
+ # (1.0 design review + JSON-Schema) is XOR: setting 0 or ≥2 identity
28
+ # fields is a data error.
29
+ def valid?
30
+ identities_set.size == 1
31
+ end
32
+
33
+ # True when two or more identity fields are set. Useful for
34
+ # migration tooling that wants to warn on ambiguity without
35
+ # rejecting outright.
36
+ def multiple_identities?
37
+ identities_set.size > 1
38
+ end
39
+
40
+ # Returns the canonical identity (URN > identifier > local_ref
41
+ # precedence). Callers that need a single value should validate
42
+ # first via `#valid?`; on a multiple-identity ref the precedence
43
+ # is still applied but the data is suspect.
44
+ def resolved_identity
45
+ return urn if urn && !urn.to_s.empty?
46
+ return identifier if identifier
47
+ return local_ref if local_ref && !local_ref.to_s.empty?
48
+
49
+ nil
50
+ end
51
+
52
+ def to_s
53
+ identity = resolved_identity
54
+ return "(invalid EntityRef)" unless identity
55
+
56
+ case identity
57
+ when StructuredIdentifier then "#{identity.prefix}/#{identity.number}"
58
+ else identity.to_s
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def identities_set
65
+ [urn, identifier, local_ref].reject do |value|
66
+ case value
67
+ when nil then true
68
+ when String then value.empty?
69
+ else false
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # EntityResolver — resolves Contact/Venue/Body references by walking
5
+ # the three-tier scope hierarchy:
6
+ #
7
+ # 1. Inline: if the entity has no +ref+ and no +local_ref+, return
8
+ # it as-is (it carries full data).
9
+ # 2. Document-scoped: if +local_ref+ is set, look up in the
10
+ # document-scoped collection (e.g. Meeting#contacts) by matching
11
+ # +urn+ against +local_ref+.
12
+ # 3. Global register: if +ref+ is set, look up in the global
13
+ # register (e.g. ContactRegister) by matching +urn+ against +ref+.
14
+ #
15
+ # Pure service: no mutation. Returns the resolved entity or nil.
16
+ class EntityResolver
17
+ attr_reader :scoped_collections, :global_registers
18
+
19
+ def initialize(scoped: {}, registers: {})
20
+ @scoped_collections = scoped
21
+ @global_registers = registers
22
+ end
23
+
24
+ def resolve(entity)
25
+ return entity unless entity.reference?
26
+
27
+ resolve_local(entity) || resolve_global(entity)
28
+ end
29
+
30
+ def resolve_all(entities)
31
+ entities.map { |e| resolve(e) }
32
+ end
33
+
34
+ private
35
+
36
+ def resolve_local(entity)
37
+ return nil if entity.local_ref.nil? || entity.local_ref.to_s.empty?
38
+
39
+ collection = find_scoped_collection(entity.class)
40
+ return nil unless collection
41
+
42
+ collection.find { |member| member_key(member) == entity.local_ref }
43
+ end
44
+
45
+ def resolve_global(entity)
46
+ return nil if entity.ref.nil? || entity.ref.to_s.empty?
47
+
48
+ register = find_global_register(entity.class)
49
+ return nil unless register
50
+
51
+ register.find_by_urn(entity.ref)
52
+ end
53
+
54
+ def find_scoped_collection(klass)
55
+ @scoped_collections[klass] ||
56
+ @scoped_collections[klass.superclass]
57
+ end
58
+
59
+ def find_global_register(klass)
60
+ @global_registers[klass] ||
61
+ @global_registers[klass.superclass]
62
+ end
63
+
64
+ def member_key(member)
65
+ member.urn
66
+ end
67
+ end
68
+ end
data/lib/edoxen/enums.rb CHANGED
@@ -26,13 +26,33 @@ module Edoxen
26
26
  emphasizing concerned accepts observing referring acting empowers
27
27
  ].freeze
28
28
 
29
- RESOLUTION_TYPE = %w[resolution recommendation decision declaration].freeze
29
+ # --- Decision-side (was Resolution; renamed 2026-07) -----------------
30
+
31
+ # DecisionKind — what kind of formal outcome this is. `resolution` is
32
+ # one value; the same formal concept has many names across bodies
33
+ # (order, ruling, determination, finding, opinion, etc.).
34
+ DECISION_KIND = %w[
35
+ resolution order ruling determination
36
+ recommendation statement finding opinion other
37
+ ].freeze
38
+
39
+ # DecisionKindCanonical — short abstract classification of decision
40
+ # kinds. Cap: 5 canonical values (hard limit per the design review).
41
+ DECISION_KIND_CANONICAL = %w[decision recommendation statement finding other].freeze
30
42
 
31
- RESOLUTION_RELATION_TYPE = %w[
32
- annexOf hasAnnex updates refines replaces considers
43
+ # DecisionStatus — lifecycle state machine.
44
+ DECISION_STATUS = %w[
45
+ draft proposed under_consideration
46
+ decided negatived withdrawn deferred
33
47
  ].freeze
34
48
 
35
- RESOLUTION_DATE_TYPE = %w[adoption drafted discussed].freeze
49
+ DECISION_RELATION_TYPE = %w[
50
+ annex_of has_annex updates refines replaces considers cites
51
+ ].freeze
52
+
53
+ DECISION_DATE_TYPE = %w[
54
+ adoption drafted discussed proposed decided negatived withdrawn published effective
55
+ ].freeze
36
56
 
37
57
  APPROVAL_TYPE = %w[affirmative negative].freeze
38
58
 
@@ -44,31 +64,125 @@ module Edoxen
44
64
  # Mirrors edoxen-model/models/{meeting,agenda,...}_*.lutaml.
45
65
  # schema/meeting.yaml references these via $defs/<EnumName>.enum.
46
66
 
47
- MEETING_TYPE = %w[plenary working_group task_group ad_hoc joint conference_session].freeze
67
+ MEETING_TYPE = %w[
68
+ plenary working_group task_group ad_hoc joint general_assembly
69
+ committee subcommittee conference workshop seminar webinar hearing
70
+ markup board_meeting annual_general_meeting other
71
+ ].freeze
72
+
73
+ # MeetingTypeCanonical — short abstract classification of meeting
74
+ # types. Cap: 4 canonical values (no `other`).
75
+ MEETING_TYPE_CANONICAL = %w[plenary governing working advisory].freeze
48
76
 
49
77
  MEETING_STATUS = %w[upcoming completed cancelled].freeze
50
78
 
51
79
  AGENDA_STATUS = %w[draft final amended cancelled superseded].freeze
52
80
 
53
- AGENDA_ITEM_KIND = %w[numbered unnumbered header opening closing].freeze
81
+ AGENDA_ITEM_KIND = %w[numbered unnumbered header opening closing aob].freeze
54
82
 
55
- AGENDA_ITEM_OUTCOME = %w[discussed resolved deferred adopted withdrawn].freeze
83
+ AGENDA_ITEM_OUTCOME = %w[discussed resolved deferred adopted withdrawn carried negatived].freeze
56
84
 
57
85
  HOST_TYPE = %w[national_body liaison associate organizer].freeze
58
86
 
59
87
  MEETING_RELATION_TYPE = %w[
60
88
  continues_from continues_to joint_with supersedes superseded_by rescheduled_from rescheduled_to
89
+ parent_of child_of sibling_of depends_on
90
+ finish_to_start finish_to_finish start_to_start start_to_finish
61
91
  ].freeze
62
92
 
63
93
  SOURCE_URL_KIND = %w[
64
- agenda_pdf minutes_pdf resolutions_pdf report_pdf register_url landing_page
94
+ agenda_pdf minutes_pdf decisions_pdf report_pdf register_url landing_page
65
95
  ].freeze
66
96
 
67
- # P2.6 / P2.7 — Attendance and Vote records.
68
97
  PARTICIPATION_STATUS = %w[present absent apologies observer excused].freeze
69
98
 
70
99
  VOTE_TYPE = %w[
71
100
  affirmative negative abstain absent not_applicable
72
101
  ].freeze
102
+
103
+ # --- New in 1.0 (broadened scope) -----------------------------------
104
+
105
+ VENUE_KIND = %w[physical virtual].freeze
106
+
107
+ VIRTUAL_FEATURE = %w[audio video chat phone screen feed].freeze
108
+
109
+ VISIBILITY = %w[public private confidential].freeze
110
+
111
+ ATTENDANCE_ROLE = %w[chair required optional non_participant].freeze
112
+
113
+ # iCalendar PARTSTAT, plain English (no jargon).
114
+ ATTENDANCE_RESPONSE = %w[
115
+ pending confirmed declined tentative delegated
116
+ ].freeze
117
+
118
+ COMPONENT_KIND = %w[
119
+ track session debate breakout bof
120
+ plenary_session working_group_session committee_of_the_whole
121
+ keynote address statement question_time
122
+ opening closing break reception registration networking other
123
+ ].freeze
124
+
125
+ # ComponentKindCanonical — the short abstract set (1.0, 1.0 design review).
126
+ # `other` is a temporary escape while the vocabulary stabilises.
127
+ #
128
+ # Cap: 5 canonical values.
129
+ COMPONENT_KIND_CANONICAL = %w[deliberative working ceremonial break other].freeze
130
+
131
+ MOTION_STATUS = %w[
132
+ introduced seconded debating question_put voting
133
+ carried negatived withdrawn lapsed
134
+ ].freeze
135
+
136
+ # Terminal MotionStatus values — `Motion#pending?` is the complement.
137
+ # Kept as a separate constant (not derived) because the partition is
138
+ # semantic, not lexical; the spec in motion_spec.rb asserts the union
139
+ # equals MOTION_STATUS and the intersection is empty (MECE).
140
+ MOTION_TERMINAL = %w[carried negatived withdrawn lapsed].freeze
141
+
142
+ VOTING_STATUS = %w[called in_progress decided withdrawn deferred].freeze
143
+
144
+ VOTING_METHOD = %w[
145
+ voice division show_of_hands roll_call electronic
146
+ secret_ballot unanimous_consent consensus
147
+ ].freeze
148
+
149
+ VOTING_OUTCOME = %w[passed negatived tied withdrawn].freeze
150
+
151
+ TOPIC_STATUS = %w[open under_discussion decided deferred withdrawn].freeze
152
+
153
+ OFFICER_ROLE = %w[
154
+ chair vice_chair deputy_chair secretary treasurer
155
+ parliamentarian presiding_officer sergeant_at_arms other
156
+ ].freeze
157
+
158
+ RECURRENCE_FREQ = %w[
159
+ secondly minutely hourly daily weekly monthly yearly
160
+ ].freeze
161
+
162
+ # Polymorphic communication channel. OCP: adding a new kind does
163
+ # not change the model — only this enum (or use `other` + extensions).
164
+ CONTACT_METHOD_KIND = %w[
165
+ phone mobile fax email url mail pager message other
166
+ ].freeze
167
+
168
+ # Polymorphic external identifier for a Contact (ORCID, ISNI,
169
+ # Wikidata, ROR, etc.). OCP: adding a new scheme only extends this
170
+ # enum (or use `other` + extensions).
171
+ CONTACT_IDENTIFIER_KIND = %w[
172
+ orcid isni wikidata ror ringgold github other
173
+ ].freeze
174
+
175
+ # --- BS 0:2006 meeting-minutes concepts --------------------------------
176
+
177
+ # StatementKind — discriminates the three BS 0:2006 statement
178
+ # types. Adding a new kind is a one-line enum extension; the
179
+ # Statement model itself never needs to change (OCP).
180
+ STATEMENT_KIND = %w[statement comment standpoint].freeze
181
+
182
+ # DeclarationKind — discriminates the two BS 0:2006 declaration
183
+ # types (conflict of interest, IPR). Adding a new kind is a
184
+ # one-line enum extension; the Declaration model itself never
185
+ # needs to change (OCP).
186
+ DECLARATION_KIND = %w[conflict_of_interest ipr].freeze
73
187
  end
74
188
  end
data/lib/edoxen/error.rb CHANGED
@@ -7,7 +7,7 @@ module Edoxen
7
7
  # Unified validation failure shape. Produced by both:
8
8
  # * SchemaValidator — JSON-Schema violations and YAML syntax errors
9
9
  # carry source: :schema (or :syntax for Psych::SyntaxError).
10
- # * ResolutionCollection.from_yaml rescues in the CLI — model parse
10
+ # * DecisionCollection.from_yaml rescues in the CLI — model parse
11
11
  # failures carry source: :model.
12
12
  #
13
13
  # One type at the seam means callers (CLI, future renderers, tests)