Architecture
Edoxen is an information model for any meeting — the umbrella term
for the published record of a meeting (agenda, attendance, motions,
votings, decisions, minutes). The model is generic by design:
its core captures concepts shared across standards bodies,
parliaments, technical communities, academic conferences, and corporate
boards. Domain-specific concepts (Bill, Witness, Petition, Quorum Bell)
live in profile extensions via the MeetingExtension slot on every
core entity (ISO 8601-2 §15).
The model has two parallel top-level containers — one for meetings, one for decisions — both sharing the same multilingual plumbing. This page walks the model first, then the pipeline, then the multilingual plumbing.
The five concerns
A meeting has five concerns, each modelled as a distinct first-class entity rather than a column on a flat table:
- Meetings — the event itself: identifier, venues (polymorphic), officers (role-discriminated), agenda, components (flat sub-events), attendance, minutes, declarations (BS 0:2006 §7.6).
- Motions + Votings — the procedural record: who moved what, how the question was put, how members voted, what the chair declared.
- Decisions — the formal outcomes (resolution, order, ruling, determination, recommendation, statement, finding, opinion).
- Topics — the subjects of discussion: documents, assets,
references, standing statements + declarations, with cross-meeting
threading via
resumption_of. - Series — the recurring parent (annual plenaries, monthly board
meetings, IETF meeting series) with a
Recurrencerule.
The model also carries BS 0:2006 §7.6 statements and declarations as first-class core entities — attached to meetings, topics, and minutes sections. See BS 0:2006 Minutes for the full treatment.
Every core entity also carries an extensions: MeetingExtension[0..*]
slot for adopter-specific profiles.
Which file root?
Your first decision: which grain do you need?
MeetingCollection
Use when you need meeting-level detail — venues, officers, agenda, components, attendance, minutes, motions, votings, and the inline decisions.
- One
Meetingper sitting - Nested
Agenda,Minutes,Attendance,Motion[],Voting[],Decision[] - Polymorphic
Venue[](physical with UN/LOCODE + IATA, virtual with URI) officers[]with role enum (chair, secretary, treasurer, ...)
DecisionCollection
Use when you have a flat batch of decisions and the meeting-level detail is out of scope or published elsewhere.
- One
Decisionper outcome - Nested
Localization[](per-language) meeting: MeetingIdentifierback-references the originating meetingbrought_by_motions[]URN-links to the procedural record
The two views capture the same events from opposite directions: a
Meeting points to its Resolutions via resolution_refs[]; a
Resolution points back to its Meeting via meeting: MeetingIdentifier. You can use one, the other, or both — they’re
not mutually exclusive.
Layer 1 — The information model
Both collections share the same shape in miniature: an outer container
with metadata plus an array of children, where each child carries
admin fields once and localizations[] for per-language content. The
full model has more — Meeting has agendas, schedules, relations;
Resolution has considerations, actions, approvals — but the
shape is the same in both.
Resolution tree
This is the resolution-grain view — one file, many resolutions, each in one or more languages:
Meeting tree
This is the meeting-grain view — one file, many meetings, each with agenda items and the resolutions they adopted:
Read the trees top-down:
ResolutionCollectionis the file root for the resolution-grain view. It carriesResolutionMetadata(the publication’s title, dates, venue) and a list ofResolutionchildren.Resolutionis one decision. Admin fields (identifier,type,doi,urn,dates,meeting,categories) live here and are declared once, never per-language. Holds alocalizations[]array of monolingual renderings.MeetingCollectionis the file root for the meeting-grain view — one or manyMeetingchildren, each with their own agendas, schedules, chairs, and adopted resolutions.Meetingis one sitting: when, where, who chaired, what was on the agenda, who was in the room, how they voted, and what was said. Admin fields declared once;MeetingLocalization[]carries per-language content.Agendais the ordered list of items the meeting worked through. EachAgendaItemis enum-typed (numbered,header,opening,closing) and may carry aresolution_refto the resolution the meeting adopted on that item.Attendance+VoteRecordcapture who was in the room and how they voted. See Attendance & Votes.Minutesis the narrative record of the meeting — the running text of what was said, sliced by agenda item. EachMinutesSectioncarries anumberfield that joins toAgendaItem.label. See Minutes.- Across the grain: a
Resolution.meetingfield carries aMeetingIdentifierback to the meeting that adopted it. AMeeting.resolution_refs[]carries the strings the other way. AndVoteRecord.resolution_refjoins votes toResolution.identifier.
Localization, the multilingual plumbing:
Localization(underResolution) is one monolingual rendering of aResolution. ISO 639-3 + ISO 15924.MeetingLocalization(underMeeting) is one monolingual rendering of aMeeting. Carries the localized title, general area, and practical info.Action,Consideration,Approvalare the leaf content nodes inside aResolution’sLocalization. They never live directly on the resolution — anything translatable lives below aLocalization.
The hard rule: anything translatable lives below a Localization
or MeetingLocalization. Anything administrative lives above it.
That separation is what makes the multilingual model work — and what
makes EN↔FR drift visible on git diff.
The full LutaML/UML definitions live in
edoxen/edoxen-model.
The Ruby classes are generated from those definitions.
Layer 2 — The round-trip pipeline
A YAML file flows through three framework stages. None of them is hand-rolled:
| Stage | What it catches |
|---|---|
| 1. Parse | Malformed YAML — tabs-as-indent, duplicated keys, encoding issues. |
| 2. Decode | Type errors — language_code: 42 instead of a string, missing required attributes, unknown attribute names (lutaml-model raises UnknownAttribute). |
| 3. Validate | Wire-format drift — regex mismatches (^[a-z]{3}$ for language codes, ^[A-Z][a-z]{3}$ for scripts), enum violations on Action.type, additionalProperties: false catches typos at the object level. |
The reverse direction (object → YAML) goes through the same
lutaml-model framework. Round-trip parity is therefore a property
of the framework, not of bespoke to_yaml code.
Layer 3 — Multilingual plumbing
EN and FR (or any ISO 639-3 pair) live as siblings inside the same
file, under a single Resolution:
Three properties fall out of this shape:
- One file per meeting, not per language. A
git diffalways shows the EN line and the FR-line that changed together. Drift is visible in review, not invisible across files. - The action verb is canonical.
type: approveslives once perAction, regardless of language. The French block reuses it; only themessagediffers. - Adding a third language is one new entry. No schema change,
no new file convention, no migration. Drop a
language_code: deublock next to the existing two.
For the deep-dive — including the translator workflow and the validation pipeline that catches drift before it ships — see Localization sync.
How the gem fits
The Ruby gem is the runtime that drives all three layers:
edoxen (gem)
├── lib/edoxen/
│ ├── models/ # lutaml-model classes (Resolution, Action, …)
│ ├── schema_validator.rb
│ └── cli/ # Thor commands: validate, normalize
├── schema/
│ └── edoxen.yaml # JSON Schema (Draft 7) — the wire-format lock
└── exe/edoxen # CLI entrypoint
lib/edoxen/models/— Ruby classes generated from the LutaML definitions inedoxen-model. Each class declares its attributes and mappings; serialization is handled bylutaml-model.schema/edoxen.yaml— the JSON Schema. Distributed inside the gem soedoxen validateworks offline. (Currently scoped toResolutionCollection; theMeetingCollectionschema is in progress.)exe/edoxen— the CLI. Wrapsvalidateandnormalizeas Thor commands; designed for CI pipelines.
The schema and the Ruby classes are both derived from the same
LutaML/UML source in
edoxen/edoxen-model.
The docs you are reading are the introduction; the model files are
the ground truth.
Where to next
- Introduction — a one-page tour of the model with a working YAML.
- Schema — the JSON Schema reference, every invariant and extension point.
- Meeting Collection — the meeting-level container.
- Agenda — agenda items, schedule slots, deadlines.
- Structured Identifier — the
{prefix, number}identifier type. - Localization sync — the EN+FR deep-dive.
- CLI —
validateandnormalizefrom the command line. edoxen/edoxen-model— the LutaML/UML source of truth.