pubid 2.0.0.pre.alpha.16 → 2.0.0.pre.alpha.18

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.
@@ -95,3 +95,5 @@ The lesson BSI paid for: a wrapper's hand-written `#number` delegation makes the
95
95
  - **`all_parts_edition_keys` missed `revision`, `reaffirmed` AND `edition_month` — three gaps, found in two passes.** `Identifier.all_parts_edition_keys` defaults to `%i[date year edition version]`. A first pass fixed IEEE's `revision` attribute (the numbered/lettered draft revision id — "2" in "P802.16Rev2"), a separate discriminator from `edition`/`year` that this list never covered, so `"IEEE P802.16Rev2".to_all_parts === "IEEE P802.16Rev3"` was `false` — two revisions of the same draft failed to collapse. Code review of that same change then found it had stopped one attribute too early: `reaffirmed` (the "(R2010)" year, read by `renderer.rb` and `urn_generator.rb`) meant two reaffirmations of the same standard — `"IEEE Std 218-1956 (R1980) …"` vs `"… (R1990) …"` — also failed to collapse; and `edition_month`, the month half of the `"Edition N YYYY-MM"` rendering (`renderer.rb`'s `"Edition #{edition} #{year}-#{edition_month}"`), survived even though its sibling `year` is already in the default list, so `"IEEE Std 802.11 Edition 3.0 2015-03"` didn't match `"… 2015-06"`. All three fixed together: `Pubid::Ieee::Identifier.all_parts_edition_keys` is `super + %i[revision reaffirmed edition_month]`. Not on the original hand-off's audit list (which didn't cover IEEE); found during code review of the OGC/3GPP/NIST fix for the same class of bug. **Lesson**: finding one missed attribute on a flavor does not mean the audit of that flavor is finished — check every date/edition/version/revision/reaffirmation-shaped attribute against the renderer and builder, not just the one the bug report named (see the root `CLAUDE.md` bullet). Locked by `spec/pubid/all_parts_edition_keys_audit_spec.rb`.
96
96
 
97
97
  - **The project designator format, and the bare-P parse that dropped it (pubid#18).** IEEE's own draft documents are cited by their short designator — "P1201/D0.3", "P10000/D1.2" — and metanorma-ieee had to post-process the full rendering to produce it. Two edits. **(1) `format: :project`** registers `Pubid::Ieee::ProjectRenderer` in the IEEE format registry: the code (with parts), a PREPENDED "P" when the identifier is a project but the code does not already carry it, and "/D<draft>"; no publisher, no type word, no date. "Project" is detected in all three places a marker can live depending on the building path — `code_obj.prefix` (the bare spelling), the "P" type code, or a `typed_stage` with `project_status` (D1-D6). **(2) The bare-P data loss**: `ieee_p_identifier` consumed its leading "P" with a bare `str("P")`, so "P1201/D0.3" parsed WITHOUT its P anywhere (the number was "1201", prefix nil) — the issue's complaint verbatim. The rule now lets `number` capture the P (the #318 pattern), so `code.prefix` survives and the designator has something to read. The DEFAULT rendering is unchanged — the corpus keeps the P in every unapproved-draft canonical, and #318's idempotence work locked that; the issue's suggestion that the default drop the P predates the corpus and is not adopted. A published (non-project) id renders its bare number under `:project` — the format is a draft-designator view.
98
+
99
+ - **Joint stage-first forms are lead ISO, and the ISO face is lossless (pubid#477)**: a stage word printed in the ISO position (`IEEE FCD 15026.3:2010`, `IEEE/IEC CD2 P62582-2017-05`) parses as printed (#469): `Builder#build_joint_development` sets `lead_party "ISO"` whatever the publisher order, so `canonical_format` is `:iso`. PR #472 had made the lead the first printed publisher and rendered an IEEE-first form in the `D=` face — but the grammar artifact cannot parse most of those renders (`D=FCD`, `D=CD.2`, a non-P number, a month), and `D=CD-2020` parses into a *different* id (`ieee_draft`, no `iso_stage`), so 19 published `relaton-data-ieee` rows stopped matching themselves. The `D=` face now prints only for an id that carries a `D=` designator, or on an explicit `to_s(format: :ieee)`. `JointDevelopment#to_iso_format` prints `iso_stage` as stored (`DIS2`, `CD4` — #472's bare `CD` lost the iteration) and the date in the spelling that parses back (`#iso_date_suffix`: `:2018`, `-2018-05`, `, April 2019`). **The test is `parse(to_s) == id`, not a stable `to_s`**: a lossy render reaches a fixed point and round-trips its *hash* (`iso_stage_spellings_spec` held both while dropping `CD4`→`CD`), yet the reparse is a different identifier. Measured over the 118 joint stage rows of `relaton-data-ieee`: `to_s` stable 100 → 118, `==` 51 → 106; the other 12 carry an `ieee_draft` or an edition that the ISO face still drops. **PARG separator-in-capture, again (#471)**: the draft `revision` capture is `".12"`, and `Draft#to_s` adds its own dot, so `handle_draft` strips the leading dot (`D2022.10.12` rendered `D2022.10..12`). Check every capture a renderer re-joins with a separator. Locked by `spec/pubid/ieee/joint_stage_roundtrip_spec.rb` (stored index hashes → `parse(to_s) == row`).
@@ -861,11 +861,12 @@ module Pubid
861
861
 
862
862
  # Detect lead party based on pattern
863
863
  if parsed[:iso_stage]
864
- # ISO stage word present. The lead party is the PRINTED first
865
- # publisher (pubid#469: the arrangement is the organization's
866
- # perspective, shown by the print) — "IEEE/ISO/IEC CD P42010"
867
- # is IEEE-led even though the stage word is ISO's.
868
- attributes[:lead_party] = attributes[:publishers]&.first || "ISO"
864
+ # ISO stage word present: the reference is printed in the ISO
865
+ # position ("IEEE FCD 15026.3:2010"), so it parses as printed
866
+ # (pubid#469) - lead ISO, ISO face, whatever the publisher
867
+ # order. The printed first publisher is not the arrangement, and
868
+ # the published relaton-data-ieee rows carry lead ISO (pubid#477).
869
+ attributes[:lead_party] = "ISO"
869
870
  attributes[:iso_stage] = extract_value(parsed[:iso_stage])
870
871
 
871
872
  # Create typed_stage for ISO stage
@@ -1471,7 +1472,12 @@ module Pubid
1471
1472
  version = version.sub(/\A-/, "") if version
1472
1473
  end
1473
1474
 
1474
- revision = extract_value(draft_data[:revision]) if draft_data[:revision]
1475
+ # The PARG capture carries its separator (".12", the #471
1476
+ # convention); Draft#to_s prints the dot itself, so keep only
1477
+ # the digits or "D2022.10.12" renders "D2022.10..12" (pubid#477).
1478
+ if draft_data[:revision]
1479
+ revision = extract_value(draft_data[:revision])&.delete_prefix(".")
1480
+ end
1475
1481
 
1476
1482
  # The compound both-systems form (docs/IEEE-DRAFT-STAGES.md
1477
1483
  # §1.3): the stage half of "=DDIS.3".
@@ -143,7 +143,12 @@ day: nil, iso_stage: nil, iso_iteration: nil)
143
143
  result = "/D#{version}"
144
144
  result += "=#{iso_stage}" if iso_stage
145
145
  result += ".#{iso_iteration}" if iso_stage && iso_iteration
146
- result += ".#{revision}" if revision
146
+ result += if revision.to_s.start_with?(".")
147
+ # a captured revision may carry its separator dot
148
+ revision.to_s
149
+ else
150
+ ".#{revision}"
151
+ end if revision
147
152
 
148
153
  if year
149
154
  # Use original month format to preserve abbreviated vs full names
@@ -69,6 +69,10 @@ module Pubid
69
69
  # Set lead_party if not provided - default to first publisher
70
70
  if args[:lead_party]
71
71
  self.lead_party = args[:lead_party]
72
+ elsif iso_stage
73
+ # A stage-first reference is lead ISO, as the builder sets it
74
+ # (pubid#477) — a hand-built id must agree with a parsed one.
75
+ self.lead_party = "ISO"
72
76
  elsif publishers && !publishers.empty?
73
77
  # Lead party defaults to first publisher if not explicitly set
74
78
  # Builder should override this with detected lead party
@@ -137,17 +141,13 @@ module Pubid
137
141
 
138
142
  # The stage word is the ISO format's own position convention —
139
143
  # it prints whenever the identifier carries one, regardless of
140
- # lead party (the format face decides, not the arrangement). A
141
- # stage-tracked D= designator decomposes into its stage word
142
- # and date ("D=CD.2-2020" → "CD2", 2020); the iteration glues
143
- # onto the word only for the stages whose printed ISO spellings
144
- # carry it (CD2, DIS2 — the grammar's multi-digit families);
145
- # the P project stage is IEEE convention and never prints here.
146
- # The printed word is always the bare stage ("CD4" prints "CD",
147
- # "DIS3" prints "DIS" — every iso_stage_spellings_spec
148
- # expectation); the iteration is draft machinery, not ISO-face
149
- # identity.
150
- stage_word = iso_stage.to_s.sub(/\A([A-Z]+?)\d+\z/, '\1')
144
+ # lead party (the format face decides, not the arrangement).
145
+ # The stage prints as stored, iteration included ("DIS2",
146
+ # "CD4"): the face is lossless, so parse(to_s) == self
147
+ # (pubid#477). A stage-tracked D= designator decomposes into its
148
+ # stage word and date ("D=CD-2020" → "CD", 2020); the P project
149
+ # stage is IEEE convention and never prints here.
150
+ stage_word = iso_stage.to_s
151
151
  stage_word = nil if stage_word.empty?
152
152
  draft_year = nil
153
153
  if stage_word.nil? &&
@@ -164,10 +164,9 @@ module Pubid
164
164
  code_str += mark unless code_str.empty?
165
165
  parts << code_str if code_str && !code_str.empty?
166
166
 
167
- # Join with space and add year with colon (the ISO position
168
- # convention; a decomposed D= date rides here as the year).
169
- result = parts.join(" ")
170
- result += ":#{year || draft_year}" if year || draft_year
167
+ # Join with space and add the date (a decomposed D= date rides
168
+ # here as the year).
169
+ result = parts.join(" ") + iso_date_suffix(year || draft_year)
171
170
  # Only the language/edition marker ("(E)", "(E/F)") prints; a
172
171
  # trailing relationship narrative is metadata, not identity.
173
172
  if parenthetical_content&.match?(%r{\A[A-Z](?:\s*[/&]\s*[A-Z])*\z})
@@ -177,6 +176,21 @@ module Pubid
177
176
  result
178
177
  end
179
178
 
179
+ # The ISO-face date, in the spelling that parses back to the same
180
+ # year and month: the colon year (":2018"); a numeric month glued
181
+ # with dashes ("-2018-05"); a text month as ", February 2015".
182
+ def iso_date_suffix(date_year)
183
+ return "" unless date_year
184
+ return ":#{date_year}" unless month
185
+
186
+ if month.match?(/\A\d+\z/)
187
+ # The grammar reads only a two-digit month ("05", never "5").
188
+ "-#{date_year}-#{month.rjust(2, '0')}"
189
+ else
190
+ ", #{month} #{date_year}"
191
+ end
192
+ end
193
+
180
194
  # Convert to IEEE format representation
181
195
  # ISO/IEC/IEEE P26511/D8-2018
182
196
  # @return [String] IEEE format string
@@ -195,8 +209,9 @@ module Pubid
195
209
 
196
210
  # Add IEEE draft notation if available (e.g., /D8). An ISO stage
197
211
  # word renders in IEEE's stage-tracked position as the ordinal-less
198
- # stage draft (docs/IEEE-DRAFT-STAGES.md §1.3, spelling 7):
199
- # "IEEE/ISO/IEC CD P42010:2020" → "IEEE/ISO/IEC P42010/D=CD-2020".
212
+ # stage draft (docs/IEEE-DRAFT-STAGES.md §1.3, spelling 7) — only
213
+ # on an explicit to_s(format: :ieee): a stage-first reference is
214
+ # lead ISO, so its canonical face is the ISO one (pubid#477).
200
215
  # The iso_stage branch is checked FIRST — the typed_stage registry
201
216
  # lookup for a stage word answers a draft-equivalent ordinal ("D2"),
202
217
  # which is not the canonical stage-tracked spelling.
data/lib/pubid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pubid
4
- VERSION = "2.0.0.pre.alpha.16"
4
+ VERSION = "2.0.0.pre.alpha.18"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubid
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre.alpha.16
4
+ version: 2.0.0.pre.alpha.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.