pubid 2.0.21 → 2.0.22
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.
- checksums.yaml +4 -4
- data/README.adoc +73 -31
- data/data/iec/legacy-notation.md +298 -0
- data/data/iso/historic-schemes.md +368 -0
- data/data/parg/iec.json +1 -1
- data/data/parg/iso.json +1 -1
- data/data/parg/iso.parg +142 -2
- data/data/parg/tables/iec_typed_stages.yaml +3 -0
- data/data/parg/tables/typed_stages_ballots.yaml +6 -0
- data/data/parg/tables/typed_stages_supplements.yaml +1 -0
- data/lib/pubid/iec/CLAUDE.md +5 -1
- data/lib/pubid/iec/builder.rb +47 -0
- data/lib/pubid/iec/identifiers/amendment.rb +34 -0
- data/lib/pubid/iec/identifiers/corrigendum.rb +6 -1
- data/lib/pubid/iec/supplement_identifier.rb +55 -25
- data/lib/pubid/iho/CLAUDE.md +2 -1
- data/lib/pubid/iho/pubid_urn_schema.rb +2 -1
- data/lib/pubid/iso/CLAUDE.md +49 -0
- data/lib/pubid/iso/builder.rb +77 -0
- data/lib/pubid/iso/identifiers/amendment.rb +1 -1
- data/lib/pubid/iso/single_identifier.rb +7 -1
- data/lib/pubid/nist/CLAUDE.md +48 -10
- data/lib/pubid/nist/builder.rb +5 -0
- data/lib/pubid/nist/identifiers/base.rb +42 -1
- data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +44 -5
- data/lib/pubid/nist/preprocessor.rb +14 -12
- data/lib/pubid/nist/urn_parser.rb +1 -1
- data/lib/pubid/version.rb +1 -1
- metadata +5 -2
data/data/parg/iso.parg
CHANGED
|
@@ -11,6 +11,20 @@
|
|
|
11
11
|
# below, whose captures carry the idf_ prefix so they never collide
|
|
12
12
|
# with the ISO ones and can bind as scalars.
|
|
13
13
|
#
|
|
14
|
+
# Historic TYPE-first ballot faces (decode-to-modern, owner ruling
|
|
15
|
+
# 2026-11-12): the DAM-family token in TYPE position — "ISO/IEC FDAM 1
|
|
16
|
+
# 14496-12:2004", "ISO/PDAM 19973-2" — and the space-attached ballot
|
|
17
|
+
# face "ISO 15877-5:2009 FDAM 1". Both branches sit LAST in the
|
|
18
|
+
# identifier alternation, so they are purely additive: every face an
|
|
19
|
+
# earlier branch parses keeps its exact parse. The ballot token
|
|
20
|
+
# captures as ballot_type_with_stage; a number on the TYPE token is
|
|
21
|
+
# space-bounded (ballot_number) so "PDAM 19973-2" keeps 19973 in the
|
|
22
|
+
# base; an on-base stamp's number/date capture as ballot_stamp_* (the
|
|
23
|
+
# stamp token itself matches uncaptured — the ballot's stage comes
|
|
24
|
+
# from the TYPE token and the decode keeps no spelling). The builder
|
|
25
|
+
# folds the captures into ONE amendment layer over the whole base, the
|
|
26
|
+
# number taken from the TYPE token, the stamp, or the implied 1.
|
|
27
|
+
#
|
|
14
28
|
# Edition 2 with typed model bindings: every capture the builder
|
|
15
29
|
# consumes is bound. Supplement layers capture into position-scoped
|
|
16
30
|
# names (supp_* on the first supplement, supp2_* on the second,
|
|
@@ -23,7 +37,7 @@
|
|
|
23
37
|
|
|
24
38
|
use idf
|
|
25
39
|
|
|
26
|
-
grammar PubidIso version "0.
|
|
40
|
+
grammar PubidIso version "0.5.0" edition 2 {
|
|
27
41
|
|
|
28
42
|
# ---- lexical (pubid/parser/common_parse_rules.rb + ISO override) ----
|
|
29
43
|
digit = %x30-39
|
|
@@ -47,6 +61,7 @@ grammar PubidIso version "0.4.0" edition 2 {
|
|
|
47
61
|
# inline the alt so the capture holds one scalar span) ----
|
|
48
62
|
typed_stage_abbrev = alt from_table "typed_stages" column "abbr"
|
|
49
63
|
supp_stage_abbrev = alt from_table "typed_stages_supplements" column "abbr"
|
|
64
|
+
ballot_stage_abbrev = alt from_table "typed_stages_ballots" column "abbr"
|
|
50
65
|
directives_stage_abbrev = alt from_table "directives_typed_stages" column "abbr"
|
|
51
66
|
directives_supp_stage_abbrev = alt from_table "directives_supplements_typed_stages" column "abbr"
|
|
52
67
|
idf_stage_abbrev = alt from_table "idf_typed_stages" column "abbr"
|
|
@@ -395,6 +410,76 @@ grammar PubidIso version "0.4.0" edition 2 {
|
|
|
395
410
|
)
|
|
396
411
|
}
|
|
397
412
|
|
|
413
|
+
# ---- historic TYPE-first ballot faces (decode-to-modern) ----
|
|
414
|
+
## The DAM-family ballot token in TYPE position. Captured under its
|
|
415
|
+
## own name: the supplement tables' captures stay reserved for the
|
|
416
|
+
## on-base layers, and the ballot's stage is the one the decode keeps.
|
|
417
|
+
ballot_type_with_stage = {
|
|
418
|
+
(
|
|
419
|
+
(alt from_table "typed_stages_ballots" column "abbr")
|
|
420
|
+
) as ballot_type_with_stage
|
|
421
|
+
}
|
|
422
|
+
## An on-base stamp riding a ballot face ("/Amd 9:2001", "/Amd.1",
|
|
423
|
+
## "/Am1:2006"): it carries the amendment's number and ballot date
|
|
424
|
+
## while its own token letters are decode-away (matched uncaptured —
|
|
425
|
+
## the ballot's stage comes from the TYPE token). Mirrors the three
|
|
426
|
+
## supplement tails: a (glued or spaced) number with an optional
|
|
427
|
+
## date, or a dot number.
|
|
428
|
+
ballot_stamp = {
|
|
429
|
+
slash supp_stage_abbrev
|
|
430
|
+
(
|
|
431
|
+
(
|
|
432
|
+
[space]
|
|
433
|
+
((1*5digit)) as ballot_stamp_number
|
|
434
|
+
[
|
|
435
|
+
[space]
|
|
436
|
+
(
|
|
437
|
+
(colon / dash) [
|
|
438
|
+
(("19" / "20") 2*digit !digit [(dash 2*digit)] [(dash 2*digit)]) as ballot_stamp_date
|
|
439
|
+
/ (("--" / "—")) as ballot_stamp_undated_marker
|
|
440
|
+
]
|
|
441
|
+
)
|
|
442
|
+
]
|
|
443
|
+
)
|
|
444
|
+
/ ( dot (1*digit) as ballot_stamp_dot_number )
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
## "ISO/IEC FDAM 1 14496-12:2004", "ISO/PDAM 19973-2": the ballot
|
|
448
|
+
## token first, then the base kept whole. The TYPE-token number is
|
|
449
|
+
## space-bounded on both sides — mandatory trailing space — so
|
|
450
|
+
## "PDAM 19973-2" cannot read 19973 as the ballot number: with no
|
|
451
|
+
## second space the group backtracks and 19973 parses in second_part.
|
|
452
|
+
ballot_supplement_identifier = {
|
|
453
|
+
prefix_with_copublishers
|
|
454
|
+
[space]
|
|
455
|
+
[slash]
|
|
456
|
+
ballot_type_with_stage
|
|
457
|
+
[ space ((1*digit)) as ballot_number space ]
|
|
458
|
+
[space]
|
|
459
|
+
[type_with_stage]
|
|
460
|
+
[space]
|
|
461
|
+
second_part
|
|
462
|
+
[ ballot_stamp ]
|
|
463
|
+
}
|
|
464
|
+
## The space-attached ballot face: "ISO 15877-5:2009 FDAM 1" — the
|
|
465
|
+
## base designation first, then the ballot token attached by a space.
|
|
466
|
+
## The base front is inlined (not supplement_base_no_third): that
|
|
467
|
+
## rule ends in base_third_part_edition, whose own optional space
|
|
468
|
+
## would consume the separator the ballot token needs. No base
|
|
469
|
+
## edition is served here — none of the stored or owner-cited ballot
|
|
470
|
+
## faces carries one.
|
|
471
|
+
ballot_space_supplement_identifier = {
|
|
472
|
+
[ (("Guide" / "GUIDE") as type_with_stage_fr) space ]
|
|
473
|
+
[prefix_with_copublishers]
|
|
474
|
+
[space]
|
|
475
|
+
[slash]
|
|
476
|
+
[type_with_stage]
|
|
477
|
+
[space]
|
|
478
|
+
second_part
|
|
479
|
+
space ballot_type_with_stage
|
|
480
|
+
[ space ((1*digit)) as ballot_number ]
|
|
481
|
+
}
|
|
482
|
+
|
|
398
483
|
# ---- directives: "ISO/IEC DIR 1", "ISO/IEC JTC 1 DIR", supplements,
|
|
399
484
|
# bundled "ISO/IEC DIR 1:2022 + IEC SUP:2022" ----
|
|
400
485
|
directives_publisher_subgroup = ("JTC 1") as subgroup
|
|
@@ -609,7 +694,8 @@ grammar PubidIso version "0.4.0" edition 2 {
|
|
|
609
694
|
}
|
|
610
695
|
|
|
611
696
|
## The ISO identifier root: common-text twin form first, then the
|
|
612
|
-
## single spine.
|
|
697
|
+
## single spine. The historic ballot faces go last — purely additive
|
|
698
|
+
## branches (an input an earlier member parses keeps that parse).
|
|
613
699
|
identifier = {
|
|
614
700
|
bare_tc_document
|
|
615
701
|
/ nsb_iso_identifier
|
|
@@ -623,6 +709,8 @@ grammar PubidIso version "0.4.0" edition 2 {
|
|
|
623
709
|
/ supplement_identifier
|
|
624
710
|
/ joint_identifier
|
|
625
711
|
/ identifier_copublishers
|
|
712
|
+
/ ballot_space_supplement_identifier
|
|
713
|
+
/ ballot_supplement_identifier
|
|
626
714
|
}
|
|
627
715
|
|
|
628
716
|
entry identifier: identifier
|
|
@@ -712,6 +800,12 @@ bs3_undated_marker -> bs3_undated_marker (string)
|
|
|
712
800
|
idf_supp_stage_iteration2 -> idf_supp_stage_iteration2 (string)
|
|
713
801
|
idf_supp_date2 -> idf_supp_date2 (string)
|
|
714
802
|
idf_supp_undated_marker -> idf_supp_undated_marker (string)
|
|
803
|
+
ballot_type_with_stage -> ballot_type_with_stage (string)
|
|
804
|
+
ballot_number -> ballot_number (string)
|
|
805
|
+
ballot_stamp_number -> ballot_stamp_number (string)
|
|
806
|
+
ballot_stamp_dot_number -> ballot_stamp_dot_number (string)
|
|
807
|
+
ballot_stamp_date -> ballot_stamp_date (string)
|
|
808
|
+
ballot_stamp_undated_marker -> ballot_stamp_undated_marker (string)
|
|
715
809
|
}
|
|
716
810
|
|
|
717
811
|
## In-file grammar tests: run at compile time, embedded in the artifact,
|
|
@@ -728,9 +822,55 @@ bs3_undated_marker -> bs3_undated_marker (string)
|
|
|
728
822
|
accept "ISO 5537|IDF 26"
|
|
729
823
|
accept "ISO 4055/Amd 1:2021"
|
|
730
824
|
accept "ISO/R 9:1962"
|
|
825
|
+
accept "ISO/IEC FDAM 1 14496-12:2004"
|
|
826
|
+
accept "ISO/PDAM 19973-2"
|
|
827
|
+
accept "ISO/IEC FPDAM 9075/1"
|
|
828
|
+
accept "ISO/IEC FPDAM 10373-6/Amd 9:2001"
|
|
829
|
+
accept "ISO/PDAM 19110:2005/Amd.1"
|
|
830
|
+
accept "ISO/IEC PDAM TR 9573-11/Amd.1"
|
|
831
|
+
accept "ISO/IEC PDAM 9541-1/Amd.4"
|
|
832
|
+
accept "ISO/IEC PDAM 2 10646:2012"
|
|
833
|
+
accept "ISO 15877-5:2009 FDAM 1"
|
|
834
|
+
accept "ISO 19776-2:2005/Am1:2006"
|
|
731
835
|
reject "nonsense"
|
|
836
|
+
reject "ISO/PDAM"
|
|
837
|
+
reject "ISO/FDAM"
|
|
732
838
|
example "ISO/IEC WD TS 25025" { publisher: "ISO" }
|
|
733
839
|
example "JCGM 200:2008(F)" { publisher: "JCGM" }
|
|
840
|
+
example "ISO/IEC FDAM 1 14496-12:2004" {
|
|
841
|
+
ballot_type_with_stage: "FDAM"
|
|
842
|
+
ballot_number: "1"
|
|
843
|
+
number_with_part: "14496-12"
|
|
844
|
+
date: "2004"
|
|
845
|
+
}
|
|
846
|
+
example "ISO/PDAM 19973-2" {
|
|
847
|
+
ballot_type_with_stage: "PDAM"
|
|
848
|
+
number_with_part: "19973-2"
|
|
849
|
+
}
|
|
850
|
+
example "ISO/IEC FPDAM 10373-6/Amd 9:2001" {
|
|
851
|
+
ballot_type_with_stage: "FPDAM"
|
|
852
|
+
ballot_stamp_number: "9"
|
|
853
|
+
ballot_stamp_date: "2001"
|
|
854
|
+
}
|
|
855
|
+
example "ISO/PDAM 19110:2005/Amd.1" {
|
|
856
|
+
ballot_type_with_stage: "PDAM"
|
|
857
|
+
ballot_stamp_number: "1"
|
|
858
|
+
}
|
|
859
|
+
example "ISO/IEC FPDAM 9075/1" {
|
|
860
|
+
ballot_type_with_stage: "FPDAM"
|
|
861
|
+
number_with_part: "9075/1"
|
|
862
|
+
}
|
|
863
|
+
example "ISO 15877-5:2009 FDAM 1" {
|
|
864
|
+
ballot_type_with_stage: "FDAM"
|
|
865
|
+
ballot_number: "1"
|
|
866
|
+
number_with_part: "15877-5"
|
|
867
|
+
date: "2009"
|
|
868
|
+
}
|
|
869
|
+
example "ISO/IEC FDAM 19776-2:2005/Am1:2006" {
|
|
870
|
+
ballot_type_with_stage: "FDAM"
|
|
871
|
+
ballot_stamp_number: "1"
|
|
872
|
+
ballot_stamp_date: "2006"
|
|
873
|
+
}
|
|
734
874
|
}
|
|
735
875
|
}
|
|
736
876
|
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
- abbr: "ANW Amd"
|
|
5
5
|
- abbr: "WD Amd"
|
|
6
6
|
- abbr: "CDAM"
|
|
7
|
+
- abbr: "PDAM"
|
|
7
8
|
- abbr: "DAM"
|
|
8
9
|
- abbr: "FDAM"
|
|
10
|
+
- abbr: "FPDAM"
|
|
9
11
|
- abbr: "PRF Amd"
|
|
10
12
|
- abbr: "Amd"
|
|
11
13
|
- abbr: "AMD"
|
|
@@ -14,6 +16,7 @@
|
|
|
14
16
|
- abbr: "ANW Cor"
|
|
15
17
|
- abbr: "WDCor"
|
|
16
18
|
- abbr: "CDCor"
|
|
19
|
+
- abbr: "CDCOR"
|
|
17
20
|
- abbr: "DCOR"
|
|
18
21
|
- abbr: "FDCOR"
|
|
19
22
|
- abbr: "PRF Cor"
|
data/lib/pubid/iec/CLAUDE.md
CHANGED
|
@@ -39,7 +39,11 @@ These notes were part of the root `CLAUDE.md`. Read them before you change `lib/
|
|
|
39
39
|
|
|
40
40
|
**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.
|
|
41
41
|
|
|
42
|
-
- **A standalone supplement is `==` its `from_hash(to_hash)` round trip — the cause was the synthetic base's stage, not the ivar swap.** A supplement written without its base (`IEC/FDAM 60038-1`, `IEC/DCOR 60038-1`, `IEC/DISH 60038-1`) gets a bare `Pubid::Iec::SingleIdentifier` base from `SupplementIdentifier#ensure_base`; `to_s`, `to_urn`, `to_hash` and `#hash` agreed on both paths, but `==` was false, so `#matches?` failed silently. **The hand-off blamed the swapped `@number`/`@part` ivars; that was wrong.** `EqualityIgnored#==` compares through `public_send`, so the reader overrides hid the swap from it. The real difference was `base.typed_stage`: `ensure_base` set an IS/undated `TypedStage` with no `code`, which `stage_to_kv` cannot serialize, so `from_hash` rebuilt the base with none. **Diagnose an `==` failure by diffing the attributes through their readers, recursively, before trusting a hypothesis about ivars.** The stage write is deleted (the JCGM "delete the write" shape); the one visible change is `root.to_s` of the synthetic base on the parse path (`IEC IS60038` → `IEC60038`, the value `from_hash` already gave). The ivar swap was cleaned up too, because it was a hazard: `ensure_base` now moves the supplement number from `@part` into `@number`, so the stored layout is what `from_hash` rebuilds, and the `#number`/`#part` overrides, `@supplement_number`, `@synthetic_base` and `mark_synthetic_standalone!` are gone. `synthetic_base?` reads `base.instance_of?(SingleIdentifier)`, so it also survives `#exclude`, which copies `base` into a new object (on `main` the copy lost the flag and rendered in attached form, `IEC IS60038/FDAM1`). **The move must not depend on which reader runs first**, and a first version got that wrong: it called `ensure_base` only in the slash-form branch of `Builder#build`, but the canonical spelling `IEC FDAM 60038-1` (what `to_s` prints) reaches a different grammar branch, so a fresh parse kept the grammar layout until something read `base` — `.number` answered `60038`, `to_mr_string` gave `iec.60038_amd.60038`, and `#exclude` (which copies through the readers) copied the unmoved numbers onto a base synthesised afterwards, rendering `IEC FDAM 60038-60038`. So `#number`/`#part` keep a thin override that only TRIGGERS the move (`settle_standalone_layout`: `ensure_base` when there is a `part` and no base) and then return the stored value, and `Iec::Identifier.parse` calls `ensure_base` on a top-level supplement so a fresh parse already stores the moved layout. Supplements without a part (consolidation members, `IEC/AMD 1:2017`) are untouched by either. Two renderings outside the fixture corpus move, both repairs: `exclude(:date)` of a standalone supplement keeps the standalone form, and `IEC FDAM 60038-1 (all parts)` renders as itself instead of `IEC IS60038/FDAM1 (all parts)`. One hazard is left, unreachable from the grammar: reading `part` on a hand-built supplement BEFORE its base is assigned synthesises a base and moves the number, so a later `base =` renders the moved number. **A second hand-off claim was also wrong: an equal `#hash` for two unequal objects is legal** (the rule is `a == b ⇒ a.hash == b.hash`); `compute_hash` does not read `base`, which is a collision, not a defect. Measured: all 12,331 pass-fixture ids are byte-identical in `to_s`, `to_urn`, `to_mr_string`, `root.number`, `to_hash` and class; all 32,949 `relaton-data-iec` `index-v2.yaml` rows keep `from_hash(row).to_hash == row` and `parse(from_hash(row).to_s) == from_hash(row)`, and none has a `pubid:iec:single-identifier` base, so **no index migration follows**. Locked by `spec/pubid/iec/standalone_supplement_round_trip_spec.rb` and by the six standalone fixtures now in `full/iec-pubid.txt`, which the whole-corpus `==` sweep in `spec/pubid/iec/root_number_spec.rb` covers. **Residue, deliberately unchanged:** the self-base form renders its number twice — `IEC/AMD 1:2017` → `IEC Amd 1-1:2017
|
|
42
|
+
- **A standalone supplement is `==` its `from_hash(to_hash)` round trip — the cause was the synthetic base's stage, not the ivar swap.** A supplement written without its base (`IEC/FDAM 60038-1`, `IEC/DCOR 60038-1`, `IEC/DISH 60038-1`) gets a bare `Pubid::Iec::SingleIdentifier` base from `SupplementIdentifier#ensure_base`; `to_s`, `to_urn`, `to_hash` and `#hash` agreed on both paths, but `==` was false, so `#matches?` failed silently. **The hand-off blamed the swapped `@number`/`@part` ivars; that was wrong.** `EqualityIgnored#==` compares through `public_send`, so the reader overrides hid the swap from it. The real difference was `base.typed_stage`: `ensure_base` set an IS/undated `TypedStage` with no `code`, which `stage_to_kv` cannot serialize, so `from_hash` rebuilt the base with none. **Diagnose an `==` failure by diffing the attributes through their readers, recursively, before trusting a hypothesis about ivars.** The stage write is deleted (the JCGM "delete the write" shape); the one visible change is `root.to_s` of the synthetic base on the parse path (`IEC IS60038` → `IEC60038`, the value `from_hash` already gave). The ivar swap was cleaned up too, because it was a hazard: `ensure_base` now moves the supplement number from `@part` into `@number`, so the stored layout is what `from_hash` rebuilds, and the `#number`/`#part` overrides, `@supplement_number`, `@synthetic_base` and `mark_synthetic_standalone!` are gone. `synthetic_base?` reads `base.instance_of?(SingleIdentifier)`, so it also survives `#exclude`, which copies `base` into a new object (on `main` the copy lost the flag and rendered in attached form, `IEC IS60038/FDAM1`). **The move must not depend on which reader runs first**, and a first version got that wrong: it called `ensure_base` only in the slash-form branch of `Builder#build`, but the canonical spelling `IEC FDAM 60038-1` (what `to_s` prints) reaches a different grammar branch, so a fresh parse kept the grammar layout until something read `base` — `.number` answered `60038`, `to_mr_string` gave `iec.60038_amd.60038`, and `#exclude` (which copies through the readers) copied the unmoved numbers onto a base synthesised afterwards, rendering `IEC FDAM 60038-60038`. So `#number`/`#part` keep a thin override that only TRIGGERS the move (`settle_standalone_layout`: `ensure_base` when there is a `part` and no base) and then return the stored value, and `Iec::Identifier.parse` calls `ensure_base` on a top-level supplement so a fresh parse already stores the moved layout. Supplements without a part (consolidation members, `IEC/AMD 1:2017`) are untouched by either. Two renderings outside the fixture corpus move, both repairs: `exclude(:date)` of a standalone supplement keeps the standalone form, and `IEC FDAM 60038-1 (all parts)` renders as itself instead of `IEC IS60038/FDAM1 (all parts)`. One hazard is left, unreachable from the grammar: reading `part` on a hand-built supplement BEFORE its base is assigned synthesises a base and moves the number, so a later `base =` renders the moved number. **A second hand-off claim was also wrong: an equal `#hash` for two unequal objects is legal** (the rule is `a == b ⇒ a.hash == b.hash`); `compute_hash` does not read `base`, which is a collision, not a defect. Measured: all 12,331 pass-fixture ids are byte-identical in `to_s`, `to_urn`, `to_mr_string`, `root.number`, `to_hash` and class; all 32,949 `relaton-data-iec` `index-v2.yaml` rows keep `from_hash(row).to_hash == row` and `parse(from_hash(row).to_s) == from_hash(row)`, and none has a `pubid:iec:single-identifier` base, so **no index migration follows**. Locked by `spec/pubid/iec/standalone_supplement_round_trip_spec.rb` and by the six standalone fixtures now in `full/iec-pubid.txt`, which the whole-corpus `==` sweep in `spec/pubid/iec/root_number_spec.rb` covers. **Residue, deliberately unchanged:** the self-base form renders its number twice — `IEC/AMD 1:2017` → `IEC Amd 1-1:2017` — because `render_supplement_standalone` prints `base.number` and then the supplement's own number, which are the same value there. (hand-off: iec-standalone-supplement-ivar-swap.) **CORRECTED 2026-11-12 (owner ruling, reverses this bullet's identity):** the part-to-number move this bullet introduced is the DEFECT the legacy-decode bullet below removes. `IEC/FDAM 60038-1` is not "base 60038 with FDAM 1": the whole `60038-1` is the base designation. The equality machinery this bullet built (the stored layout `from_hash` rebuilds, the reader-triggered settle, `synthetic_base?` reading the class) is kept and holds on the corrected identity.
|
|
43
|
+
|
|
44
|
+
- **The legacy prefix faces decode onto the modern identity (owner ruling 2026-11-12; evidence: `data/iec/legacy-notation.md`; reverses #693's resolution).** The old notation prints the stage token BEFORE the base designation — the JTC1 ballot faces `IEC/FDAM 60038-1`, `ISO/IEC PDAM 12207`, `ISO/IEC PDAM 2 10646:2012`, `ISO/IEC FPDAM 9075/1`, `ISO/IEC FDAM 1 14496-12:2004`, and the IEC draft faces `IEC/DCOR 60038-1`, `IEC/DISH 60038-1`, `IEC CDISH 60050-191`. #693's model had resolved those by STEALING the designation's part for the supplement (`ensure_base` moved `@part` into `@number`), so the old face and the modern on-base face (`IEC 60038-1/FDAM 1`) disagreed on the same document's identity. The decode now: **the designation moves WHOLE onto the base** (number, part, subpart, and the date — old notation prints the base's year, and a draft supplement carries no publication date of its own), **the supplement's number defaults to 1** (the no-number rule: an unnumbered DAM/PDAM/FDAM/FPDAM/DCOR/DISH means Amendment/Corrigendum/Interpretation-Sheet 1), and **the base is a concrete `InternationalStandard`**, not the synthetic `SingleIdentifier` — that concreteness is what makes the legacy face hash-match (and equal) the modern parse: `parse("IEC/DCOR 60038-1") == parse("IEC 60038-1/DCOR 1")`. The render is the modern on-base slash face (`IEC 60038-1/FDAM1`), so the legacy face is not a spelling facet — it is decoded, IHO-style.
|
|
45
|
+
**Two grammar halves.** The DAM-family prefix faces (`DAM`/`PDAM`/`FDAM`/`FPDAM`, spelled literally in the rule so the typed_stages table stays put for them) raised on main; the new `legacy_supplement` rule in `pubid-grammar` captures `legacy_stage`, the optional `legacy_supplement_number` (N before the base), and the base designation — including the old slash-part spelling (`FPDAM 9075/1` = base 9075, part 1; captured as `legacy_base_number` + `legacy_base_part`, and ordered BEFORE the dash-part alternative because `number_with_part` also matches a bare "9075", which would have left the "/1" dangling). The rule sits ahead of `standalone_supplement` in the ordered choice so both the slash and space faces reach it, and its date rides with the designation. PDAM/FPDAM joined the `iec_typed_stages` table and `Amendment::TYPED_STAGES` (stage codes mirror ISO's entries: PDAM at the CD level, FPDAM at the FDIS level; the LEAD harmonized codes are the circulation slots 30.20/50.20 — ISO's 30.00/50.00 are CDAM's and FDAM's (type, leading code) URN keys). The non-DAM draft faces (DCOR, FDCOR, CDISH, DISH, and any typed standalone with a designation-shaped token) already parsed; they are corrected by `ensure_base`, so the two paths converge on one decode. **The discriminator between a legacy face and a true standalone is the PART:** a designation token (`60038-1`, `9075/1`) moves whole onto the base; a bare ordinal (`IEC/AMD 1:2017`) is the supplement's own number and keeps the synthetic self-base (`IEC Amd 1-1:2017`, unchanged). Under the JTC1 reading a bare number after a DAM-family token is a base designation (`IEC FDAM 60038` → `IEC 60038/FDAM1`), not a self-base mangle — that residue from the #693 bullet is gone.
|
|
46
|
+
**Measured by the replay gate, not the suite alone:** over all 26,838 full-fixture inputs, `to_s`/`to_urn`/`to_mr_string`/`to_hash`/class/`root.number` are byte-identical to the `main` baseline EXCEPT exactly the 6 #693-era standalone rows (both faces of FDAM/DCOR/DISH `60038-1`), which re-resolve to the corrected identities; 0 inputs stopped parsing; the prefix faces go raise→parse (12,491 full entries → 12,359 pass, fail steady at 132). #693's equality invariant (`parse == from_hash(to_hash)`) holds on every corrected identity. Locked by `spec/pubid/iec/standalone_supplement_round_trip_spec.rb` (rewritten: legacy faces decode and equal their modern parses, the self-base standalone keeps its form), `stage_separator_spec.rb`, `amendment_spec.rb`, `corrigendum_spec.rb`, `interpretation_sheet_spec.rb`, and the pass fixtures re-pinned by `validation:classify`. **Remaining gaps, deliberately open:** the dotted on-base `/Amd.1` spelling still raises in the IEC flavor (ISO accepts it), and the stamped prefix faces (`ISO/IEC FPDAM 10373-6/Amd 9:2001`, `ISO/IEC PDAM 9541-1/Amd.4`, `ISO/IEC FDAM 19776-2:2005/Am1:2006`, `ISO/IEC PDAM TR 9573-11/Amd.1`) stay raising — their on-base amendment stamps need the dotted/ISO stamp spellings the IEC flavor does not carry (gap 4 of the draft); the ISO-side agent owns that study.
|
|
43
47
|
|
|
44
48
|
## pubid-scheme URN (issue #670; gaps closed by #687)
|
|
45
49
|
|
data/lib/pubid/iec/builder.rb
CHANGED
|
@@ -57,6 +57,53 @@ module Pubid
|
|
|
57
57
|
def build(parsed_hash)
|
|
58
58
|
all_parts = parsed_hash.delete(:all_parts)
|
|
59
59
|
|
|
60
|
+
# Legacy DAM-family prefix face (IEC/FDAM 60038-1, ISO/IEC PDAM 2
|
|
61
|
+
# 10646:2012, ISO/IEC FPDAM 9075/1): the JTC1 ballot spelling puts
|
|
62
|
+
# the stage token before the base designation. Decode onto the
|
|
63
|
+
# modern identity (owner ruling 2026-11-12, with data/iec/
|
|
64
|
+
# legacy-notation.md): the designation is the BASE's, kept whole —
|
|
65
|
+
# the old slash-part spelling "9075/1" is base 9075 part 1 — and
|
|
66
|
+
# the supplement's number is the captured N, defaulting to 1 (the
|
|
67
|
+
# no-number rule: unnumbered DAM/PDAM/FDAM/FPDAM means Amendment 1).
|
|
68
|
+
# The date rides with the designation (old notation prints the
|
|
69
|
+
# base's year); a draft supplement carries none of its own. The
|
|
70
|
+
# base builds through the ordinary single-document path, so it is
|
|
71
|
+
# the same InternationalStandard the modern on-base parse holds and
|
|
72
|
+
# `parse(legacy) == parse(modern)`.
|
|
73
|
+
if parsed_hash[:legacy_stage]
|
|
74
|
+
supp = build_supplement(
|
|
75
|
+
type_with_stage: parsed_hash[:legacy_stage],
|
|
76
|
+
number_with_part: parsed_hash[:legacy_supplement_number] || "1",
|
|
77
|
+
)
|
|
78
|
+
base_hash = {}
|
|
79
|
+
{
|
|
80
|
+
publisher: :publisher,
|
|
81
|
+
legacy_base_number_with_part: :number_with_part,
|
|
82
|
+
date: :date,
|
|
83
|
+
undated_marker: :undated_marker,
|
|
84
|
+
edition: :edition,
|
|
85
|
+
languages: :languages,
|
|
86
|
+
}.each do |source, target|
|
|
87
|
+
base_hash[target] = parsed_hash[source] if parsed_hash[source]
|
|
88
|
+
end
|
|
89
|
+
# The grammar binds the legacy face's copublisher as a top-level
|
|
90
|
+
# scalar (legacy_copublisher) — the wasm binder scatters array
|
|
91
|
+
# bindings over the sibling list when a bare copublisher node
|
|
92
|
+
# rides a flat shape — so the builder attaches it here, in the
|
|
93
|
+
# same shape the parse path used to carry.
|
|
94
|
+
if parsed_hash[:legacy_copublisher]
|
|
95
|
+
base_hash[:copublishers] = [{ copublisher: parsed_hash[:legacy_copublisher] }]
|
|
96
|
+
end
|
|
97
|
+
# The slash-part designation ("9075/1") captured number and part
|
|
98
|
+
# separately; they go on straight as the plain strings the retyped
|
|
99
|
+
# attributes are, not through cast (which has no :part type).
|
|
100
|
+
base = build(base_hash)
|
|
101
|
+
base.number = parsed_hash[:legacy_base_number].to_s if parsed_hash[:legacy_base_number]
|
|
102
|
+
base.part = parsed_hash[:legacy_base_part].to_s if parsed_hash[:legacy_base_part]
|
|
103
|
+
return apply_fragment_notation(wrap_with_supplement(base, supp),
|
|
104
|
+
parsed_hash)
|
|
105
|
+
end
|
|
106
|
+
|
|
60
107
|
# Second supplement layer (/AMD1:2018/COR1:2019): build the
|
|
61
108
|
# first-supplement form, then wrap the second supplement around
|
|
62
109
|
# it.
|
|
@@ -54,6 +54,25 @@ module Pubid
|
|
|
54
54
|
name: "Committee Draft Amendment",
|
|
55
55
|
harmonized_stages: %w[30.00 30.20 30.60 30.92 30.98 30.99],
|
|
56
56
|
),
|
|
57
|
+
# The JTC1 ladder's pre-DAM ballot face (zero IEC-only record usage
|
|
58
|
+
# — the DAM family rides on joint work only; see
|
|
59
|
+
# data/iec/legacy-notation.md). Stage codes mirror the ISO flavor's
|
|
60
|
+
# entry for the same letter: PDAM sits at the CD level (30.xx).
|
|
61
|
+
# The LEAD harmonized code is the circulation slot (30.20
|
|
62
|
+
# "circulated as CD"), not ISO's 30.00 — the URN writes
|
|
63
|
+
# stage-{harmonized_stages.first} and parses it back by
|
|
64
|
+
# (type, leading code), which CDAM (30.00) already claims; see
|
|
65
|
+
# spec/pubid/iec/pnw_stage_spec.rb.
|
|
66
|
+
Pubid::Components::TypedStage.new(
|
|
67
|
+
code: :pdam,
|
|
68
|
+
stage_code: :cd,
|
|
69
|
+
type_code: :amd,
|
|
70
|
+
abbr: ["PDAM"],
|
|
71
|
+
short_abbr: nil,
|
|
72
|
+
long_abbr: nil,
|
|
73
|
+
name: "Proposed Draft Amendment",
|
|
74
|
+
harmonized_stages: %w[30.20 30.00 30.60 30.92 30.93 30.98 30.99],
|
|
75
|
+
),
|
|
57
76
|
Pubid::Components::TypedStage.new(
|
|
58
77
|
code: :damd,
|
|
59
78
|
stage_code: :damd,
|
|
@@ -74,6 +93,21 @@ module Pubid
|
|
|
74
93
|
name: "Final Draft Amendment",
|
|
75
94
|
harmonized_stages: %w[50.00 50.20 50.60 50.92 50.98 50.99],
|
|
76
95
|
),
|
|
96
|
+
# The JTC1 ladder's pre-FDAM ballot face (same provenance as PDAM
|
|
97
|
+
# above): FPDAM mirrors the ISO flavor's entry, at the FDIS level
|
|
98
|
+
# (50.xx). Its LEAD harmonized code is the circulation slot
|
|
99
|
+
# (50.20), not ISO's 50.00, which FDAM already claims as its
|
|
100
|
+
# (type, leading code) URN key.
|
|
101
|
+
Pubid::Components::TypedStage.new(
|
|
102
|
+
code: :fpdam,
|
|
103
|
+
stage_code: :fdamd,
|
|
104
|
+
type_code: :amd,
|
|
105
|
+
abbr: ["FPDAM"],
|
|
106
|
+
short_abbr: nil,
|
|
107
|
+
long_abbr: nil,
|
|
108
|
+
name: "Final Proposed Draft Amendment",
|
|
109
|
+
harmonized_stages: %w[50.20 50.00 50.60 50.92 50.98 50.99],
|
|
110
|
+
),
|
|
77
111
|
Pubid::Components::TypedStage.new(
|
|
78
112
|
code: :pubamd,
|
|
79
113
|
stage_code: :published,
|
|
@@ -44,7 +44,12 @@ module Pubid
|
|
|
44
44
|
code: :cdcor,
|
|
45
45
|
stage_code: :cd,
|
|
46
46
|
type_code: :cor,
|
|
47
|
-
|
|
47
|
+
# "CDCOR" is the all-caps spelling the attached-supplement
|
|
48
|
+
# renderer emits (abbr.first.upcase) when a legacy prefix face
|
|
49
|
+
# decodes onto a CDCor draft ("IEC/CDCor 60038-1" renders
|
|
50
|
+
# "IEC 60038-1/CDCOR1"); appended as an alias so the canonical
|
|
51
|
+
# render re-parses. abbr.first stays "CDCor" (spelling-only).
|
|
52
|
+
abbr: ["CDCor", "CDCOR"],
|
|
48
53
|
short_abbr: "CDCor",
|
|
49
54
|
long_abbr: "CD Cor",
|
|
50
55
|
name: "Committee Draft Corrigendum",
|
|
@@ -4,6 +4,11 @@ module Pubid
|
|
|
4
4
|
module Iec
|
|
5
5
|
# Identifier that represents a supplement to a base identifier.
|
|
6
6
|
class SupplementIdentifier < SingleIdentifier
|
|
7
|
+
# The supplement number a legacy standalone face implies when its
|
|
8
|
+
# type token carries none ("IEC/DCOR 60038-1" is Corrigendum 1 of
|
|
9
|
+
# 60038-1); the owner's no-number rule.
|
|
10
|
+
LEGACY_STANDALONE_SUPPLEMENT_NUMBER = "1"
|
|
11
|
+
|
|
7
12
|
attribute :base, Identifier, polymorphic: true
|
|
8
13
|
|
|
9
14
|
# Serialize the supplemented document under "base". The supplement's own
|
|
@@ -22,8 +27,9 @@ module Pubid
|
|
|
22
27
|
# (e.g. a bare "+AMD1:2008" nested in a consolidation): a fake doc whose
|
|
23
28
|
# number equals the supplement's own. It renders nothing and the getter
|
|
24
29
|
# recreates it identically on load, so don't persist it. A standalone
|
|
25
|
-
# supplement ("IEC/
|
|
26
|
-
# the
|
|
30
|
+
# supplement in the legacy prefix face ("IEC/DCOR 60038-1") instead
|
|
31
|
+
# holds the real base designation (base.number != own number), which
|
|
32
|
+
# must be kept.
|
|
27
33
|
return if model.synthetic_base? && base.number.to_s == model.number.to_s
|
|
28
34
|
|
|
29
35
|
doc.add_child(Lutaml::KeyValue::DataModel::Element.new("base",
|
|
@@ -45,13 +51,14 @@ module Pubid
|
|
|
45
51
|
@base || ensure_base
|
|
46
52
|
end
|
|
47
53
|
|
|
48
|
-
# A standalone supplement
|
|
49
|
-
#
|
|
50
|
-
# Not every construction path goes through one
|
|
51
|
-
# 60038-1"
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
54
|
+
# A standalone supplement written in the legacy prefix face holds the
|
|
55
|
+
# grammar's layout (the whole base designation on itself) until
|
|
56
|
+
# #ensure_base moves it. Not every construction path goes through one
|
|
57
|
+
# builder branch ("IEC DCOR 60038-1" reaches the typed branch, a
|
|
58
|
+
# hand-built Amendment none), so these readers make the move before
|
|
59
|
+
# they answer: the result never depends on which reader ran first, and
|
|
60
|
+
# #exclude, which copies through the readers, copies the moved layout.
|
|
61
|
+
# They only TRIGGER the move; they return the stored value.
|
|
55
62
|
def number
|
|
56
63
|
settle_standalone_layout
|
|
57
64
|
super
|
|
@@ -81,14 +88,27 @@ module Pubid
|
|
|
81
88
|
|
|
82
89
|
# Synthesise the base of a supplement that names none, and return it.
|
|
83
90
|
#
|
|
84
|
-
# A standalone supplement
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
91
|
+
# A standalone supplement written in the legacy prefix face ("IEC/DCOR
|
|
92
|
+
# 60038-1", "IEC/DISH 60038-1") arrives from the grammar with the BASE
|
|
93
|
+
# designation stored on itself — the base number in `number`, the base
|
|
94
|
+
# part (and subpart) in `part`/`subpart`. That layout is the grammar's
|
|
95
|
+
# bound shape, not the identity: the face names a supplement OF that
|
|
96
|
+
# designation, so the designation moves WHOLE onto a concrete
|
|
97
|
+
# InternationalStandard base (publisher, copublishers and the date go
|
|
98
|
+
# with it — old notation prints the base's year, and a draft
|
|
99
|
+
# supplement carries no publication date of its own) and the
|
|
100
|
+
# supplement keeps a number of its own, defaulting to 1 — the
|
|
101
|
+
# no-number rule (an unnumbered draft face means
|
|
102
|
+
# Amendment/Corrigendum/Interpretation-Sheet 1; owner ruling
|
|
103
|
+
# 2026-11-12, reversing #693's part-to-number move, which stole the
|
|
104
|
+
# base's part for the supplement). The concrete base is what makes the
|
|
105
|
+
# decode hash-match (and equal) the modern on-base parse of the same
|
|
106
|
+
# document: `parse("IEC/DCOR 60038-1") == parse("IEC 60038-1/DCOR1")`.
|
|
107
|
+
#
|
|
108
|
+
# A supplement with no part (a true standalone like "IEC/AMD 1:2017",
|
|
109
|
+
# or a bare "+AMD1:2008" in a consolidation) keeps its own number and
|
|
110
|
+
# gets a synthetic self-base whose number equals it; its date stays
|
|
111
|
+
# its own.
|
|
92
112
|
#
|
|
93
113
|
# The base deliberately gets NO typed_stage. A stage without a code is
|
|
94
114
|
# not serialized, so `from_hash` cannot restore one, and a stage set
|
|
@@ -102,15 +122,22 @@ module Pubid
|
|
|
102
122
|
@ensuring_base = true
|
|
103
123
|
return nil unless publisher && (@number || @part)
|
|
104
124
|
|
|
105
|
-
base = SingleIdentifier.new
|
|
106
|
-
base.publisher = publisher
|
|
107
|
-
base.number = @number if @number
|
|
108
|
-
base.subpart = @subpart if @subpart
|
|
109
|
-
base.date = date if date
|
|
110
|
-
|
|
111
125
|
if @part
|
|
112
|
-
|
|
126
|
+
base = Identifiers::InternationalStandard.new
|
|
127
|
+
base.publisher = publisher
|
|
128
|
+
base.copublishers = copublishers if copublishers&.any?
|
|
129
|
+
base.number = @number
|
|
130
|
+
base.part = @part
|
|
131
|
+
base.subpart = @subpart if @subpart
|
|
132
|
+
base.date = date if date
|
|
133
|
+
@number = LEGACY_STANDALONE_SUPPLEMENT_NUMBER
|
|
113
134
|
@part = nil
|
|
135
|
+
@date = nil
|
|
136
|
+
else
|
|
137
|
+
base = SingleIdentifier.new
|
|
138
|
+
base.publisher = publisher
|
|
139
|
+
base.number = @number if @number
|
|
140
|
+
base.date = date if date
|
|
114
141
|
end
|
|
115
142
|
|
|
116
143
|
@base = base
|
|
@@ -122,7 +149,10 @@ module Pubid
|
|
|
122
149
|
# from_hash) synthesised for a standalone supplement; a real attached
|
|
123
150
|
# base is always a concrete type (InternationalStandard, SheetIdentifier
|
|
124
151
|
# …). Read from the base itself, so it survives #exclude, which copies
|
|
125
|
-
# `base` into a new object.
|
|
152
|
+
# `base` into a new object. A legacy prefix face's designation moves
|
|
153
|
+
# onto a CONCRETE InternationalStandard instead (see #ensure_base), so
|
|
154
|
+
# it renders in the attached, modern form and hash-matches the modern
|
|
155
|
+
# on-base parse of the same document.
|
|
126
156
|
def synthetic_base?
|
|
127
157
|
base.instance_of?(::Pubid::Iec::SingleIdentifier)
|
|
128
158
|
end
|
data/lib/pubid/iho/CLAUDE.md
CHANGED
|
@@ -234,4 +234,5 @@ component with its own edition/year right after it (`sup-`, `part-`,
|
|
|
234
234
|
`parted-`, `appendix-`, `appsub-`, `vol-`, `issue-` …). Case is preserved
|
|
235
235
|
(`part-A`); the yearbook year is a volume (`vol-2024`, no `year-` value —
|
|
236
236
|
the legacy `y.` marker stays decodable in the official URN parser, never
|
|
237
|
-
emitted). The
|
|
237
|
+
emitted). The positional `urn:iho:` form of `UrnGenerator` stays on
|
|
238
|
+
`#to_urn` — pubid emits no `urn:mrn:iho:…` URN.
|
|
@@ -17,7 +17,8 @@ module Pubid
|
|
|
17
17
|
# `specvar-`). Letters stay case-preserved (`part-A`). The legacy
|
|
18
18
|
# `y.` yearbook marker is decodable in the official URN parser, never
|
|
19
19
|
# emitted here: a periodical has no `year-` atom value, its volume
|
|
20
|
-
# carries the year. The
|
|
20
|
+
# carries the year. The positional urn:iho:… form of UrnGenerator
|
|
21
|
+
# stays on #to_urn — pubid emits no urn:mrn:iho:… URN.
|
|
21
22
|
PubidUrnSchema = Pubid::PubidUrn.define("iho") do
|
|
22
23
|
atom "edition", "ed"
|
|
23
24
|
atom "year", "year"
|
data/lib/pubid/iso/CLAUDE.md
CHANGED
|
@@ -158,3 +158,52 @@
|
|
|
158
158
|
another reason — `DirectivesSupplement#supplement_publisher` is an
|
|
159
159
|
`Iso::Components::Publisher` after `parse` and a `Pubid::Components::Publisher`
|
|
160
160
|
after `from_hash` (11 supplements, 3 bundles).
|
|
161
|
+
|
|
162
|
+
- **Historic TYPE-first ballot faces decode to the canonical on-base slash
|
|
163
|
+
form (owner ruling 2026-11-12, Option B — IHO-style decode-to-modern;
|
|
164
|
+
SSOT `data/iso/historic-schemes.md`).** The DAM-family token in TYPE
|
|
165
|
+
position (`ISO/IEC FDAM 1 14496-12:2004`, `ISO/PDAM 19973-2`) and the
|
|
166
|
+
space-attached ballot face (`ISO 15877-5:2009 FDAM 1`) used to raise; the
|
|
167
|
+
grammar (v0.5.0) gained two additive branches at the END of the
|
|
168
|
+
identifier alternation, so every face an existing member parses keeps its
|
|
169
|
+
exact parse (replayed: 15,307 fixture inputs and 213,414 stored
|
|
170
|
+
docidentifier faces byte-identical on `to_s`/`to_urn`/`to_hash`/
|
|
171
|
+
`to_mr_string`). What to know before editing either side:
|
|
172
|
+
- **The ballot token captures as `ballot_type_with_stage`** (table
|
|
173
|
+
`typed_stages_ballots`: PDAM, DAM, DAmd, FDAM, FDAmd, FPDAM), NOT as
|
|
174
|
+
`supp_type_with_stage` — a shared name would make the builder's
|
|
175
|
+
supplement-prefix peel wrap a second layer, while the stored twins keep
|
|
176
|
+
ONE level. `Builder#build_ballot_supplement` assembles the single
|
|
177
|
+
amendment layer and nests the rest (prefix, copublishers, the base's
|
|
178
|
+
own `type_with_stage` — the `TR` of `PDAM TR 9573-11` — and
|
|
179
|
+
`number_with_part`/`date`) as `:base`.
|
|
180
|
+
- **`ballot_number` is space-bounded on both sides** (mandatory trailing
|
|
181
|
+
space in the grammar group) so `PDAM 19973-2` cannot read 19973 as the
|
|
182
|
+
ballot number: with no second space the group backtracks and 19973
|
|
183
|
+
stays in `second_part`. This is the base-whole guard — the IEC-flavor
|
|
184
|
+
hazard (#693's `ensure_base`) of a base part migrating into the
|
|
185
|
+
supplement slot cannot happen here; the legacy slash-part of
|
|
186
|
+
`FPDAM 9075/1` stays a base part (`9075-1`).
|
|
187
|
+
- **The number precedence is TYPE token → on-base stamp → implied "1"**;
|
|
188
|
+
the stamp's number/date capture as `ballot_stamp_*` while its token
|
|
189
|
+
letters match UNCAPTURED (Option B keeps no spelling — the ballot's
|
|
190
|
+
stage is the TYPE token's). The implied 1 is the owner's no-number
|
|
191
|
+
rule, applied by `Builder#apply_implied_amendment_number` on EVERY
|
|
192
|
+
builder path (parse, URN, bundle members) for `Identifiers::Amendment`
|
|
193
|
+
with a nil number and `typed_stage.code` ∈ `{pdam, damd, fdamd, fpdam}`
|
|
194
|
+
— by `#code`, NOT `#stage_code` (PDAM's stage_code is `:cd`), and
|
|
195
|
+
`to_sym`-ed (the bound map delivers strings). The published stage
|
|
196
|
+
(abbr `Amd`) is out of scope: a numberless `ISO 1234/Amd` stays nil.
|
|
197
|
+
- **The `Am` spelling** joined `typed_stages_supplements` (after the Amd
|
|
198
|
+
entries) and the published stage's abbr list, so `/Am1:2006` parses as
|
|
199
|
+
an alias of `/Amd 1` through the EXISTING supplement branch and the
|
|
200
|
+
ballot stamp accepts it the same way. `detect_stage_format_long` now
|
|
201
|
+
treats an original spelling that is a prefix of the long abbr as the
|
|
202
|
+
long family, so `Am` renders canonical `Amd`; `AMD` still renders short
|
|
203
|
+
(facets keep spelling on render), `Amd.` still renders `Amd`.
|
|
204
|
+
- **The decode is one-way**: parse-in only, canonical renders emit the
|
|
205
|
+
on-base slash face, and a ballot face == its canonical spelling
|
|
206
|
+
(original_abbr is equality-ignored). Locked by
|
|
207
|
+
`spec/pubid/iso/ballot_face_decode_spec.rb` (all 13 SSOT anchors, the
|
|
208
|
+
no-number rule, base-whole guards, Am alias, stored-twin agreement, and
|
|
209
|
+
the website-face-vs-stored-face drift rows the SSOT §5 records).
|