pubid 2.0.0.pre.alpha.57 → 2.0.0.pre.alpha.59

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.
@@ -30,4 +30,4 @@ These notes were part of the root `CLAUDE.md`. Read them before you change `lib/
30
30
 
31
31
  **Two parent-slot gaps recorded by the cross-flavor `base` survey** (`lib/pubid/bsi/CLAUDE.md` has the measured version). `Identifiers::TestReportForm#cispr_identifier` is a polymorphic `Identifier` parent slot with no `#root` override, so `root` returns the form itself. And `Identifiers::ConsolidatedIdentifier#base_document` returns `identifiers&.first` **without** the recursive `&.base_document` that its BSI, CSA and CEN counterparts use — a one-level peel where the others peel fully, so a consolidated edition of a supplement does not reduce to the bare standard. Neither is chased here; both are pinned by nothing, which is why they are written down.
32
32
 
33
- **Stage codes — the SSOT and what is settled.** The authoritative source for IEC stage abbreviations is committed in this repo: **`data/iec/stage-codes.md`** — the 52-row IEC/ISO stage-code correspondence table transcribed from IEC's official *IEC/ISO Harmonized APIs* documents (v0.0.8 2023-06, api-portal.iec.ch; v0.0.7 2021-05, iec-apiportal.apigee.io — the successors that replace the IEC Data Feeds), the legacy Data-Feeds field examples, and the IS-lifecycle ladder from `StageCodes_IS_v2.pdf` (2023-06). `www.iec.ch` itself is fully WAF-gated to non-browsers and the Wayback Machine holds no snapshots of its PDFs, so the extract — not the unreachable original — is the reference. **`Pubid::Iec.locate_stage` reads `TYPED_STAGES` only** (`lib/pubid/iec.rb`); `PROJECT_STAGES` (AFDIS, TCDV, PRVC…) are not queryable through it. Every one of metanorma-iec's 55 `STAGE_CODES` — the list quoted in pubid#237 — resolves, locked by `spec/pubid/iec/typed_stages_spec.rb` in the issue's own order; aliases are always **appended, never prepended**, so `abbr.first` (what renders) is unchanged. Notable rulings from that pass: **ADIS** is the legacy Data-Feeds spelling of AFDIS ("Approved for FDIS circulation", ISO_LEVEL 40.99) and is an alias on `cdv` (the typed stage claiming 40.99); **CDISH/CDPAS** are the 50.20 rows of the DISH/DPAS deliverables, not "committee draft ISH/PAS" (30.xx); bare **PUB**, and the codes **AWG/CANV**, are defined by no IEC source and are deliberately unregistered (pubid#572, closed — the extract's "absent from every source" section is the standing evidence). **Reconciliation items recorded in the extract, deliberately not applied:** DELPUB sits at 99.60 there while the `is` ladder does not claim 99.60; CAN carries two codes (30.98 IS/TR/TS, 50.98 PAS/ISH/COR); the rows CDISH-table-siblings DECDISH/MERGED/SPLIT/PCC/PRVN/DEL/RPUB/TPUB and the per-deliverable translation/preparation families are not registered as typed-stage abbreviations.
33
+ **Stage codes — the SSOT and what is settled.** The authoritative source for IEC stage abbreviations is committed in this repo: **`data/iec/stage-codes.md`** — the 52-row IEC/ISO stage-code correspondence table transcribed from IEC's official *IEC/ISO Harmonized APIs* documents (v0.0.8 2023-06, api-portal.iec.ch; v0.0.7 2021-05, iec-apiportal.apigee.io — the successors that replace the IEC Data Feeds), the legacy Data-Feeds field examples, and the IS-lifecycle ladder from `StageCodes_IS_v2.pdf` (2023-06). `www.iec.ch` itself is fully WAF-gated to non-browsers and the Wayback Machine holds no snapshots of its PDFs, so the extract — not the unreachable original — is the reference. **`Pubid::Iec.locate_stage` reads `TYPED_STAGES` only** (`lib/pubid/iec.rb`); `PROJECT_STAGES` (AFDIS, TCDV, PRVC…) are not queryable through it. Every one of metanorma-iec's 55 `STAGE_CODES` — the list quoted in pubid#237 — resolves, locked by `spec/pubid/iec/typed_stages_spec.rb` in the issue's own order; aliases are always **appended, never prepended**, so `abbr.first` (what renders) is unchanged. Notable rulings from that pass: **ADIS** is the legacy Data-Feeds spelling of AFDIS ("Approved for FDIS circulation", ISO_LEVEL 40.99) and is an alias on `cdv` (the typed stage claiming 40.99); **CDISH/CDPAS** are the 50.20 rows of the DISH/DPAS deliverables, not "committee draft ISH/PAS" (30.xx); **Reconciliation items recorded in the extract, deliberately not applied:** DELPUB sits at 99.60 there while the `is` ladder does not claim 99.60; CAN carries two codes (30.98 IS/TR/TS, 50.98 PAS/ISH/COR); the rows CDISH-table-siblings DECDISH/MERGED/SPLIT/PCC/PRVN/DEL/RPUB/TPUB and the per-deliverable translation/preparation families are not registered as typed-stage abbreviations.
@@ -1079,11 +1079,17 @@ module Pubid
1079
1079
  # P + a /D designator (any draft capture) is a DRAFT, never a
1080
1080
  # printed Std (the owner's 2026-10-24 ruling): the P marker is
1081
1081
  # project status, so the joint/project-draft family owns the row
1082
- # whatever date trails the designator. The non-P rows keep the
1083
- # printed-strip doctrine (the normalization ledger).
1084
- p_with_draft = parsed[:project_marker] &&
1082
+ # whatever date trails the designator. The ruling extends to the
1083
+ # P-less joint rows (pubid#597): a numeric /D designator alone is
1084
+ # project status too — the row is a draft AND takes the P, so the
1085
+ # marker is set below when the source spelling lacked it.
1086
+ p_with_draft = (parsed[:project_marker] || numeric_draft) &&
1085
1087
  (parsed[:draft_version] || parsed[:draft_iso_stage] ||
1086
1088
  parsed[:draft_month] || parsed[:draft_year])
1089
+ if p_with_draft && !parsed[:project_marker]
1090
+ attributes[:project_marker] = true
1091
+ parsed[:project_marker] = "P"
1092
+ end
1087
1093
  printed_joint = parsed[:iso_published] && parsed[:iso_stage].nil? &&
1088
1094
  !p_with_draft &&
1089
1095
  (parsed[:printed_dash_year] || parsed[:printed_month_year] ||
@@ -1151,6 +1157,13 @@ module Pubid
1151
1157
  return Identifiers::Standard.new(**printed_attrs.compact)
1152
1158
  end
1153
1159
 
1160
+ # The designation route ("IEEE P24748-4/D=FDIS") has no
1161
+ # publishers group and no parsed publisher slot; IEEE is the
1162
+ # sole publisher of the D= designator face, and the elections
1163
+ # below read the builder-local slots.
1164
+ attributes[:publisher] ||= "IEEE"
1165
+ attributes[:copublisher] ||= []
1166
+
1154
1167
  # The re-parse of the pubid#203 designator render ("IEEE
1155
1168
  # P15026.2/D=FDIS-201008") captures the stage and the verbatim
1156
1169
  # date token (":2020", "-201008") with no publisher slot; the
@@ -1182,9 +1195,12 @@ module Pubid
1182
1195
  if parsed[:iso_stage_iteration]
1183
1196
  stage_abbr += extract_value(parsed[:iso_stage_iteration])
1184
1197
  end
1198
+ # The designation route ("IEEE P24748-4/D=FDIS") carries no
1199
+ # publishers group, so the builder-local copublisher is nil on
1200
+ # it — the empty test must admit nil as none.
1185
1201
  bare_ieee_project = parsed[:project_marker] &&
1186
1202
  attributes[:publisher] == "IEEE" &&
1187
- attributes[:copublisher] == []
1203
+ (attributes[:copublisher] || []).empty?
1188
1204
  if bare_ieee_project && parsed[:draft_stage_year].nil?
1189
1205
  # pubid#203: a bare-IEEE project row carrying an ISO stage is
1190
1206
  # an IEEE draft of joint ISO/IEC work ("IEEE FDIS P15026.2,
@@ -1262,8 +1278,15 @@ module Pubid
1262
1278
  # rather than the joint-development model.
1263
1279
  if attributes[:draft] && parsed[:iso_stage].nil? &&
1264
1280
  parsed[:iec_stage].nil? && parsed[:amd_number].nil?
1265
- draft_attrs = { code: attributes[:code],
1266
- draft: attributes[:draft] }
1281
+ # The ruling (pubid#597): a joint row with a numeric /D designator
1282
+ # is a draft AND takes the P project marker, whatever the source
1283
+ # spelling printed — the marker is set on the identifier (and the
1284
+ # code prepended) even when the row reached it without one.
1285
+ draft_code = attributes[:code].to_s
1286
+ draft_code = "P" + draft_code unless draft_code.start_with?("P")
1287
+ draft_attrs = { code: draft_code,
1288
+ draft: attributes[:draft],
1289
+ project_marker: true }
1267
1290
  if attributes[:publishers]
1268
1291
  draft_attrs[:publisher] = attributes[:publishers].first
1269
1292
  rest = attributes[:publishers][1..]
@@ -1517,9 +1540,13 @@ module Pubid
1517
1540
  if pub_data[:copublishers]
1518
1541
  copubs = pub_data[:copublishers]
1519
1542
  copubs = [copubs] unless copubs.is_a?(Array)
1543
+ # A capture may carry its own separators ("/IEC/ISO" — a
1544
+ # joint token glued to the leading slash); the copublisher
1545
+ # collection renders join("/"), so entries hold bare orgs.
1520
1546
  attributes[:copublisher] = copubs.filter_map do |cp|
1521
1547
  extract_value(cp[:copublisher])
1522
- end
1548
+ end.flat_map { |c| c.to_s.sub(%r{\A/}, "").split("/") }
1549
+ .reject(&:empty?)
1523
1550
  end
1524
1551
  elsif parsed[:publisher]
1525
1552
  attributes[:publisher] = extract_value(parsed[:publisher])
@@ -2008,7 +2035,14 @@ module Pubid
2008
2035
  end
2009
2036
  end
2010
2037
  else
2011
- version = extract_value(draft_data)
2038
+ # A degraded wire leaf carries the whole draft clause verbatim
2039
+ # ("/D2.1.1" — the three-segment designator the engine cannot
2040
+ # structure): Draft.parse is the inverse of Draft#to_s, so it
2041
+ # strips the prefix instead of double-appending it.
2042
+ draft = Components::Draft.parse(extract_value(draft_data).to_s)
2043
+ attributes[:draft_obj] = draft
2044
+ attributes[:draft] = draft.to_s
2045
+ return
2012
2046
  end
2013
2047
 
2014
2048
  # A stage-token draft's month rides the D= designator zero-padded
@@ -169,7 +169,12 @@ day: nil, iso_stage: nil, iso_iteration: nil)
169
169
  end
170
170
 
171
171
  def to_s
172
- result = "/D#{version}"
172
+ # A version that already opens with a designator word ("ED4",
173
+ # the IEC edition-draft ordinal; an ISO/IEC stage token such
174
+ # as "FDIS_March") renders verbatim — prepending D
175
+ # concatenates a hallucinated token ("DED4", "DFDIS") that no
176
+ # face spells. Every other version keeps the D designator.
177
+ result = version&.match?(/\A(ED\d|(FDIS|FCD|CDV|DIS|CD|WD|PWI|NP))/) ? "/#{version}" : "/D#{version}"
173
178
  result += "=#{iso_stage}" if iso_stage
174
179
  result += ".#{iso_iteration}" if iso_stage && iso_iteration
175
180
  result += if revision.to_s.start_with?(".")
@@ -120,13 +120,13 @@ module Pubid
120
120
  # Handle draft as component object
121
121
  if args[:draft_obj]
122
122
  self.draft_obj = args[:draft_obj]
123
- self.draft = args[:draft_obj].to_s
123
+ self.draft = args[:draft_obj].to_s.sub(%r{\A/}, "")
124
124
  elsif args[:draft].is_a?(String)
125
125
  self.draft_obj = Components::Draft.parse(args[:draft])
126
- self.draft = draft_obj.to_s
126
+ self.draft = draft_obj.to_s.sub(%r{\A/}, "")
127
127
  elsif args[:draft]
128
128
  self.draft_obj = args[:draft]
129
- self.draft = args[:draft].to_s
129
+ self.draft = args[:draft].to_s.sub(%r{\A/}, "")
130
130
  end
131
131
 
132
132
  # Set other attributes.
@@ -150,9 +150,11 @@ module Pubid
150
150
  public_send(setter, value)
151
151
  end
152
152
  end
153
+
153
154
  end
154
155
 
155
- # Override accessors to return component objects.
156
+ # Override accessors to return component objects.
157
+
156
158
  def code
157
159
  code_obj
158
160
  end
@@ -231,7 +233,23 @@ module Pubid
231
233
  # no expansion (Draft.parse accepts the slashless form).
232
234
  def self.from_hash(data, options = {})
233
235
  data = Compaction.expand(Compaction.deep_dup(data)) if data.is_a?(::Hash)
234
- super
236
+ identifier = super
237
+ # == compares the raw ivars: a rebuild whose lazy readers have not
238
+ # run carries @code_obj/@draft_obj nil while a parse has them set,
239
+ # so the objects compare unequal although their serialized hashes
240
+ # agree (pubid#596). Materialize the runtime components eagerly.
241
+ identifier.materialize_runtime_components
242
+ identifier
243
+ end
244
+
245
+ # Rebuild the runtime components from the wire strings. Safe on every
246
+ # construction path: the readers' ||= no-ops when already set, and the
247
+ # CodeNumber rebuild covers the split-column rows that carry no code
248
+ # string.
249
+ def materialize_runtime_components
250
+ code_obj
251
+ draft_obj
252
+ self
235
253
  end
236
254
 
237
255
  # Register the identifier classes the automatic `Identifiers::*` scan
@@ -141,11 +141,16 @@ module Pubid
141
141
  # so the WHOLE type word is dropped — not just "Std" (pubid#318: the
142
142
  # half-strip left "Draft" in the render, the re-parse dropped it, and
143
143
  # to_s needed three rounds to converge while losing the type).
144
- should_render_type = id.publisher&.match?(/^(IEEE|AIEE)/)
145
-
146
- if should_render_type && !id.typed_stage&.project_status && id.type && !id.type.to_s.strip.empty? && id.type != "P" &&
144
+ # A project draft carries the word Draft whatever the lead
145
+ # publisher — the ISO-led joint drafts (pubid#597) print it too.
146
+ should_render_type = id.publisher&.match?(/^(IEEE|AIEE)/) ||
147
+ id.is_a?(Identifiers::ProjectDraftIdentifier)
148
+
149
+ if should_render_type && !id.typed_stage&.project_status &&
150
+ ((id.type && !id.type.to_s.strip.empty? && id.type != "P") ||
151
+ id.is_a?(Identifiers::ProjectDraftIdentifier)) &&
147
152
  !id.draft_status.to_s.match?(/unapproved/i)
148
- type_str = id.type.dup
153
+ type_str = id.type ? id.type.dup : "Draft"
149
154
  type_str = type_str.sub(/^P/, "") if type_str.start_with?("P")
150
155
  # A draft carries the word Draft alone in every face (the C2
151
156
  # ruling): Std/Standard never renders on a draft, whatever
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.57"
4
+ VERSION = "2.0.0.pre.alpha.59"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubid
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre.alpha.57
4
+ version: 2.0.0.pre.alpha.59
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-24 00:00:00.000000000 Z
11
+ date: 2026-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model