colregs 0.3.2 → 0.3.4

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.
@@ -72,15 +72,16 @@ Neither consumer lives in this repo.
72
72
  - **REQ-SCOPE-3** — A jurisdiction MUST be expressible as a *delta*: entries
73
73
  absent from a jurisdiction's data inherit from `intl`. A jurisdiction MUST
74
74
  NOT require restating the whole body of rules. Inheritance is "unless
75
- suppressed", not unconditional — see Q-11: verified Inland structure
76
- (Rule 28 "[Reserved]") means silence-means-inherit would apply
77
- international law where the national body deliberately has none, so no
78
- non-`intl` jurisdiction lands before an explicit suppression mechanism
79
- exists. That bar is about suppression, not licensing: it is independent of
80
- REQ-PROV-2 and ADR 0010, and it binds whether a jurisdiction's text ships or
81
- is withheld. A delta that only *adds* entries is exempt: it suppresses nothing,
82
- so the Q-11 hazard cannot arise from it (ADR 0008, `rule:30a:mooring_buoy`/`rule:30b:mooring_buoy`).
83
- A delta that suppresses or replaces an `intl` entry still waits.
75
+ suppressed", not unconditional: verified Inland structure (Rule 28
76
+ "[Reserved]") means silence-means-inherit would apply international law
77
+ where the national body deliberately has none, so a jurisdiction MUST
78
+ tombstone each inherited entry it deliberately lacks in `suppressions[]`
79
+ (ADR 0018; Q-11). The delta is an RFC 7396 merge patch over `intl` by
80
+ entry id — own entries present, tombstones `null`, silence inherits — and
81
+ a test proves the stored tables equal to that patch applied. This binds
82
+ whether a jurisdiction's text ships or is withheld (REQ-PROV-2, ADR 0010
83
+ are separate). A delta that only *adds* entries has no tombstones (ADR
84
+ 0008); one that replaces an `intl` entry tombstones it and adds its own.
84
85
  - **REQ-SCOPE-4** — Adding a jurisdiction MUST be additive. It MUST NOT require
85
86
  a schema change or edits to existing `intl` entries.
86
87
  - **REQ-SCOPE-5** — Geography that gates a rule (Great Lakes, Western Rivers,
@@ -164,14 +165,14 @@ Four layers, each independently addressable.
164
165
  - **REQ-MODEL-6** — Entries MUST compose. Multiple entries applying to one fact
165
166
  record is the normal case, not an error (Rule 28 is "in addition to" Rule 23).
166
167
  - **REQ-MODEL-7** — Five relations MUST be supported:
167
- - `rel:includes` — import another entry's **lights only**, never its predicate;
168
- - `rel:in_lieu_of` — legal alternatives for the same fact record;
168
+ - `rel:includes` — import another entry's lights, modality and scalar gates, never its axes (ADR 0019);
169
+ - `rel:in_lieu_of` — legal alternatives for the same fact record; overlapping targets are alternatives to each other (ADR 0019);
169
170
  - `rel:excludes` — mutual exclusion, including across rules; symmetric
170
171
  (A excludes B implies B excludes A), and no participating entry MAY be
171
172
  forceful (`modality:shall`/`modality:shall-if-practicable`) — a directed "this one
172
- prevails" is `rel:overrides` (ADR 0007), not `rel:excludes`;
173
- - `rel:exempts` — one entry lifting another's obligation;
174
- - `rel:conditional_includes` — import or alternatives, gated on a predicate.
173
+ prevails" is `rel:overrides` (ADR 0007), not `rel:excludes`; a constraint on one display, never a removal (ADR 0019);
174
+ - `rel:exempts` — one entry lifting another's obligation; reaches an entry in force, never an import (ADR 0019);
175
+ - `rel:conditional_includes` — import or alternatives, gated on a predicate; a `one_of` yields exactly one per display, or none under a `may` carrier (ADR 0019).
175
176
  The five are not interchangeable; README.md holds the working semantics.
176
177
  - **REQ-MODEL-13** — Where a condition goes is not a style choice. A
177
178
  condition on whether a paragraph *applies to this vessel at all* MUST be in
@@ -474,24 +475,21 @@ See ADR 0003.
474
475
  resolve to a known paragraph path, and on a corpus filename that disagrees
475
476
  with the file's internal metadata. Silence MUST NOT imply coverage (the
476
477
  language mirror of REQ-SCOPE-6).
477
- - **REQ-LANG-6** **(unimplemented — no display catalogs exist)** — Display
478
- strings for the identifier vocabularies (light names, fact-value labels,
479
- modality labels, image captions) MUST be addressable via stable string keys
480
- with per-language catalogs, separate from legal corpora. Catalog entries are
481
- static strings: no interpolation, plural or gender grammar — message
482
- composition belongs to the consumer's i18n system, and this package MUST NOT
483
- grow one. Each catalog MUST carry lightweight provenance: contributors,
484
- reviewers, review date, licence. Maintainer notes inside structural files
485
- are working documentation, not display strings, not part of the localization
486
- surface, and stay untranslated.
487
- - **REQ-LANG-7** **(unimplemented in part — no fallback policy is encoded, but
488
- the mixed-corpus statement is missing from the package documentation)** — The
489
- package MUST NOT encode a language fallback policy, and MUST NOT silently
490
- substitute one corpus for another. Text is only addressable inside a corpus,
491
- so every textual unit a consumer retrieves is attributable to its corpus;
492
- package documentation MUST state that a mixed-corpus rendering is never a
493
- single authoritative edition. Choice and fallback beyond that are the
494
- consumer's (the language mirror of REQ-CONS-3).
478
+ - **REQ-LANG-6** — Display strings for the closed vocabularies the package
479
+ emits (light, modality, role, encounter, jurisdiction, fact values, and
480
+ image captions **(unimplemented — no catalog yet)**) MUST be addressable
481
+ via stable string keys with per-language catalogs, separate from legal
482
+ corpora; not being an identifier does not exclude a vocabulary. Catalog
483
+ entries are static strings — no interpolation, plural or gender grammar —
484
+ and MUST carry provenance (contributors, reviewers, review date, licence);
485
+ definitions stay untranslated in structural files, not catalogued.
486
+ - **REQ-LANG-7** — The package MUST NOT encode a language fallback policy,
487
+ and MUST NOT silently substitute one corpus for another. Text is only
488
+ addressable inside a corpus, so every textual unit a consumer retrieves
489
+ is attributable to its corpus; package documentation MUST state that a
490
+ mixed-corpus rendering is never a single authoritative edition. Choice
491
+ and fallback beyond that are the consumer's (the language mirror of
492
+ REQ-CONS-3).
495
493
  - **REQ-LANG-8** — A `community`-tier corpus MUST record who produced and
496
494
  who reviewed it. Machine translation without named human review MUST NOT
497
495
  be accepted.
@@ -507,6 +505,9 @@ See ADR 0003.
507
505
  reflects. The two MAY differ — a corpus transcribed from an older
508
506
  consolidation is legitimate — but the difference MUST be machine-visible,
509
507
  never silent.
508
+ - **REQ-LANG-11** — Exactly one language MUST be declared reference-complete;
509
+ CI MUST fail if its catalog omits an emitted value. Other catalogs MAY be
510
+ partial, gated only by the existing key-resolution check.
510
511
 
511
512
  ---
512
513
 
@@ -638,8 +639,7 @@ specification until 1.0.
638
639
 
639
640
  ### Open gates
640
641
 
641
- Each gate names the declined design, the closing event, and the trigger.
642
-
642
+ Each names the declined design, closing event, and trigger.
643
643
  - **GATE-1 — `paragraph_id` split from `citation_path`**
644
644
  (ADR 0003, declined; REQ-LANG-3, REQ-PKG-4).
645
645
  *Closing event*: the 1.0 tag. Before it, splitting the two is a
@@ -763,8 +763,9 @@ Each gate names the declined design, the closing event, and the trigger.
763
763
  unlikely short of major success. Recorded regardless: "unlikely" is not
764
764
  a closing event (REQ-GATE-1).
765
765
 
766
- Gates whose closing event is "none" are recorded because a future reader
767
- will otherwise re-ask whether they were merely deferred. They were not.
766
+ Four gates from ADR 0013 (option B), *closing event* for all four the first national text whose edition cannot be determined: **GATE-7 — validity intervals on editions (`in_force_until`)**, trigger an edition superseded with no successor to bound it; **GATE-8 — a per-source publication record separate from the edition (FRBR manifestation)**, trigger two published sources for one edition that disagree; **GATE-9 — a content digest per corpus for verification**, trigger a corpus differing from its cited source with no edition or source change; **GATE-10 — a skeleton per edition**, trigger the first amendment that deletes or renumbers a path.
767
+
768
+ Gates whose closing event is "none" are recorded because a future reader will otherwise re-ask whether they were merely deferred. They were not.
768
769
 
769
770
  ### Gate status is data
770
771
 
@@ -930,14 +931,13 @@ Tracked here until resolved; each becomes an ADR.
930
931
  Rule 28 "[Reserved]" and has no counterpart for `23(d)(ii)`/`(iii)`
931
932
  ([verification](verification/2026-08-30-q6-q8.md), Claim 1), so a
932
933
  `us/inland` delta that is merely silent there would inherit the `intl`
933
- entries and assert international obligations on inland waters. A delta
934
- needs explicit suppression records (tombstones) alongside overrides —
935
- "this path/entry deliberately does not exist here", distinguishable from
936
- "not yet transcribed". Decide the mechanism in the second-jurisdiction
937
- bundle (GATE-1 re-take, GATE-2, Q-10); until then no non-`intl`
938
- jurisdiction lands. This is the live blocker on CEVNI, and it is *not* the
939
- licence one: ADR 0010 removed the licence block by permitting structure with
940
- the text withheld, and left this one standing.
934
+ entries and assert international obligations on inland waters.
935
+ **Resolved 2026-09-16, ADR 0018.** Tombstones: `suppressions[]` in
936
+ `data/applicability.json`, one record per (jurisdiction, `intl` entry),
937
+ and the whole delta is an RFC 7396 merge patch by entry id, proven by
938
+ test; the two verified Inland absences are the first tombstones. The hold
939
+ on non-`intl` jurisdictions is lifted (CEVNI: structure unblocked on both
940
+ counts, this and ADR 0010). GATE-1, Q-10, Q-8 stay open on their triggers.
941
941
  Also from the same verification pass, tracked on the global board rather
942
942
  than here: four transcription defects in `data/rules.json` itself
943
943
  (`21(a)`, `21(b)`, `23(b)`, `29(b)`) — a data fix, not a design
@@ -1153,7 +1153,7 @@ named that gap before the class existed.
1153
1153
  first-cut table, but it is the one paragraph of Rule 13 that assigns a role
1154
1154
  and a `category:classification` entry has nowhere to put one. Entry `rule:13a` is
1155
1155
  `category:precedence` here, with 13(b)'s sector test left for the `category:classification`
1156
- entry that would set the `hist:was_overtaking` latch. Settled with the rest
1156
+ entry that would set the `hist:was_overtaking` overtaking-history fact. Settled with the rest
1157
1157
  of `Q-14`, paragraph by paragraph.
1158
1158
 
1159
1159
  **Closed in pencil 2026-09-04 (PR #26).** 13(b) is now written, as *two*
@@ -1161,7 +1161,7 @@ named that gap before the class existed.
1161
1161
  encounter type belongs to the pair and reading aspect alone classified the
1162
1162
  overtaken vessel's side of the same encounter as a crossing. 13(a) stays `category:precedence`, and its predicate now carries both
1163
1163
  halves of "any vessel overtaking any other": the 13(b) sector and the 13(d)
1164
- latch, as an `any_of`, which closes the gap the entry recorded. 13(d) is a
1164
+ overtaking history, as an `any_of`, which closes the gap the entry recorded. 13(d) is a
1165
1165
  third `category:classification` entry, reading history and no geometry at all. The
1166
1166
  split confirms the thing ADR 0005 §1 was really defending — one category per
1167
1167
  paragraph. **Reversed in part, 2026-09-16:** ADR 0015 as rewritten makes
@@ -1242,7 +1242,7 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
1242
1242
  again: on those unordered pairs Rule 15 lays a give-way duty on a vessel
1243
1243
  that may be unable to discharge it, which is Rule 2's region and is
1244
1244
  recorded, not gated. `rule:13a` needs no override against `rule:15a:keep_out_of_the_way` — the
1245
- entry excludes every overtaking by the `hist:was_overtaking` latch and by
1245
+ entry excludes every overtaking by the `hist:was_overtaking` overtaking history and by
1246
1246
  13(b)'s sector — and a test pins that absence so it stays a reason rather
1247
1247
  than an oversight. The hand-list of six is asserted complete by a derived
1248
1248
  check: every Rule 18 entry that assigns a helm role and does not gate a
@@ -1284,7 +1284,7 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
1284
1284
  - **Q-43** — **The partition needs history to be present, and says nothing when
1285
1285
  it is absent.** `rule:14b` and `rule:15a:crossing` are gated on `hist:was_overtaking`
1286
1286
  being `false` on both subjects, which is 13(d)'s requirement and the only way
1287
- the three encounter types stay disjoint once the latch is set. Because absent
1287
+ the three encounter types stay disjoint once the overtaking history is set. Because absent
1288
1288
  is absent, a situation that omits the fact is classified as *no encounter at
1289
1289
  all* rather than as a head-on or a crossing. That is conservative in the
1290
1290
  right direction and completely silent, which is the wrong way to be
@@ -1344,7 +1344,7 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
1344
1344
  in seamanship and is one here. Settled by deciding whether the language gains
1345
1345
  fact-to-fact comparison — a bigger change than `not` and `any_of` were, and
1346
1346
  not one to make for a single paragraph.
1347
- - **Q-47** — **13(d)'s latch never clears.** The paragraph runs "until she is
1347
+ - **Q-47** — **13(d)'s overtaking history never clears.** The paragraph runs "until she is
1348
1348
  finally past and clear", nothing in the fact vocabulary carries that, and so
1349
1349
  in this model `hist:was_overtaking` is set by a consumer and cleared by a
1350
1350
  consumer while entry `rule:13d` classifies an overtaking for as long as it stands.
@@ -1354,14 +1354,14 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
1354
1354
  `category:conduct` monitors, which are the things that watch a duty end rather than
1355
1355
  begin.
1356
1356
 
1357
- **Ruled 2026-09-08:** while one vessel holds the latch, 13(a)'s sector
1358
- test is suppressed on the other, newly-gaining vessel — once overtaking,
1359
- always overtaking, per 13(d)'s own text, so a geometry test that would
1360
- newly name a second give-way vessel must not fire while the first still
1361
- holds the role by history alone. `rule:13a`'s sector branch now also reads
1362
- `other:hist:was_overtaking: false`; the closing fixture in
1357
+ **Ruled 2026-09-08:** while one vessel's overtaking history stands, 13(a)'s
1358
+ sector test is suppressed on the other, newly-gaining vessel — once
1359
+ overtaking, always overtaking, per 13(d)'s own text, so a geometry test
1360
+ that would newly name a second give-way vessel must not fire while the
1361
+ first still holds the role by history alone. `rule:13a`'s sector branch now
1362
+ also reads `other:hist:was_overtaking: false`; the closing fixture in
1363
1363
  `fixtures/situation-fixtures.json` is the case this closes — a vessel
1364
- drops back onto the latch-holder's own stern, and only the latch-holder
1364
+ drops back onto the history-holder's own stern, and only the history-holder
1365
1365
  gives way.
1366
1366
  - **Q-48** — **Nothing checks that a situation is geometrically possible.**
1367
1367
  `self:geo:rel_bearing_deg`, `other:geo:rel_bearing_deg`, the two
@@ -1392,7 +1392,7 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
1392
1392
  bearing-rate values were placeholders. They were re-derived from the headings
1393
1393
  and speeds. Two cases could not keep their story: the R1 head-on had both
1394
1394
  vessels with the other to starboard, which no speeds make a steady bearing,
1395
- and is recast as one; the 13(d) latch case is 400 m abeam with the CPA
1395
+ and is recast as one; the 13(d) overtaking-history case is 400 m abeam with the CPA
1396
1396
  already past, and lost `rule:7d_i`. The partition sweep now constructs positions
1397
1397
  and headings for every bearing pair and is consistent at every point. The
1398
1398
  "never both give-way" property is asserted over a sweep of steady-bearing
@@ -1416,6 +1416,7 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
1416
1416
  - Default: narrow (nothing changes).
1417
1417
  - Recommendation: narrow — the test named below shows the fact resolves out of sight
1418
1418
  (`Q-49: hist:was_overtaking resolves out of sight; rule:13d does not fire`, `test/data.test.mjs`).
1419
+ - **Q-58** — **Do the Rule 18/12 role entries need `other:hist:was_overtaking: false`, like `rule:13a` and `rule:15a:keep_out_of_the_way` (Q-47)?** The nine `18a*`/`18b*`/`18c*` and three `12a*` entries carry no such gate, and `rule:13a` overrides them only when *self* holds the history; when the *other* holds it, self is named give-way under Rule 18 or 12 while 13(a) puts the duty on the other alone. Default **ungated** (nothing changes), recorded as a `gap` on the twelve. Recommend **gated**, in Q-47's shape: 13(a)'s 'notwithstanding' and Rule 18's own exception for Rule 13 both displace these entries, and Q-47 already ruled the history decides who holds the role. colregs-engine#75 (closed) read the table as self-centric and recommended a consumer swap subjects for the other's duty; that answers the other's role, not self's wrong one. `colregs#117`.
1419
1420
 
1420
1421
  ### From the Part B invariants (P4.1)
1421
1422
 
@@ -1426,22 +1427,20 @@ nothing is blocked while open.
1426
1427
  - **Q-50** — **Does risk of collision gate Rules 13 and 18?** 14(a) and 15(a)
1427
1428
  say "so as to involve risk of collision"; Rule 13 and Rule 18 do not.
1428
1429
  *Wide:* they attach whenever in sight and the geometry holds — Rule 11 is the
1429
- only gate Section II states — so a vessel overtaking a mile clear abeam is
1430
- give-way and Rule 16 binds her. *Narrow:* Section II's duties attach only with
1430
+ only gate Section II states. *Narrow:* Section II's duties attach only with
1431
1431
  risk of collision; 14 and 15 are emphasis. Default **wide**: `rule:13a`, `rule:13b`,
1432
- `rule:13d` and every Rule 18 entry read `in_sight` only. No recommendation.
1433
- - **Q-51** — **What arms 13(d)'s latch?** *A:* 13(b)'s deeming, at the first
1432
+ `rule:13d` and every Rule 18 entry read `in_sight` only. **Ruled
1433
+ 2026-09-17:** wide — the window is the encounter itself. A pair the engine is
1434
+ evaluating is an encounter, at whatever range suits the vessels; inside it 13
1435
+ and 18 attach on sight and geometry alone, and how close is clear stays a
1436
+ monitor parameter. *The Nowy Sacz* [1977] 2 Lloyd's Rep 91 (CA) holds risk
1437
+ of collision unnecessary for the overtaking rule; *Meghna Princess* [2017]
1438
+ SGHC 220 [56] restates it; nothing found on Rule 18 either way (colregs#72).
1439
+ - **Q-51** — **What arms 13(d)'s overtaking history?** *A:* 13(b)'s deeming, at the first
1434
1440
  state the geometry holds. *B:* 13(a)'s duty actually attaching. They differ
1435
- where the geometry holds but a condition on 13(a) does not — `Q-50`'s
1436
- surface. Pencilled **B** ✎ (Solace, 2026-09-14): the latch arms when the
1437
- duty attaches. KIVELI [2025] EWHC 1185 (Admlty) holds the analogous Rule 14
1438
- latch arms on risk of collision and then persists; no authority was found on
1439
- overtaking geometry with no risk of collision. Free to reverse: four entries
1440
- (`rule:13a`, `rule:13b`, `rule:13d`) and their fixtures, and nothing consumes the one
1441
- state this changes the answer for. Settled by: a decision on Rule 13 itself,
1442
- or Cockcroft & Lameijer on whether overtaking status needs risk of
1443
- collision.
1444
- - **Q-52** — **What does 13(d)'s latch forbid?** *Narrow:* reclassification to
1441
+ only on `Q-50`'s surface. **Ruled 2026-09-17:** B, closed by `Q-50` wide —
1442
+ with no gate between geometry and duty the two readings coincide (colregs#72).
1443
+ - **Q-52** — **What does 13(d)'s overtaking history forbid?** *Narrow:* reclassification to
1445
1444
  *crossing* only, as the paragraph says, leaving head-on to Rule 14 on the
1446
1445
  geometry of the moment. *Broad:* the encounter stays an overtaking and no
1447
1446
  other Section II classification attaches. Both preserve the duty; they differ
@@ -1456,23 +1455,25 @@ nothing is blocked while open.
1456
1455
  *Suspension:* "may, however" lifts the duty once non-compliance is apparent;
1457
1456
  a monitor then flags nothing. *Exception:* the duty stands and a departure is
1458
1457
  lawful only as action to avoid collision by her manoeuvre alone; a monitor
1459
- flags any other alteration. No default (Rule 17 has no entry). Recommend
1460
- **exception**: 17(a)(ii) describes an action, not a proviso, and leaves a
1461
- monitor something to check — a drafting argument, not a source.
1462
- - **Q-54** — **Are Rule 17's phases monotone?** *Latching:* one three-valued
1463
- monotone latch per stand-on vessel per encounter; late compliance does not
1464
- take the permission away. *Re-evaluating:* each phase is a predicate on the
1465
- current state and the vessel may fall back — and must she then hold her *new*
1466
- course and speed? TLC distinguishes them on a four-state trace, so settle
1467
- before P4.2. No default. Recommend **latching**: one late alteration does not
1468
- restore reliance — an argument from purpose, not a source.
1458
+ flags any other alteration. No default (Rule 17 has no entry). **Ruled
1459
+ 2026-09-16:** exception — the 17(a)(i) duty stands and a departure is lawful
1460
+ only as action to avoid collision; a monitor flags any other alteration.
1461
+ Purpose, not source (colregs#72); read with `Q-54`.
1462
+ - **Q-54** — **Are Rule 17's phases monotone?** *Monotone:* one three-valued
1463
+ marker per stand-on vessel per encounter; late compliance does not take the
1464
+ permission away. *Re-evaluating:* each phase is a predicate on the current
1465
+ state and the vessel may fall back — must she then hold her *new* course
1466
+ and speed? TLC distinguishes them on a four-state trace. **Ruled
1467
+ 2026-09-16:** monotone — permission or duty, once arisen, survives belated
1468
+ compliance; a fall-back needs a baseline the Rules lack (`Q-57`). No holding on point (colregs#72); *Ever Smart* [2021] UKSC 6 para 61 has the obligation cease at 17(a)(ii)/17(b) with no revival — consistent with monotone, not a ruling on it.
1469
1469
  - **Q-55** — **What does a visibility transition do to Section II state?**
1470
1470
  Rule 11 and 19(a) switch on the current state; no paragraph says what
1471
- becomes of a 13(d) latch or a Rule 17 phase. *Persisting:* they belong to the
1471
+ becomes of a 13(d) overtaking history or a Rule 17 phase. *Persisting:* they belong to the
1472
1472
  encounter and survive the fog. *Resetting:* Section II starts afresh on the
1473
1473
  geometry when sight is regained — the reclassification 13(d) forbids, via
1474
- visibility. No default (nothing is temporal). Recommend **persisting**, which
1475
- `INV-13d`'s "same encounter" is written for; likeliest surprising TLC trace.
1474
+ visibility. No default (nothing is temporal). **Deferred indefinitely** (Solace,
1475
+ 2026-09-16): no authority either way (colregs#72); scenario contrived. Lean, not
1476
+ ruling: **resetting** — a new encounter after the fog; persisting withdrawn.
1476
1477
  - **Q-56** — **The third visibility state: a hole, or closed by the model?**
1477
1478
  Not in sight *and* not in or near restricted visibility (clear weather, beyond
1478
1479
  visual range, radar contact) is outside Rule 11 and 19(a): Section I only.
@@ -1494,7 +1495,5 @@ module, expensive after.
1494
1495
 
1495
1496
  A third, ruled rather than pencil: **`docs/part-b-invariants.md` stays
1496
1497
  hand-written Markdown; no derived JSON registry.** Ruled 2026-09-08. Revisit
1497
- only if P4.2's TLA+ needs to cite `INV-` ids mechanically — the id scheme
1498
- just above is already the cheap-to-change half of that trigger, so the
1499
- revisit costs one file's worth of tooling, not a re-derivation of the ids
1500
- themselves.
1498
+ only if P4.2's TLA+ needs to cite `INV-` ids mechanically; the cost then is one
1499
+ file's worth of tooling, not a re-derivation of the ids.
@@ -923,6 +923,96 @@
923
923
  "fact:length_m": 11.6
924
924
  },
925
925
  "expect": []
926
+ },
927
+ {
928
+ "name": "constrained by draft, US Inland: Rule 28 is [Reserved], the record is out of vocabulary and selects nothing",
929
+ "jurisdiction": "us/inland",
930
+ "facts": {
931
+ "fact:propulsion": "propulsion:power",
932
+ "fact:activity": "activity:cbd",
933
+ "fact:position": "position:underway",
934
+ "fact:length_m": 200
935
+ },
936
+ "expect": []
937
+ },
938
+ {
939
+ "name": "6 m outboard, max speed 6 kn, US Inland: 23(d)(ii) has no Inland counterpart, 23(d)(i) is replaced by 23(d), 23(a) is inherited",
940
+ "jurisdiction": "us/inland",
941
+ "facts": {
942
+ "fact:propulsion": "propulsion:power",
943
+ "fact:activity": "activity:none",
944
+ "fact:position": "position:underway",
945
+ "fact:length_m": 6,
946
+ "fact:max_speed_kn": 6
947
+ },
948
+ "expect": [
949
+ "rule:23a_i",
950
+ "rule:23a_ii",
951
+ "rule:23a_iii_iv",
952
+ "rule:23d"
953
+ ]
954
+ },
955
+ {
956
+ "name": "constrained by draft, at anchor, US Inland: Rule 30 is inherited untouched",
957
+ "jurisdiction": "us/inland",
958
+ "facts": {
959
+ "fact:propulsion": "propulsion:power",
960
+ "fact:activity": "activity:cbd",
961
+ "fact:position": "position:anchored",
962
+ "fact:length_m": 200
963
+ },
964
+ "expect": [
965
+ "rule:30a",
966
+ "rule:30c"
967
+ ]
968
+ },
969
+ {
970
+ "name": "same yacht motoring, US Inland: 23(d)(i) is replaced by Inland 23(d)",
971
+ "jurisdiction": "us/inland",
972
+ "facts": {
973
+ "fact:propulsion": "propulsion:power",
974
+ "fact:activity": "activity:none",
975
+ "fact:position": "position:underway",
976
+ "fact:length_m": 11.6
977
+ },
978
+ "expect": [
979
+ "rule:23a_i",
980
+ "rule:23a_ii",
981
+ "rule:23a_iii_iv",
982
+ "rule:23d"
983
+ ]
984
+ },
985
+ {
986
+ "name": "pushing ahead, not a composite unit, US Inland: 24(c)(iii) replaces the sternlight with two towing lights",
987
+ "jurisdiction": "us/inland",
988
+ "facts": {
989
+ "fact:propulsion": "propulsion:power",
990
+ "fact:activity": "activity:pushing",
991
+ "fact:position": "position:underway",
992
+ "fact:length_m": 30,
993
+ "fact:composite_unit": false
994
+ },
995
+ "expect": [
996
+ "rule:24c:towing_lights",
997
+ "rule:24c:underway_lights"
998
+ ]
999
+ },
1000
+ {
1001
+ "name": "pushing ahead, not a composite unit, at anchor, US Inland (issue #137 review: sidelights/towing lights are underway-gated, masthead only at anchor)",
1002
+ "jurisdiction": "us/inland",
1003
+ "facts": {
1004
+ "fact:propulsion": "propulsion:power",
1005
+ "fact:activity": "activity:pushing",
1006
+ "fact:position": "position:anchored",
1007
+ "fact:length_m": 30,
1008
+ "fact:composite_unit": false
1009
+ },
1010
+ "expect": [
1011
+ "rule:24c:towing_lights",
1012
+ "rule:30a",
1013
+ "rule:30b",
1014
+ "rule:30c"
1015
+ ]
926
1016
  }
927
1017
  ]
928
1018
  }
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "colregs",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "COLREGS 72 and its national amalgamations as language-neutral JSON — rule text, light definitions, and applicability predicates — with cross-implementation fixtures",
5
5
  "license": "Apache-2.0",
6
+ "colregs": {
7
+ "referenceLanguage": "en"
8
+ },
6
9
  "repository": {
7
10
  "type": "git",
8
11
  "url": "git+https://github.com/mark-brannan/colregs.git"
@@ -306,6 +306,21 @@
306
306
  "classification_shape": { "type": "string" }
307
307
  }
308
308
  },
309
+ "suppressions": {
310
+ "$comment": "Tombstones (REQ-SCOPE-3, ADR 0018): a jurisdiction deliberately lacks an inherited intl entry, as distinct from not-yet-transcribed. With the jurisdiction's own entries these form its RFC 7396 merge patch over intl; suppresses is the key that patch sets to null.",
311
+ "type": "array",
312
+ "items": {
313
+ "type": "object",
314
+ "additionalProperties": false,
315
+ "required": ["jurisdiction", "suppresses", "cite", "why"],
316
+ "properties": {
317
+ "jurisdiction": { "type": "string", "pattern": "^[a-z]+(/[a-z]+)+$" },
318
+ "suppresses": { "$ref": "#/$defs/ruleId" },
319
+ "cite": { "type": "string", "minLength": 1 },
320
+ "why": { "type": "string", "minLength": 1 }
321
+ }
322
+ }
323
+ },
309
324
  "known_omissions": {
310
325
  "type": "array",
311
326
  "items": {
@@ -9,7 +9,6 @@
9
9
  "colregs",
10
10
  "applied",
11
11
  "exempted",
12
- "excluded",
13
12
  "overridden",
14
13
  "displays",
15
14
  "optional_additions",
@@ -27,9 +26,6 @@
27
26
  "exempted": {
28
27
  "$ref": "evaluation.schema.json#/$defs/byEntryList"
29
28
  },
30
- "excluded": {
31
- "$ref": "evaluation.schema.json#/$defs/byEntryList"
32
- },
33
29
  "overridden": {
34
30
  "$ref": "evaluation.schema.json#/$defs/byEntryList"
35
31
  },
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/mark-brannan/colregs/schema/i18n-catalog.schema.json",
4
4
  "title": "data/i18n/*.json",
5
- "description": "Display catalog for one BCP 47 language: UI strings for identifier vocabularies (light names, fact-axis labels, modality labels, image captions), not legal text -- see docs/adr/0003-language-as-a-dimension.md 'Display catalogs'.",
5
+ "description": "Display catalog for one BCP 47 language: UI strings for the closed vocabularies the package emits (lights, modalities, roles, encounters, jurisdictions, closed-list fact values), not legal text -- see docs/adr/0003-language-as-a-dimension.md 'Display catalogs' and REQ-LANG-6.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": ["language", "provenance", "strings"],
@@ -25,18 +25,39 @@
25
25
  },
26
26
  "strings": {
27
27
  "type": "object",
28
- "description": "One section per vocabulary. Every section is keyed by the identifier itself, as documented in docs/identifiers.md -- light identifiers (light:) and closed-vocabulary identifiers (modality:, and any of role:/encounter:/category: that gain a catalog section) alike, as stored in data/applicability.json. Values are static strings: no interpolation, no plurals -- the consumer's i18n system owns composition.",
28
+ "description": "One section per vocabulary (REQ-LANG-6, REQ-LANG-9's ruling docs/adr/0017). Every section is keyed by the identifier itself, as documented in docs/identifiers.md -- light, modality, role and encounter identifiers carry a type prefix; jurisdiction and closed-list fact values are keyed by their own bare or fact-prefixed form. Values are static strings: no interpolation, no plurals -- the consumer's i18n system owns composition.",
29
29
  "additionalProperties": false,
30
30
  "properties": {
31
- "light": {
31
+ "lights": {
32
32
  "type": "object",
33
33
  "propertyNames": { "pattern": "^light:[a-z_]+$" },
34
34
  "additionalProperties": { "$ref": "#/$defs/label" }
35
35
  },
36
- "modality": {
36
+ "modalities": {
37
37
  "type": "object",
38
38
  "propertyNames": { "pattern": "^modality:[a-z]+(-[a-z]+)*$" },
39
39
  "additionalProperties": { "$ref": "#/$defs/label" }
40
+ },
41
+ "roles": {
42
+ "type": "object",
43
+ "propertyNames": { "pattern": "^role:[a-z]+(-[a-z]+)*$" },
44
+ "additionalProperties": { "$ref": "#/$defs/label" }
45
+ },
46
+ "encounters": {
47
+ "type": "object",
48
+ "propertyNames": { "pattern": "^encounter:[a-z]+(-[a-z]+)*$" },
49
+ "additionalProperties": { "$ref": "#/$defs/label" }
50
+ },
51
+ "jurisdictions": {
52
+ "type": "object",
53
+ "propertyNames": { "pattern": "^[a-z]+(/[a-z]+)*$" },
54
+ "additionalProperties": { "$ref": "#/$defs/label" }
55
+ },
56
+ "facts": {
57
+ "type": "object",
58
+ "description": "Closed-list fact-value labels only (Tier A): propulsion, activity, position, rule18_class, dynamics, obstruction_side, wind_side. Field names (fact:length_m, geo:tcpa_s, ...) have no closed list behind them and are never catalogued here -- form-label text and unit/number formatting are the consumer's.",
59
+ "propertyNames": { "pattern": "^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*$" },
60
+ "additionalProperties": { "$ref": "#/$defs/label" }
40
61
  }
41
62
  }
42
63
  }
@@ -53,10 +53,9 @@
53
53
  "^light:[a-z_]+$": {
54
54
  "type": "object",
55
55
  "additionalProperties": false,
56
- "required": ["name", "colregs_term", "cite", "character", "rule21"],
56
+ "required": ["term", "cite", "character", "rule21"],
57
57
  "properties": {
58
- "name": { "type": "string", "minLength": 1 },
59
- "colregs_term": { "type": "string", "minLength": 1 },
58
+ "term": { "type": "string", "minLength": 1 },
60
59
  "cite": { "type": "string", "minLength": 1 },
61
60
  "color": { "type": ["string", "null"] },
62
61
  "color_note": { "type": "string" },
@@ -12,6 +12,54 @@
12
12
  "note": {
13
13
  "type": "string"
14
14
  },
15
+ "paragraphs": {
16
+ "$ref": "#/$defs/paragraphs"
17
+ },
18
+ "deltas": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "description": "Per-jurisdiction skeleton deltas over `paragraphs` (ADR 0020): own paragraphs by path, suppressed paths, everything else inherited.",
22
+ "patternProperties": {
23
+ "^[a-z]+(/[a-z]+)+$": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": [
27
+ "paragraphs",
28
+ "suppressions"
29
+ ],
30
+ "properties": {
31
+ "note": {
32
+ "type": "string"
33
+ },
34
+ "paragraphs": {
35
+ "$ref": "#/$defs/paragraphs"
36
+ },
37
+ "suppressions": {
38
+ "type": "array",
39
+ "items": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": [
43
+ "path",
44
+ "why"
45
+ ],
46
+ "properties": {
47
+ "path": {
48
+ "$ref": "evaluation.schema.json#/$defs/paragraphCite"
49
+ },
50
+ "why": {
51
+ "type": "string",
52
+ "minLength": 1
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ },
62
+ "$defs": {
15
63
  "paragraphs": {
16
64
  "type": "object",
17
65
  "additionalProperties": false,
@@ -1,22 +0,0 @@
1
- {
2
- "id": "intl@2016.fi.finlex",
3
- "edition": "intl@2016",
4
- "edition_status": "claimed",
5
- "language": "fi",
6
- "source_id": "finlex",
7
- "tier": "national",
8
- "normalization": "NFC",
9
- "source": {
10
- "publisher": "Finlex (Oikeusministeriö), Suomen säädöskokoelman sopimussarja",
11
- "title": "Asetus kansainvälisistä säännöistä yhteentörmäämisen ehkäisemiseksi merellä vuonna 1972 tehdyn yleissopimuksen voimaansaattamisesta, SopS 30/1977",
12
- "url": "https://www.finlex.fi/fi/sopimukset/sopsteksti/1977/19770030",
13
- "retrieved": "2026-09-12"
14
- },
15
- "rights": {
16
- "source_text": "Tekijänoikeuslaki 404/1961 §9: no copyright in treaties and their official translations",
17
- "redistribution_basis": "Finlex FAQ: published materials carry no usage restriction; open data CC BY 4.0",
18
- "package_licence": "Apache-2.0"
19
- },
20
- "note": "Stub: identity and provenance only; the Finnish text is not transcribed yet. GATE-2 is adopted in docs/gates.json, settled by ADR 0013, so nothing structural blocks it.",
21
- "paragraphs": {}
22
- }