Skip to content

cimas sync 2026-07-08: rubocop todo inherit fix + release.yml bundle install#90

Open
opoudjis wants to merge 22 commits into
mainfrom
cimas-sync-2026-07-08
Open

cimas sync 2026-07-08: rubocop todo inherit fix + release.yml bundle install#90
opoudjis wants to merge 22 commits into
mainfrom
cimas-sync-2026-07-08

Conversation

@opoudjis

@opoudjis opoudjis commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Supersedes #88 from prior cimas-sync waves.

Summary

Cimas sync 2026-07-08 — master template corrections for two release-adjacent gaps:

  1. .rubocop.yml — appended .rubocop_todo.yml as the last inherit_from entry so per-gem todos take effect against the shared oss-guides config. inherit_from is last-wins; the previous ordering left the shared config's stricter Metrics/* rules overriding per-gem grandfathering, and the todo became inert. Empirically verified on suma 2026-07-06: with the todo listed first, 34 Metrics/* offenses remained; moved last, cleared. Audit confirmed all 54 gems mapping this template already carry a live .rubocop_todo.yml, so this reference is safe to emit unconditionally. See metanorma/ci:a864ed9.

  2. .github/workflows/release.yml — added explicit release_command: | bundle install\n bundle exec rake release, compensating for metanorma/ci#314's bundler_cache deprecation (hardcoded to false in the release job after the metanorma-cli v1.16.6 Bundler::GemNotFound: metanorma-nist incident). Without this override, the reusable's default release_command: bundle exec rake release fires against uninstalled gems and fails. Surfaced by @kwkwan on metanorma-plugin-lutaml#285; deeper fix (bundle install inside rubygems-release.yml's release job itself) tracked at metanorma/ci#349. See metanorma/ci:f994f4c.

What to expect on your gem

  • If your gem's .rubocop_todo.yml covers current offenses: CI stays green.
  • If your gem's .rubocop_todo.yml has plugin-drift (missed cops from ci#334's rubocop-rspec / rubocop-performance / rubocop-rake addition, or subsequent minor releases of those plugins): CI will red on rubocop-style failures. Fix per-gem: bundle exec rubocop --regenerate-todo — same recipe as sts-ruby / suma on 2026-07-05.
  • release.yml is template-only: no runtime effect until the gem's next release, at which point the two-line release_command kicks in and fixes the missing-bundle-install failure that was silent-broken before.

Wave discipline

  • --flatten-stale full auto-close of prior stale cimas-sync-* wave branches on this repo.
  • Fortnightly cadence maintained; this is a scheduled sync, not an emergency.

🤖

andrew2net and others added 22 commits April 24, 2026 09:25
* Add pubid-iho gem for IHO publication identifiers

New gem parses and renders IHO (International Hydrographic Organization)
identifiers across the S, P, M, B, and C series, including letter-suffixed
numbers (S-5A), colon/slash/hyphen sub-publications (S-158:100, P-1/21,
P-6-3), appendices (S-65 Ap. B), and the Part syntax requested in
relaton/relaton-iho#23 (S-100 Part 1). The IHO prefix is optional on input
and always emitted on output. Wired into the meta-gem, release order, and
integration dependency chain spec.

* Add pubid-iho gem dependency to Gemfile
Lets pubid-iho parse and render forms like "IHO S-100 Annex A 5.2.0"
and "IHO S-98 Annex C 1.0.0". Annex is a single uppercase letter,
following the IHO usage observed in metanorma/iho-s-100 (Annex A
"Terms and Definitions") and metanorma-iho#344 (S-98 Annex A/B/C).

Mirrors the existing `appendix` plumbing: parser rule, identifier
attribute, renderer method, plus round-trip spec cases (with and
without version, with the IHO prefix optional on input).

Refs relaton/relaton-iho#23.
Three additions to the IHO grammar, all driven by metanorma-iho#344:

* `Appendix` is accepted as an input alias for `Ap.` (canonical form
  remains `Ap.` for backwards compatibility with the existing
  `IHO S-65 Ap. A` / `IHO S-53 Ap. 1` data and specs).
* Appendix value extended from `digits | letter` to also accept
  `letter-digits` (e.g. `Appendix D-2` for S-122/S-123).
* New `Suppl <digits>` rule and `:supplement` attribute, used by
  S-66 Supplements 1–4.

Adds 6 round-trip cases plus the Appendix-canonicalisation pair in
identifier_spec; existing 28 cases still pass (34 examples total).

Refs relaton/relaton-iho#23.
* Add i18n for ITU annex-to-Special-Publication identifiers

Implements localized rendering of "Annex to ITU-T OB ..." identifiers
in fr/es/ar/ru/zh, with both short (structural) and long (title-style)
forms via `to_s(language:, format: :long)`. Caller pattern matches
metanorma-itu PR #497 (front_id.rb#itu_id_lang).

Refs metanorma/pubid-itu#43.

* Differentiate i18n_lang (rendering text) from language (document)

Adds an `i18n_lang:` kwarg to `Pubid::Itu::Identifier#to_s` that controls
identifier text translation, separate from the identifier's `language`
attribute (which remains the document's own language and produces the
trailing "-F"/"-S" suffix). The two can now be set independently:

  Pubid::Itu::Identifier.create(language: :fr, ...)
    .to_s(i18n_lang: :en)
  # => "Annex to ITU-T OB No. 1000-F"

`language:` on `to_s` is retained as a deprecated alias for `i18n_lang:`
to preserve backward compatibility for callers that already conflated
the two (notably metanorma-itu PR #497).

Refs #38 (comment).

* Address PR #38 review: unify fr/es short=long, add German i18n

* French and Spanish short forms now use the same `annex_long` template
  as their long forms (`Annexe au BE de l'UIT 1000-F`,
  `Anexo al BE de la UIT N.º 1000-S`). ITU itself uses the localized
  abbreviation `BE` rather than mixing the English `OB` into French or
  Spanish text.

* German `i18n_lang: :de` now works: `annex_to.de` adds the
  "Anhang zum" prefix, producing `Anhang zum ITU-T OB No. 1000`.
  German is not an official ITU language so there is no `annex_long`
  template and no language-suffix code.

* `render_language` no longer emits a stub trailing dash when the
  language has no LANGUAGES code mapping (fixes the
  `Anhang zum  ITU OB 1000-` artifact reported in the review).

The `ITU OB` vs `ITU-T OB` issue (ITU drops the sector for the
Operational Bulletin) is intentionally deferred per @opoudjis.

Refs #38 (comment).

* Reject sector for OB (cross-bureau publication)

OB (Operational Bulletin) is a cross-bureau ITU publication and must
never be associated with a single bureau (T, R, D). The API now
enforces this.

* Identifier::Base#initialize raises ArgumentError when series == "OB"
  and sector is set. Direct callers must omit the sector.
* Identifier::Base.transform (parser path) silently strips the sector
  when series is OB, so legacy strings like "ITU-T OB.1096" still
  round-trip — they normalize to "ITU OB No. 1096".
* Renderer omits the "-sector" separator when sector is empty, so
  rendered OB identifiers are "ITU OB No. 1000" rather than
  "ITU-T OB No. 1000".

Test fixture updates: existing OB parse fixtures updated to expect the
normalized form; OB create fixtures drop sector: "T". New tests for
the validation and the parse-time normalization.

Heads-up for downstream callers: metanorma-itu PR #497 front_id.rb
sets bureau ||= "T" unconditionally, which will now raise for OB.
The caller needs to skip the bureau when series == "OB".

Refs #38 (comment).
The auto-generated cassette path
spec/vcr_cassettes/Pubid_Nist_Identifier_Base/generate_NIST_PubID_string_outputs/identifier_from_NIST_Tech_Pubs/parse_identifiers_successfully.yml
exceeded Windows MAX_PATH (260 chars) when checked out under Bundler's
deep vendor directory, breaking metanorma-plateau Windows GHA builds.
Override the cassette name explicitly so the path is no longer derived
from nested RSpec context descriptions.

Fixes #49
Two `part_matcher` greediness bugs in `pubid-iso` surfaced by the ISO
Open Data deliverables dump:

- An alphabetic part code starting with a Roman-numeral letter (C, D,
  I, L, M, V, X) was misparsed because `roman_numerals >> digits.absent?`
  consumed the leading letter (e.g. `C` of `CS`) and left the rest of
  the part unconsumed. Add a negative lookahead so the Roman branch
  only succeeds when not followed by another uppercase letter.

- `ISO N/Add M` (no part, no year) failed because `part_matcher`
  greedily matched `/A` as a single-letter part, leaving `dd M`
  unparseable. The `Amd`/`Cor` variants worked because those tokens are
  blocked by `supplements.absent?`; `Add` is handled by the separate
  `addendum` rule and had no equivalent guard. Add `(str("Add") |
  str("ADD")).absent?` to mirror that protection.

Together these fix 50 distinct identifiers in the ISO Open Data
deliverables dataset (plus all `Withdrawn N/Add M` rows that
relaton-iso rewrites to `ISO N/Add M` upstream).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two parser/normalization gaps surfaced by the ISO Open Data deliverables
dump (~80k references):

- `<stage> <type> <num>` and `<typed_stage> <type> <num>` shapes were
  rejected because `std_document_body` only allowed a single prefix slot
  before the number. `ISO/IEC WD TR 14143-5` worked (WD in slot 1, TR in
  slot 2) but `ISO/IEC DIS TR 14143-5`, `ISO/IEC WD ISP 11182-3`,
  `ISO/IEC NP ISP 29110-4-2` did not. Refactor slot 2 to allow an
  optional stage/typed_stage followed by an optional type, which lets
  the natural `<stage|typed_stage> <type>` order parse.

- `DIS <type>` and `FDIS <type>` are long-form spellings of canonical
  draft typed-stages (`DTR`, `FDTR`, `DPAS`, `FDPAS`, `DTS`, `FDTS`,
  `DISP`, `FDISP`). Add two regex normalizations to `update_codes.yaml`
  that rewrite the long form to the canonical short form before parsing.

- Lowercase letter / `xx` part suffixes from early-stage `ISO/NP 10303`
  references (`-5a`, `-5b`, `-1xx`, `-2xx`) are non-canonical spellings.
  Add four explicit `update_codes.yaml` entries to normalize them to
  the uppercase canonical form, which already parses.

Together these eliminate 55 distinct identifier failures from the ISO
Open Data deliverables sweep (64 → 9 after the previous PR's 257 → 64;
the remaining 9 are stage-prefixed supplements, addressed separately).
…rse (#58)

Closes the last 9 parser failures from the ISO Open Data deliverables
sweep, and corrects a latent `part_matcher` greediness bug that was
producing incorrect AST for stage-prefixed supplements on identifiers
with no part or year.

## part_matcher misparse (root cause)

Inputs like `ISO 9000/WD Amd 1` were "parsing" but with the stage `WD`
greedily eaten as a part code, yielding the wrong AST and the wrong
round-trip output `ISO 9000-WD/Amd 1`. The same shape with `Add`
instead of `Amd` failed outright (since `Add` is not in `SUPPLEMENTS`
the rest of the rule chain couldn't recover). Both manifest the same
underlying issue: `part_matcher` did not reject stage tokens followed
by a supplement keyword.

Two new absence guards in `part_matcher`:

- `staged_supplement.absent?` — blocks `DAM`, `DCOR`, `DSuppl` etc.
- `((stage | typed_stage) >> space >> (supplements | "Add" | "ADD")).absent?` —
  blocks `WD Amd`, `PWI Add`, `DIS Cor`, `CD Suppl`, etc.

Existing specs only used these forms with a part or year (which routes
around `part.maybe`), so the misparse was dormant — no test regressions.

## addendum rule extension

`Add` was the only supplement keyword without a stage-prefix path in
its grammar rule. Add an optional `stage.as(:typed_stage) >> space`
prefix mirroring the supplement rule, enabling `WD Add`, `PWI Add`.

## update_codes.yaml normalizations

Six regex entries rewrite long-form `DIS|FDIS <supplement-type>` to
the canonical short staged-supplement token (which `staged_supplement`
already accepts):

- `/DIS Amd ` → `/DAM `, `/FDIS Amd ` → `/FDAM `
- `/DIS Cor ` → `/DCOR `, `/FDIS Cor ` → `/FDCOR `
- `/DIS Suppl ` → `/DSuppl `
- `/DIS Add ` → `/DAD `

Plus a `/DAdd ` → `/DAD ` entry for the legacy long-form Draft Addendum
spelling.

## Result

ISO Open Data sweep: 9 → 0 failures (cumulative across PRs #56, #57:
257 → 0).
…ge) (#70)

Annotated rendering (#27) wrapped every prerendered param value in a
<span> inside Pubid::Core::Renderer::Base#prerender_params. The ISO
renderer, however, renders :stage in two phases: render_stage returns
the Stage object unchanged during prerender, and postrender_stage
renders it later in the overridden #render_identifier.

With annotated: true, phase one stringified the Stage into a <span>
String, so phase two's stage.empty_abbr? raised
  NoMethodError: undefined method 'empty_abbr?' for an instance of String

This broke to_s(annotated: true) for every staged ISO identifier
(ISO/DIS, ISO/CD, ISO/FDIS, ISO/NP, ISO/WD, ISO/AWI, ISO/DTR, ...).
Published (no-stage) and pure-type (TR/TS) identifiers were unaffected,
which is why the existing annotated_spec — covering only TR — missed it.

Guard prerender annotation to String-rendered values only; deferred
domain objects (Stage) are left intact for the subclass's phase-two
handling, which already annotates them via annotate_value(:stage, ...).

Adds stage regression coverage to pubid-iso annotated_spec.
Amendment#urn and Corrigendum#urn raised NoEditionError unless the base
document had an edition, even though the URN renderer produces a valid URN
without one (e.g. ISO 10231:2003/Amd 1:2015 ->
urn:iso:std:iso:10231:amd:2015:v1, which the spec already declared as the
expected URN). The parent Supplement#urn renders unconditionally; only these
two subclasses added the guard.

The raise broke serialization of working-draft amendments whose base has no
edition (e.g. ISO/IEC 23008-1/WD Amd 1) in downstream consumers that render
the URN during round-trip serialization (relaton-bib deep_clone).

Drop the guard so both render like their parent. Update the amendment spec
that asserted the raise to assert the round-trip URN, and add forward URN
coverage to the editionless corrigendum context.
…release_command bundle install)

request-checks: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants