Migration to 1.0

Edoxen 1.0 is a breaking release that broadens the model from a standards-body-specific Resolution model to a generic meeting/decision model with profile extensions for domain-specific concepts.

This page summarizes the breaking changes and shows the migration path. Full TODO roadmap: edoxen-model/TODO.refactor/.

TL;DR

legacy 1.0
Resolution class Decision class (with kind: resolution)
ResolutionType enum DecisionKind enum (9 values)
ResolutionCollection DecisionCollection
Meeting.virtual: Boolean Meeting.venues: Venue[] (polymorphic)
Meeting.chair / Meeting.secretary Meeting.officers[] with role enum
Meeting.schedule[] (ScheduleItem) Meeting.components[] (MeetingComponent)
Resolution alone Decision + Motion + Voting (procedural core)
(none) Topic + TopicDocument + TopicAsset
(none) MeetingSeries (recurring meetings)
(none) MeetingExtension (profile mechanism)
(none) Recurrence (ISO 8601-2 §13 structured)
(none) Venue polymorphic via PhysicalVenue / VirtualVenue subclasses

Why 1.0

The original model was designed for standards bodies (ISO, ITU, OIML, ILO, BIPM). The 2026 broadening extends coverage to:

  • Parliamentary bodies (UK Hansard, HK LegCo, US Congress)
  • Technical community meetings (IETF, W3C, Apache Foundation)
  • Academic conferences (Crossref-registered)
  • Corporate governance (board meetings)
  • Generic web/virtual meetings (iCalendar semantics)

The core schema is the intersection of all domains. Domain-specific concepts (Bill, Witness, Petition, Address, Statement, QuorumBell) live in profile extensions, not the core.

See references/ for the 9 reference corpora that drive the model.

Architectural principle: generic core + profile extensions

Every core entity has an extensions: MeetingExtension[0..*] slot. Adopters register their own profile namespace and define extension kinds.

extensions:
  - profile: legco
    kind: summoning_bell
    attributes:
      - key: sectionCode
        value: b2d
      - key: timestamp
        value: "2024-01-15T11:30:00Z"

This pattern follows ISO 8601-2 §15 (Profiles).

Polymorphic Venue

The most visible breaking change: Meeting.virtual: Boolean is gone. Replaced by a polymorphic Venue base class with two subclasses:

class Venue {
  kind: VenueKind  // physical | virtual
  name: String
  ...
}

class PhysicalVenue < Venue {
  unlocode: String
  iata_code: String
  address: String
  ...
}

class VirtualVenue < Venue {
  uri: String
  features: VirtualFeature[]
  passcode: String
  ...
}

A meeting can have multiple venues of either kind (hybrid meetings, multi-room conferences bridged by video, etc.).

UN/LOCODE and IATA codes are validated by the unlocodes and iata gems.

Procedural core: Decision, Motion, Voting

legacy had only Resolution. 1.0 adds Motion and Voting as distinct core entities:

  • Motion — a procedural act (“I move that…”). State machine: introduced → seconded → debating → question_put → voting → carried/negatived/withdrawn/lapsed
  • Voting — state machine for a single vote on a Motion: called → in_progress → decided/withdrawn/deferred. Multiple votings can occur on the same motion.
  • Decision — the formal outcome. A Decision results from a successful Motion (was a “draft resolution” before vote; becomes a real “resolution” after vote passes).

Sample YAML

Three samples in samples/:

  • oiml-ciml-56.yaml — standards body (CIML Meeting 56, unanimous consent)
  • legco-sitting-2024-01-15.yaml — parliamentary (HK LegCo, division vote)
  • hybrid-board-meeting.yaml — corporate (roll_call with casting_vote tie-breaker)

Migration script (TODO)

A scripts/migrate_to_v1.rb will be provided before 1.0.0 final. The transformation rules:

# Resolution → Decision
decision.kind = "resolution"  # was implicit
decision.status = "decided"   # was implicit
decision.brought_by_motions = []  # was implicit

# Meeting fields
meeting.venues = []
if meeting.virtual
  meeting.venues << VirtualVenue.new(uri: ...)
end
meeting.venues << PhysicalVenue.new(location.attributes)

# Chair/secretary → officers[]
meeting.officers = []
meeting.officers << Officer.new(role: "chair", person: meeting.chair) if meeting.chair
meeting.officers << Officer.new(role: "secretary", person: meeting.secretary) if meeting.secretary

References