edoxen 0.3.0 → 0.5.0

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +24 -10
  3. data/README.adoc +170 -31
  4. data/TODO.meeting-agenda/01-design-decisions.md +132 -0
  5. data/TODO.meeting-agenda/02-lutaml-canonical.md +61 -0
  6. data/TODO.meeting-agenda/03-ruby-models.md +73 -0
  7. data/TODO.meeting-agenda/04-schema.md +34 -0
  8. data/TODO.meeting-agenda/05-fixtures.md +43 -0
  9. data/TODO.meeting-agenda/06-specs.md +52 -0
  10. data/TODO.meeting-agenda/07-cli.md +55 -0
  11. data/TODO.meeting-agenda/08-docs.md +35 -0
  12. data/TODO.meeting-agenda/09-verify-and-release.md +41 -0
  13. data/TODO.meeting-agenda/10-future-enhancements.md +72 -0
  14. data/TODO.meeting-agenda/README.md +30 -0
  15. data/lib/edoxen/action.rb +0 -6
  16. data/lib/edoxen/agenda.rb +26 -0
  17. data/lib/edoxen/agenda_item.rb +17 -0
  18. data/lib/edoxen/approval.rb +0 -7
  19. data/lib/edoxen/cli.rb +199 -95
  20. data/lib/edoxen/consideration.rb +0 -6
  21. data/lib/edoxen/date_range.rb +11 -0
  22. data/lib/edoxen/deadline.rb +10 -0
  23. data/lib/edoxen/enums.rb +24 -0
  24. data/lib/edoxen/error.rb +32 -2
  25. data/lib/edoxen/host_ref.rb +14 -0
  26. data/lib/edoxen/localization.rb +0 -12
  27. data/lib/edoxen/location.rb +15 -0
  28. data/lib/edoxen/meeting.rb +68 -0
  29. data/lib/edoxen/meeting_collection.rb +26 -0
  30. data/lib/edoxen/meeting_collection_metadata.rb +11 -0
  31. data/lib/edoxen/meeting_identifier.rb +0 -5
  32. data/lib/edoxen/meeting_localization.rb +15 -0
  33. data/lib/edoxen/meeting_relation.rb +12 -0
  34. data/lib/edoxen/person.rb +14 -0
  35. data/lib/edoxen/reference.rb +13 -0
  36. data/lib/edoxen/resolution.rb +21 -12
  37. data/lib/edoxen/resolution_collection.rb +0 -5
  38. data/lib/edoxen/resolution_date.rb +0 -5
  39. data/lib/edoxen/resolution_metadata.rb +5 -9
  40. data/lib/edoxen/resolution_relation.rb +0 -6
  41. data/lib/edoxen/schedule_item.rb +16 -0
  42. data/lib/edoxen/schema_validator.rb +12 -28
  43. data/lib/edoxen/source_url.rb +6 -8
  44. data/lib/edoxen/structured_identifier.rb +0 -5
  45. data/lib/edoxen/url.rb +0 -6
  46. data/lib/edoxen/version.rb +1 -1
  47. data/lib/edoxen.rb +17 -0
  48. data/schema/edoxen.yaml +7 -0
  49. data/schema/meeting.yaml +358 -0
  50. metadata +27 -2
@@ -0,0 +1,52 @@
1
+ # Phase 6 — Specs
2
+
3
+ Priority: **P0**
4
+
5
+ ## Goal
6
+
7
+ Comprehensive spec coverage. One spec file per new model class, plus
8
+ CLI and schema-sync coverage. Real model instances only — no
9
+ `double()`.
10
+
11
+ ## Deliverables — `spec/edoxen/meeting_*_spec.rb` (one per class)
12
+
13
+ For each of the 13 new classes:
14
+
15
+ - Construction with explicit attrs.
16
+ - Round-trip YAML serialization.
17
+ - Enum coverage where applicable (one example per enum value).
18
+ - Real-instance types (e.g., `Meeting#agenda` returns an `Edoxen::Agenda`).
19
+
20
+ ## Deliverables — additional specs
21
+
22
+ - `spec/edoxen/meeting_collection_spec.rb` — top-level container,
23
+ round-trips, lookup helpers (`find_by_urn`, `find_by_identifier`).
24
+ - `spec/edoxen/meeting_lookup_spec.rb` — `Meeting#in_language`,
25
+ `Meeting#primary_localization`, `Meeting#find_agenda_item`.
26
+ - `spec/edoxen/schema_meeting_enum_sync_spec.rb` — every enum in
27
+ `schema/meeting.yaml` `$defs` matches the corresponding
28
+ `Edoxen::Enums::*` frozen array.
29
+ - `spec/edoxen/schema_meeting_model_sync_spec.rb` — every Ruby
30
+ Meeting-related class has matching properties/collection flags in
31
+ `schema/meeting.yaml` `$defs`.
32
+ - `spec/edoxen/cli_meetings_spec.rb` — `validate-meetings` and
33
+ `normalize-meetings` subcommands, spawned via `Open3.capture3`
34
+ against the new fixtures.
35
+ - `spec/edoxen/schema_validator_meeting_spec.rb` — SchemaValidator
36
+ constructed with the meeting schema path; happy path + each error
37
+ type (additionalProperties, required, enum, pattern, type).
38
+
39
+ ## Style rules
40
+
41
+ - Real model instances only — no `double()`.
42
+ - Each example ≤ 10 lines.
43
+ - Round-trip examples cover every fixture.
44
+
45
+ ## Acceptance criteria
46
+
47
+ - [ ] One spec file per new class (13 files).
48
+ - [ ] +5 additional specs (collection, lookup, schema enum sync,
49
+ schema model sync, CLI).
50
+ - [ ] +1 schema_validator spec for the meeting schema path.
51
+ - [ ] `bundle exec rspec` — 0 failures (existing 197 + new ≈ 80+).
52
+ - [ ] `bundle exec rubocop` clean.
@@ -0,0 +1,55 @@
1
+ # Phase 7 — CLI integration
2
+
3
+ Priority: **P1**
4
+
5
+ ## Goal
6
+
7
+ Add `validate-meetings` and `normalize-meetings` subcommands. Reuse
8
+ the existing `Edoxen::Cli::Batch` deep module — no scaffold
9
+ duplication.
10
+
11
+ ## Deliverables
12
+
13
+ - `lib/edoxen/cli.rb` — two new subcommands:
14
+
15
+ ```ruby
16
+ desc "validate-meetings YAML_FILE_PATTERN",
17
+ "Validate Meeting/Agenda YAML file(s) against schema/meeting.yaml."
18
+
19
+ def validate_meetings(pattern)
20
+ validator = SchemaValidator.new(meeting_schema_path)
21
+ Batch.run(self, pattern, header: "🔍 Validating meetings") do |file|
22
+ errors = validator.validate_file(file) + collect_meeting_model_errors(file)
23
+ errors.empty? ? Batch::Result.ok("VALID") : Batch::Result.bad(errors.map(&:to_clickable_format))
24
+ end
25
+ end
26
+
27
+ desc "normalize-meetings YAML_FILE_PATTERN",
28
+ "Round-trip Meeting YAML file(s) through the model (--output DIR | --inplace)."
29
+
30
+ option :output, type: :string
31
+ option :inplace, type: :boolean
32
+
33
+ def normalize_meetings(pattern)
34
+ # Same shape as normalize — uses Batch.run
35
+ end
36
+ ```
37
+
38
+ - `lib/edoxen/cli.rb` private: `meeting_schema_path`,
39
+ `collect_meeting_model_errors`.
40
+
41
+ ## Why separate subcommands
42
+
43
+ - Different root schema, different model parser.
44
+ - `validate` (existing) is for ResolutionCollection; backward
45
+ compatibility preserved.
46
+ - Future: a `validate-all` that auto-detects via `oneOf` if needed.
47
+
48
+ ## Acceptance criteria
49
+
50
+ - [ ] `edoxen validate-meetings PATTERN` exits 0 on valid fixtures.
51
+ - [ ] `edoxen validate-meetings PATTERN` exits non-zero + lists
52
+ errors on invalid input.
53
+ - [ ] `edoxen normalize-meetings PATTERN --output DIR` round-trips.
54
+ - [ ] Existing `validate` and `normalize` (for Resolutions) unchanged.
55
+ - [ ] `edoxen help` lists both pairs of commands.
@@ -0,0 +1,35 @@
1
+ # Phase 8 — Documentation
2
+
3
+ Priority: **P1**
4
+
5
+ ## Goal
6
+
7
+ README + CLAUDE.md reflect the Meeting/Agenda model. The canonical
8
+ edoxen-model README documents the new LUTAML files.
9
+
10
+ ## Deliverables — edoxen gem
11
+
12
+ - `README.adoc`:
13
+ - New "Meeting & Agenda model" section with ASCII tree.
14
+ - Quick-start example showing both Resolution and Meeting parsing.
15
+ - Cross-link to the canonical LUTAML files.
16
+ - `CLAUDE.md`:
17
+ - Architecture section mentions `Meeting`, `Agenda`, the meeting
18
+ schema, and the separate CLI subcommands.
19
+ - Audit checklist extended: schema_model_sync_spec covers both
20
+ `schema/edoxen.yaml` and `schema/meeting.yaml`.
21
+
22
+ ## Deliverables — edoxen-model
23
+
24
+ - `README.adoc`:
25
+ - File index extended with all new `.lutaml` files.
26
+ - New "Meeting model" section explaining the Meeting/Agenda split,
27
+ URN linking to Resolutions, and the localization pattern.
28
+ - Sample meeting YAML mirroring one of the gem's fixtures.
29
+
30
+ ## Acceptance criteria
31
+
32
+ - [ ] Both READMEs updated.
33
+ - [ ] edoxen CLAUDE.md updated.
34
+ - [ ] Sample YAML in edoxen-model README would pass
35
+ `edoxen validate-meetings`.
@@ -0,0 +1,41 @@
1
+ # Phase 9 — Verify, PR, merge, release
2
+
3
+ Priority: **P0**
4
+
5
+ ## Goal
6
+
7
+ Land everything via PRs. Verify GHA passes. Merge. Bump edoxen minor
8
+ (new feature) and release.
9
+
10
+ ## Branch / PR plan
11
+
12
+ | Repo | Branch | PRs against |
13
+ |---|---|---|
14
+ | `metanorma/edoxen-model` | `feat/meeting-agenda-model` | main |
15
+ | `metanorma/edoxen` | `feat/meeting-agenda-model` | main |
16
+
17
+ Order: edoxen-model first (canonical), then edoxen (mirror).
18
+
19
+ ## Verification gates
20
+
21
+ Per branch, before pushing:
22
+
23
+ - [ ] `bundle exec rspec` — 0 failures.
24
+ - [ ] `bundle exec rubocop` — 0 offenses.
25
+ - [ ] `bundle exec exe/edoxen validate-meetings "spec/fixtures/meetings/*.yaml"` — clean.
26
+ - [ ] `bundle exec exe/edoxen normalize-meetings --output /tmp/out` — round-trips.
27
+ - [ ] No AI attribution in any commit.
28
+ - [ ] No TODOs / FIXMEs in committed source.
29
+ - [ ] No transient files (.DS_Store, .bak, .swp).
30
+
31
+ ## Post-merge
32
+
33
+ - [ ] Trigger `gh workflow run release.yml --ref main -f next_version=minor`
34
+ on `metanorma/edoxen`.
35
+ - [ ] Confirm new version appears on rubygems.org.
36
+ - [ ] Confirm tag `vX.Y.Z` pushed.
37
+
38
+ ## Why minor (not patch)
39
+
40
+ The new Meeting/Agenda model is an additive feature. No breaking
41
+ change to existing Resolution APIs. Semver minor on the 0.x line.
@@ -0,0 +1,72 @@
1
+ # Phase 10 — Future enhancements (not in this release)
2
+
3
+ Priority: **P2**
4
+
5
+ These are documented as known-future-work; not executed in this pass.
6
+ Each one would be its own PR.
7
+
8
+ ## P2.1 — Per-schedule-item localization
9
+
10
+ `ScheduleItemLocalization` carrying per-language `event` and
11
+ `description`. Triggered when a real consumer needs WG meeting titles
12
+ in FR alongside EN.
13
+
14
+ ## P2.2 — Meeting series
15
+
16
+ `MeetingSeries` carrying `{identifier, name, description}` with
17
+ `Meeting.series_ref: String` (URN). Triggered when navigation across
18
+ ordinals becomes a real use case (ISO/TC 154 plenary history page).
19
+
20
+ ## P2.3 — Contribution model
21
+
22
+ Replace flat `Person.contributors` with a `Contribution` class
23
+ matching the comment in the original draft (`'contributors should
24
+ defer to Contribution model`). Triggered when we need to distinguish
25
+ "presented" vs "authored" vs "reviewed" contributions per meeting.
26
+
27
+ ## P2.4 — Auto-detect document type in CLI
28
+
29
+ A single `edoxen validate PATTERN` that auto-detects
30
+ ResolutionCollection vs Meeting via JSON-Schema `oneOf`. Triggered
31
+ when users complain about remembering two subcommand names.
32
+
33
+ ## P2.5 — Cross-document link validation
34
+
35
+ A spec/assertion that every `Meeting.resolution_refs[*]` URN has a
36
+ matching `ResolutionCollection` somewhere in the data set, and vice
37
+ versa. Triggered when broken links cause real bugs.
38
+
39
+ ## P2.6 — Attendance roster
40
+
41
+ `Meeting.attendance: Attendance[]` carrying per-person attendance
42
+ records (present/absent/apologies + affiliation). Triggered when a
43
+ consumer needs the full attendance record, not just officers.
44
+
45
+ ## P2.7 — Vote records
46
+
47
+ `VoteRecord { resolution_ref, person, vote: VoteType }` linked to a
48
+ Meeting. Triggered when ballot-by-ballot transparency becomes a
49
+ requirement.
50
+
51
+ ## P2.8 — Minutes narrative
52
+
53
+ A `Minutes` class distinct from `Agenda`, capturing the actual
54
+ narrative of what was said (linked Markdown / Asciidoc blocks per
55
+ agenda item). Triggered when the OIML `meetings/` directory starts
56
+ receiving Bulletin scans.
57
+
58
+ ## P2.9 — federation (multi-body)
59
+
60
+ A `Federation` or `JointMeeting` class for meetings organized by
61
+ multiple bodies (e.g., a JWG with TC 154 + TC 307). Triggered when
62
+ real joint-meeting data lands.
63
+
64
+ ## P2.10 — IANA / ISO 3166-1 / ISO 639-3 / ISO 15924 reference data
65
+
66
+ Built-in lookup tables to validate `country_code`, `language_code`,
67
+ `script` against the actual standard. Triggered when typo-rate
68
+ becomes painful.
69
+
70
+ ## Acceptance criteria
71
+
72
+ This file documents intent only; nothing to land in this release.
@@ -0,0 +1,30 @@
1
+ # TODO.meeting-agenda — index
2
+
3
+ The work to land a canonical Meeting + Agenda + AgendaItem model
4
+ linkable to Resolutions/Decisions across `edoxen` and `edoxen-model`.
5
+
6
+ ## Phases
7
+
8
+ | # | File | Priority | Status |
9
+ |---|---|---|---|
10
+ | 1 | [01-design-decisions.md](01-design-decisions.md) | P0 | done |
11
+ | 2 | [02-lutaml-canonical.md](02-lutaml-canonical.md) | P0 | pending |
12
+ | 3 | [03-ruby-models.md](03-ruby-models.md) | P0 | pending |
13
+ | 4 | [04-schema.md](04-schema.md) | P0 | pending |
14
+ | 5 | [05-fixtures.md](05-fixtures.md) | P0 | pending |
15
+ | 6 | [06-specs.md](06-specs.md) | P0 | pending |
16
+ | 7 | [07-cli.md](07-cli.md) | P1 | pending |
17
+ | 8 | [08-docs.md](08-docs.md) | P1 | pending |
18
+ | 9 | [09-verify-and-release.md](09-verify-and-release.md) | P0 | pending |
19
+ | 10 | [10-future-enhancements.md](10-future-enhancements.md) | P2 | documented |
20
+
21
+ ## Execution order
22
+
23
+ Phases 2 → 9 are executed in order. Phase 10 documents known-future-
24
+ work only.
25
+
26
+ ## Branches
27
+
28
+ - `metanorma/edoxen-model` ← `feat/meeting-agenda-model` (Phase 2)
29
+ - `metanorma/edoxen` ← `feat/meeting-agenda-model` (Phases 3–8)
30
+ - Phase 9 merges both, then releases `edoxen` as a minor bump.
data/lib/edoxen/action.rb CHANGED
@@ -7,11 +7,5 @@ module Edoxen
7
7
  attribute :type, :string, values: Enums::ACTION_TYPE
8
8
  attribute :date_effective, ResolutionDate
9
9
  attribute :message, :string
10
-
11
- key_value do
12
- map "type", to: :type
13
- map "date_effective", to: :date_effective
14
- map "message", to: :message
15
- end
16
10
  end
17
11
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # The business-order document of a Meeting. Distinct from the
5
+ # timetable (Schedule) — the Agenda orders topics; the Schedule
6
+ # orders time slots.
7
+ #
8
+ # An Agenda may be versioned independently of the Meeting: a draft
9
+ # agenda circulates weeks before; a final agenda at meeting time;
10
+ # an amended agenda if items are added during the meeting. The
11
+ # `status` field captures that lifecycle.
12
+ class Agenda < Lutaml::Model::Serializable
13
+ attribute :identifier, StructuredIdentifier, collection: true
14
+ attribute :status, :string, values: Enums::AGENDA_STATUS
15
+ attribute :source_doc, :string
16
+ attribute :items, AgendaItem, collection: true
17
+ attribute :opening_session, ScheduleItem
18
+ attribute :closing_session, ScheduleItem
19
+
20
+ # Find an agenda item by its label (e.g., "5.2"). Returns nil
21
+ # when no item matches.
22
+ def find_item(label)
23
+ items&.find { |item| item.label == label.to_s }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Edoxen
4
+ # One entry on an Agenda. `label` is the visible identifier (e.g.,
5
+ # "5.2"); `kind` discriminates numbered/unnumbered/header/opening/
6
+ # closing; `outcome` records what happened; `resolution_ref`
7
+ # optionally links to the URN of the resolution this item produced.
8
+ class AgendaItem < Lutaml::Model::Serializable
9
+ attribute :label, :string
10
+ attribute :kind, :string, values: Enums::AGENDA_ITEM_KIND
11
+ attribute :title, :string
12
+ attribute :description, :string
13
+ attribute :references, Reference, collection: true
14
+ attribute :outcome, :string, values: Enums::AGENDA_ITEM_OUTCOME
15
+ attribute :resolution_ref, :string
16
+ end
17
+ end
@@ -8,12 +8,5 @@ module Edoxen
8
8
  attribute :degree, :string, values: Enums::APPROVAL_DEGREE
9
9
  attribute :date, ResolutionDate
10
10
  attribute :message, :string
11
-
12
- key_value do
13
- map "type", to: :type
14
- map "degree", to: :degree
15
- map "date", to: :date
16
- map "message", to: :message
17
- end
18
11
  end
19
12
  end