colregs 0.3.2 → 0.3.3

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.
package/README.md CHANGED
@@ -56,7 +56,10 @@ lights and shapes, Part D sound and light signals, and Annex I geometry.
56
56
  [`docs/requirements.md`](docs/requirements.md) is the numbered contract for
57
57
  all of it. One case the Convention cannot state, a vessel made fast to a
58
58
  mooring buoy, lives only under `us/inland`
59
- ([ADR 0008](docs/adr/0008-mooring-buoy-modifier.md)).
59
+ ([ADR 0008](docs/adr/0008-mooring-buoy-modifier.md)); two the Inland Rules
60
+ deliberately lack, Rule 28 and 23(d)(ii), are tombstoned there
61
+ ([ADR 0018](docs/adr/0018-jurisdiction-delta-is-a-merge-patch.md)). That is
62
+ the whole of `us/inland` today: four records, not a model of the Inland Rules.
60
63
 
61
64
  ## The layers
62
65
 
@@ -104,7 +107,12 @@ same thing.
104
107
 
105
108
  **Jurisdiction is a dimension, not a fork.** Every record carries a
106
109
  `jurisdiction`: `intl`, or `<country-or-body>/<waters>` as a delta on it.
107
- Entries a jurisdiction doesn't override are inherited, not restated.
110
+ Entries a jurisdiction doesn't override are inherited, not restated. The
111
+ delta is an [RFC 7396](https://www.rfc-editor.org/rfc/rfc7396) merge patch
112
+ over `intl` keyed by entry id: the jurisdiction's own entries are its keys,
113
+ its `suppressions` are the keys set to `null`, and silence inherits
114
+ ([ADR 0018](docs/adr/0018-jurisdiction-delta-is-a-merge-patch.md)). Any
115
+ conformant merge-patch library reproduces the resolved rule set.
108
116
 
109
117
  **Predicates, not enumerations.** Gates are `fact:length_m < 7`, never a
110
118
  pre-built list of configurations. Enumerated tables are where prior art
@@ -136,12 +144,12 @@ paragraphs prevails is a relation.
136
144
 
137
145
  | relation | meaning |
138
146
  |---|---|
139
- | `rel:includes` | import the referenced entry's **lights only**, never its predicate |
140
- | `rel:conditional_includes` | import lights when the stated `when` holds; `one_of` is a set of legal alternatives |
141
- | `rel:in_lieu_of` | this entry's lights replace the referenced entries' lights |
142
- | `rel:excludes` | must not be shown together: a pick-one between alternatives, never one obligation vetoing another |
143
- | `rel:exempts` | the referenced requirement does not apply (30(e)) |
144
- | `rel:overrides` | this paragraph's requirement prevails over the referenced one's when both apply (Rule 18; Rule 26(a) over Rule 30's anchor lights) |
147
+ | `rel:includes` | import the referenced entry's lights, their modality and its scalar gates — never its axes (ADR 0019) |
148
+ | `rel:conditional_includes` | import lights when the stated `when` holds; `one_of` is a set of legal alternatives, exactly one per display, or none under a `may` carrier |
149
+ | `rel:in_lieu_of` | this entry's lights replace the referenced entries' lights; two entries replacing overlapping sets are alternatives to each other |
150
+ | `rel:excludes` | must not be shown together: a pick-one between alternatives, never one obligation vetoing another; a constraint on one display, never a removal |
151
+ | `rel:exempts` | the referenced requirement does not apply (30(e)); reaches an entry in force, never an import |
152
+ | `rel:overrides` | this paragraph's requirement prevails over the referenced one's when both apply (Rule 18; Rule 26(a) over Rule 30's anchor lights); reaches an entry in force, never an import |
145
153
 
146
154
  Modality is `modality:shall`, `modality:may`, `modality:shall-if-practicable`, `modality:shall-not`,
147
155
  `modality:shall-not-impede`, or `modality:conditional` with a `modality_by` table when it turns
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "conditions": "night (Rule 20(b): sunset to sunrise), and by 20(c) in restricted visibility by day. Day shapes are not in this release.",
3
3
  "relations": {
4
- "rel:includes": "import another entry's lights only, never its predicate",
5
- "rel:conditional_includes": "import lights when the stated `when` holds; `one_of` is a set of legal alternatives among which the vessel picks one",
6
- "rel:in_lieu_of": "this entry's lights replace the referenced entries' lights",
7
- "rel:excludes": "the referenced entries must not be shown at the same time",
8
- "rel:exempts": "the referenced entries are not required",
9
- "rel:overrides": "the superiority relation: the referencing norm's obligation displaces the referenced norm's when both apply"
4
+ "rel:includes": "import another entry's lights, their modality and its scalar gates, never its axes (ADR 0019)",
5
+ "rel:conditional_includes": "import lights when the stated `when` holds; `one_of` is a set of legal alternatives among which the vessel picks exactly one, or none under a `may` carrier (ADR 0019)",
6
+ "rel:in_lieu_of": "this entry's lights replace the referenced entries' lights; two entries whose targets intersect are alternatives to each other (ADR 0019)",
7
+ "rel:excludes": "the referenced entries must not be shown at the same time; a constraint on one display, never a removal (ADR 0019)",
8
+ "rel:exempts": "the referenced entries are not required; reaches an entry in force, never an import (ADR 0019)",
9
+ "rel:overrides": "the superiority relation: the referencing norm's obligation displaces the referenced norm's when both apply; reaches an entry in force, never an import (ADR 0019)"
10
10
  },
11
11
  "modalities": {
12
12
  "modality:shall": "mandatory",
@@ -110,6 +110,11 @@
110
110
  "cite": "14(a)",
111
111
  "what": "When two power-driven vessels are meeting on reciprocal or nearly reciprocal courses so as to involve risk of collision each shall alter her course to starboard so that each shall pass on the port side of the other.",
112
112
  "why": "`category:conduct`, not `category:classification`: the paragraph states the situation in words and prescribes the action, and 14(b) is the paragraph that deems the situation to exist -- entry rule:14b cites it. The action is monitored over a trace rather than evaluated at a point (ADR 0005 sec. 1), and no conduct entry shape exists yet. Q-45."
113
+ },
114
+ {
115
+ "cite": "28",
116
+ "what": "Under us/inland a record carrying activity:cbd selects nothing: rule:28 is suppressed and the Rule 23(a) entries it imported read activity:none.",
117
+ "why": "'Constrained by her draft' is not an Inland status, so the record is out of vocabulary there and the package does not pick a reading for it; a consumer that decodes SignalK for inland waters maps it to activity:none (ADR 0018)."
113
118
  }
114
119
  ],
115
120
  "represented_paragraphs": [
@@ -2176,5 +2181,19 @@
2176
2181
  "modality": "modality:may",
2177
2182
  "note": "The under-50 m alternative, on the same 33 CFR 90.5 deeming that brings rule:30a:mooring_buoy in. It is a separate entry because widening rule:30b's predicate would edit an intl entry to carry a national delta, which REQ-SCOPE-4 forbids."
2178
2183
  }
2184
+ ],
2185
+ "suppressions": [
2186
+ {
2187
+ "jurisdiction": "us/inland",
2188
+ "suppresses": "rule:28",
2189
+ "cite": "28",
2190
+ "why": "33 CFR 83.28 is [Reserved]: 'constrained by her draft' is not an Inland Rules status, so the Rule 28 signal is deliberately absent under us/inland rather than untranscribed (Q-11, ADR 0018)."
2191
+ },
2192
+ {
2193
+ "jurisdiction": "us/inland",
2194
+ "suppresses": "rule:23d_ii",
2195
+ "cite": "23(d)(ii)",
2196
+ "why": "33 CFR 83.23(d) is a single paragraph with no counterpart to the under-7 m, 7 kn exception of 23(d)(ii); the exception is deliberately absent under us/inland (Q-11, ADR 0018)."
2197
+ }
2179
2198
  ]
2180
2199
  }
package/data/i18n/en.json CHANGED
@@ -7,21 +7,87 @@
7
7
  "reviewed_by": [
8
8
  "mark-brannan"
9
9
  ],
10
- "review_date": "2026-09-13"
10
+ "review_date": "2026-09-16"
11
11
  },
12
12
  "strings": {
13
- "light": {
13
+ "lights": {
14
14
  "light:masthead": "Masthead light",
15
+ "light:sidelights": "Sidelights",
15
16
  "light:sidelight_starboard": "Starboard sidelight",
16
17
  "light:sidelight_port": "Port sidelight",
17
18
  "light:sternlight": "Sternlight",
18
19
  "light:towing": "Towing light",
19
- "light:all_round": "All-round light"
20
+ "light:all_round": "All-round light",
21
+ "light:flashing": "Flashing light",
22
+ "light:torch": "Electric torch or lighted lantern",
23
+ "light:deck_lights": "Working or equivalent lights"
20
24
  },
21
- "modality": {
25
+ "modalities": {
22
26
  "modality:shall": "Required",
23
27
  "modality:may": "Permitted",
24
- "modality:shall-if-practicable": "Required if practicable"
28
+ "modality:shall-if-practicable": "Required if practicable",
29
+ "modality:conditional": "Conditional",
30
+ "modality:exempt": "Exempt",
31
+ "modality:shall-not": "Prohibited",
32
+ "modality:shall-not-impede": "Must not impede"
33
+ },
34
+ "roles": {
35
+ "role:give-way": "Give way",
36
+ "role:stand-on": "Stand on",
37
+ "role:shall-not-impede": "Must not impede",
38
+ "role:keep-clear": "Keep clear",
39
+ "role:none": "No role"
40
+ },
41
+ "encounters": {
42
+ "encounter:head-on": "Head-on",
43
+ "encounter:crossing": "Crossing",
44
+ "encounter:overtaking": "Overtaking",
45
+ "encounter:none": "No encounter"
46
+ },
47
+ "jurisdictions": {
48
+ "intl": "International",
49
+ "us/inland": "US Inland"
50
+ },
51
+ "facts": {
52
+ "propulsion:power": "Power-driven",
53
+ "propulsion:sail": "Sailing",
54
+ "propulsion:oars": "Oars",
55
+ "activity:none": "None",
56
+ "activity:fishing": "Fishing",
57
+ "activity:trawling": "Trawling",
58
+ "activity:towing": "Towing",
59
+ "activity:pushing": "Pushing",
60
+ "activity:being_towed": "Being towed",
61
+ "activity:nuc": "Not under command",
62
+ "activity:ram": "Restricted in her ability to manoeuvre",
63
+ "activity:ram_underwater": "Restricted in her ability to manoeuvre, engaged in underwater operations",
64
+ "activity:cbd": "Constrained by her draught",
65
+ "activity:mine": "Engaged in minesweeping",
66
+ "activity:pilot": "Engaged on pilotage duty",
67
+ "activity:diving": "Engaged in diving operations",
68
+ "position:underway": "Underway",
69
+ "position:anchored": "At anchor",
70
+ "position:aground": "Aground",
71
+ "position:moored": "Moored",
72
+ "rule18_class:nuc": "Not under command",
73
+ "rule18_class:ram": "Restricted in ability to manoeuvre",
74
+ "rule18_class:fishing": "Fishing",
75
+ "rule18_class:wig": "Wing-in-ground craft",
76
+ "rule18_class:cbd": "Constrained by draught",
77
+ "rule18_class:sail": "Sailing vessel",
78
+ "rule18_class:power": "Power-driven vessel",
79
+ "dynamics:tanker": "Tanker",
80
+ "dynamics:cargo": "Cargo ship",
81
+ "dynamics:ferry": "Ferry",
82
+ "dynamics:fishing": "Fishing vessel",
83
+ "dynamics:yacht": "Yacht",
84
+ "dynamics:rib": "RIB",
85
+ "dynamics:unknown": "Unknown",
86
+ "obstruction_side:port": "Port",
87
+ "obstruction_side:starboard": "Starboard",
88
+ "wind_side:port": "Port",
89
+ "wind_side:starboard": "Starboard",
90
+ "wind_side:unknown": "Unknown"
25
91
  }
26
92
  }
27
93
  }
package/data/i18n/fi.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "review_date": "2026-09-15"
9
9
  },
10
10
  "strings": {
11
- "light": {
11
+ "lights": {
12
12
  "light:masthead": "Mastovalo",
13
13
  "light:sidelight_starboard": "Oikea sivuvalo",
14
14
  "light:sidelight_port": "Vasen sivuvalo",
@@ -16,7 +16,7 @@
16
16
  "light:towing": "Hinausvalo",
17
17
  "light:all_round": "Ympäri näköpiirin näkyvä valo"
18
18
  },
19
- "modality": {
19
+ "modalities": {
20
20
  "modality:shall": "Pakollinen",
21
21
  "modality:may": "Sallittu",
22
22
  "modality:shall-if-practicable": "Pakollinen, jos mahdollista"
package/data/lights.json CHANGED
@@ -8,8 +8,7 @@
8
8
  },
9
9
  "lights": {
10
10
  "light:masthead": {
11
- "name": "masthead light",
12
- "colregs_term": "masthead light",
11
+ "term": "masthead light",
13
12
  "cite": "21(a)",
14
13
  "color": "white",
15
14
  "character": "steady",
@@ -22,8 +21,7 @@
22
21
  "rule21": true
23
22
  },
24
23
  "light:sidelights": {
25
- "name": "sidelights",
26
- "colregs_term": "sidelights",
24
+ "term": "sidelights",
27
25
  "cite": "21(b)",
28
26
  "character": "steady",
29
27
  "composite": true,
@@ -36,8 +34,7 @@
36
34
  "rule21": true
37
35
  },
38
36
  "light:sidelight_starboard": {
39
- "name": "starboard sidelight",
40
- "colregs_term": "sidelight",
37
+ "term": "sidelight",
41
38
  "cite": "21(b)",
42
39
  "color": "green",
43
40
  "character": "steady",
@@ -50,8 +47,7 @@
50
47
  "rule21": true
51
48
  },
52
49
  "light:sidelight_port": {
53
- "name": "port sidelight",
54
- "colregs_term": "sidelight",
50
+ "term": "sidelight",
55
51
  "cite": "21(b)",
56
52
  "color": "red",
57
53
  "character": "steady",
@@ -64,8 +60,7 @@
64
60
  "rule21": true
65
61
  },
66
62
  "light:sternlight": {
67
- "name": "sternlight",
68
- "colregs_term": "sternlight",
63
+ "term": "sternlight",
69
64
  "cite": "21(c)",
70
65
  "color": "white",
71
66
  "character": "steady",
@@ -78,8 +73,7 @@
78
73
  "rule21": true
79
74
  },
80
75
  "light:towing": {
81
- "name": "towing light",
82
- "colregs_term": "towing light",
76
+ "term": "towing light",
83
77
  "cite": "21(d)",
84
78
  "color": "yellow",
85
79
  "character": "steady",
@@ -92,8 +86,7 @@
92
86
  "rule21": true
93
87
  },
94
88
  "light:all_round": {
95
- "name": "all-round light",
96
- "colregs_term": "all-round light",
89
+ "term": "all-round light",
97
90
  "cite": "21(e)",
98
91
  "color": null,
99
92
  "color_note": "colour is fixed by the citing paragraph, not by Rule 21",
@@ -106,8 +99,7 @@
106
99
  "rule21": true
107
100
  },
108
101
  "light:flashing": {
109
- "name": "flashing light",
110
- "colregs_term": "flashing light",
102
+ "term": "flashing light",
111
103
  "cite": "21(f)",
112
104
  "color": null,
113
105
  "color_note": "colour is fixed by the citing paragraph",
@@ -122,8 +114,7 @@
122
114
  "rule21": true
123
115
  },
124
116
  "light:torch": {
125
- "name": "electric torch or lighted lantern",
126
- "colregs_term": "electric torch or lighted lantern",
117
+ "term": "electric torch or lighted lantern",
127
118
  "cite": "25(d)(i)",
128
119
  "color": "white",
129
120
  "character": "shown in sufficient time to prevent collision",
@@ -133,8 +124,7 @@
133
124
  "note": "Not a fixed navigation light: it is held ready at hand and shown on approach. It has no prescribed arc, range or position."
134
125
  },
135
126
  "light:deck_lights": {
136
- "name": "working or equivalent lights",
137
- "colregs_term": "working or equivalent lights",
127
+ "term": "working or equivalent lights",
138
128
  "cite": "30(c)",
139
129
  "color": null,
140
130
  "character": "steady",
package/data/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.3.2"
2
+ "version": "0.3.3"
3
3
  }
@@ -102,6 +102,8 @@ data one.
102
102
  international law where a national body deliberately has none. That is
103
103
  about deltas, not licences; it survives this ADR untouched and is the live
104
104
  blocker on CEVNI. Read the two together or the wrong one gets blamed.
105
+ *(ADR 0018 has since supplied that mechanism; the bar is now "land with
106
+ your tombstones", not "wait".)*
105
107
  - `schema/rules.schema.json` carries the conditional: `text` required unless
106
108
  `text_status` is `withheld`, in which case it is forbidden and
107
109
  `withheld_reason` is required. Only `withheld_reason` is refused on a
@@ -0,0 +1,102 @@
1
+ # ADR 0018 — A jurisdiction delta is an RFC 7396 merge patch, and a tombstone is its `null`
2
+
3
+ Date: 2026-09-16
4
+ Status: proposed — merging this PR is the ruling; a revert undoes it
5
+
6
+ ## Context
7
+
8
+ REQ-SCOPE-3 says a jurisdiction is a delta over `intl`: entries it does not
9
+ mention are inherited. Q-11 found that unsafe as stated. 33 CFR 83 leaves
10
+ Rule 28 "[Reserved]" and has no counterpart to 23(d)(ii), so a `us/inland`
11
+ delta that was merely silent there would inherit `rule:28` and assert an
12
+ international obligation the Inland Rules deliberately do not carry. Both
13
+ absences are verified against the primary source (Cornell LII's copy of
14
+ 33 CFR 83: "§ 83.28 [Reserved] (Rule 28)"; 83.23(d) is a single paragraph
15
+ with no subparagraphs). Q-11 asked for tombstones — "this entry deliberately
16
+ does not exist here", distinguishable from "not yet transcribed" — and held
17
+ every non-`intl` jurisdiction until the mechanism existed. Issue #45 asked,
18
+ separately, which standard encoding the delta should follow.
19
+
20
+ Both questions have been waiting on a second jurisdiction to force them.
21
+ Issue #137 records that nothing was proposing one. `us/inland` already exists
22
+ with two add-only entries under ADR 0008's carve-out, and the two absences
23
+ above are the counterexample that carve-out was left open for: a delta that
24
+ must suppress, not only add.
25
+
26
+ ## Decision
27
+
28
+ 1. **The delta semantics are RFC 7396, JSON Merge Patch.** A jurisdiction's
29
+ delta over `intl` is, in meaning, a merge-patch document keyed by entry
30
+ id: present keys are that jurisdiction's own entries, `null` keys are its
31
+ tombstones, and every key the patch does not mention is inherited. Any
32
+ conformant merge-patch implementation applied to the `intl` entries by
33
+ id reproduces the jurisdiction's resolved rule set. RFC 6902 is not
34
+ adopted: an operation script with JSON Pointer paths would duplicate the
35
+ id addressing `docs/identifiers.md` already provides.
36
+
37
+ 2. **Storage stays columnar; the patch is derived.** Entries keep their
38
+ `jurisdiction` field in the one `entries[]` table, as ADR 0008 laid them
39
+ down. Tombstones are a second top-level table, `suppressions[]`, one
40
+ record per (jurisdiction, entry): `jurisdiction`, `suppresses` (the
41
+ `intl` entry id), `cite` (the paragraph that has no counterpart) and
42
+ `why` (one sentence and the source). `test/data.test.mjs` builds the
43
+ merge patch from those two tables, applies it with a literal RFC 7396
44
+ implementation, and asserts the result equals the evaluator's
45
+ jurisdiction filter. That test is the statement that storage and
46
+ semantics agree; if they ever diverge, the test names which.
47
+
48
+ 3. **Replace is suppress plus add.** A jurisdiction that reads a paragraph
49
+ differently tombstones the `intl` entry and adds its own, under its own
50
+ id (ADR 0015 — the ids are distinct because the norms are). There is no
51
+ third operation and no in-place edit of an `intl` entry: REQ-SCOPE-4
52
+ holds.
53
+
54
+ 4. **The array caveat is accepted and irrelevant here.** Merge Patch
55
+ replaces an array wholesale. The patch is keyed at entry granularity, so
56
+ the only arrays it ever touches are inside a whole entry being added, and
57
+ an entry is small. A jurisdiction never patches one element of an
58
+ inherited entry's `lights`; it replaces the entry (point 3).
59
+
60
+ 5. **Only an inherited entry can be tombstoned.** A tombstone naming a
61
+ jurisdiction's own entry, or another jurisdiction's, is rejected by test:
62
+ the first is a deletion, the second was never in force.
63
+
64
+ 6. **A tombstone is verified the way an entry is.** Each needs one fixture
65
+ under its jurisdiction whose facts match the suppressed entry's predicate
66
+ and whose expectation omits it, and one `intl` fixture showing the entry
67
+ in force at the base (REQ-VERIFY-3, both sides).
68
+
69
+ 7. **Two tombstones land with this ADR**, both verified: `us/inland`
70
+ suppresses `rule:28` (Rule 28 "[Reserved]") and `rule:23d_ii` (no
71
+ counterpart to the under-7 m, 7 kn exception). They are enough to
72
+ exercise inherit and delete; no verified replace case exists in Part C
73
+ yet, so that branch of point 3 is stated, schema-ready and unexercised.
74
+
75
+ ## Consequences
76
+
77
+ - **Q-11 is answered and its hold is lifted.** REQ-SCOPE-3's "no non-`intl`
78
+ jurisdiction lands before an explicit suppression mechanism exists" is
79
+ discharged by this mechanism; a jurisdiction lands with its tombstones or
80
+ it does not land. ADR 0008's add-only carve-out survives as the trivial
81
+ case: a delta with no tombstones is a merge patch with no `null`s.
82
+ - **Issue #45 is closed by this ADR.** README names the RFC beside the
83
+ jurisdiction paragraph so a consumer knows which library semantics
84
+ reproduce the inheritance.
85
+ - **A suppressed entry can leave a record out of vocabulary.** Under
86
+ `us/inland` a record carrying `activity:cbd` now selects nothing:
87
+ `rule:28` is gone and the Rule 23(a) entries it imported read
88
+ `activity:none`. That is the honest answer — "constrained by her draft" is
89
+ not an Inland status — and the package does not pick a reading. It is
90
+ recorded in `known_omissions` and pinned by a fixture. Whether the fact
91
+ vocabulary should carry per-jurisdiction membership so a consumer's
92
+ decode can be checked is a new question, not decided here.
93
+ - **`rule:23d_i` cites a path Inland does not spell.** 33 CFR 83.23(d) has
94
+ no `(i)`; the content sits at bare `83.23(d)`. The entry is inherited
95
+ correctly — the norm is the same — and the citation-spelling question is
96
+ GATE-1's, untouched here.
97
+ - **GATE-1, Q-10 and Q-8 are not taken.** Q-11 named a "second-jurisdiction
98
+ bundle"; this ADR takes only the item that gated data. The others stay
99
+ open on their own triggers.
100
+ - **Cost to reverse.** Delete `suppressions[]` from data, schema and the
101
+ three tests; the two fixtures fail and are deleted with them. No consumer
102
+ reads the table yet. Pre-1.0, a revert.
@@ -0,0 +1,106 @@
1
+ # ADR 0019 — What a relation reaches, and what an import reads
2
+
3
+ Date: 2026-09-16
4
+ Status: proposed — merging this PR is the ruling; a revert undoes it
5
+
6
+ ## Context
7
+
8
+ REQ-MODEL-7 and the README define the six relation verbs by meaning and
9
+ leave composition to the consumer (REQ-CONS-3). colregs-engine#33 checked
10
+ the seven composition decisions its evaluator makes where the data is
11
+ silent. ADR 0007 settled the largest, direction: a directed "this one
12
+ prevails" is `rel:overrides`, and `rel:excludes` is pick-one between
13
+ alternatives. Three readings of the verbs remain stated only in
14
+ colregs-engine's `docs/engine-notes.md` (items 2, 3 and 5) and pinned only
15
+ by its tests: how far a relation reaches, what a `one_of` yields, and which
16
+ part of a referenced entry's predicate an import consults. Each is a
17
+ reading of the Rules, and the package that carries the Rules should say it.
18
+
19
+ ## Decision
20
+
21
+ 1. **Displacing relations reach entries in force; composing relations
22
+ reach what is exhibited.** An entry is *in force* when its own predicate
23
+ holds. `rel:overrides` and `rel:exempts` act on entries in force and on
24
+ nothing else: an import is not in force, it is a light set its carrier
25
+ exhibits, and the carrier is what a displacing relation must name.
26
+ `rel:in_lieu_of`, `rel:excludes`, `rel:includes` and
27
+ `rel:conditional_includes` act on what one display may contain, imports
28
+ included, and remove nothing from the set of entries in force. Direction
29
+ is carried by the verb, never by modality: an override fires from a
30
+ forceful entry (ADR 0007), an exemption from a `modality:exempt` one,
31
+ and `rel:excludes` fires from nowhere — it is symmetric and already
32
+ barred to forceful entries by CI (`test/data.test.mjs`, "rel:excludes is
33
+ reciprocated"). A consumer that infers a veto from an excluder's modality
34
+ is reading a relation the data does not carry.
35
+
36
+ 2. **A `one_of` yields exactly one option per display, or none when the
37
+ carrier is `modality:may`.** Under a `may` carrier the chosen option is
38
+ exhibited in lieu of the carrier's own lights, and the none choice
39
+ exhibits them (25(d)(ii): the sailing lights, or failing them the torch).
40
+ An option already in force by its own predicate discharges the set: the
41
+ carrier imports nothing, and the option composes as the entry it is. A
42
+ vessel restricted in her ability to manoeuvre at anchor shows 30(a) or
43
+ 30(b) because 30(a) and 30(b) apply to her, not because 27(b)(iv)
44
+ redirects her to them. Every choice is returned (REQ-MODEL-8).
45
+
46
+ 3. **An import reads the referenced entry's lights, their modality, and
47
+ its scalar gates — never its axes.** An import is a `rel:includes`, the
48
+ `rel:includes` of a `rel:conditional_includes` branch, or a `one_of`
49
+ option. `facts.json`'s `axes` and `modifiers` are the field: a key
50
+ declared there is the vessel's situation, and the carrier's redirect has
51
+ already placed her in it — 30(d) sends a vessel aground to "the lights
52
+ prescribed in paragraph (a) or (b)", and that she is not at anchor is the
53
+ premise of the redirect, not a gate on it. A key not declared there is a
54
+ fact the redirect does not alter, and the referenced entry's condition
55
+ on it binds: 30(b)'s "less than 50 metres" binds a vessel aground as it
56
+ binds one at anchor, and 25(b)'s "less than 20 metres" binds a vessel
57
+ under oars. Modality is the referenced entry's, resolved against the
58
+ vessel (Rule 28's three reds are `may`; the Rule 23 lights it imports
59
+ stay `shall`). A gate the carrier needs that the source does not carry is
60
+ written on the carrier's branch `when`, as 27(f) and 29(a) do; nothing
61
+ is inferred from a source's axes in either direction. REQ-MODEL-7's
62
+ "lights only, never its predicate" is amended to say this.
63
+
64
+ 4. **Two entries in force whose `rel:in_lieu_of` targets intersect are
65
+ alternatives to each other** and never share a display. This is an
66
+ invariant of the verb, not a declared pair list. The two pairs the data
67
+ can hold in force together read that way in the text: 23(d)(ii)'s
68
+ display is 23(d)(i)'s with the sidelights made practicable-only, and
69
+ 25(d)(i)'s torch is what a vessel shows *if she does not* exhibit (a) or
70
+ (b). The Rule 24 entries that share targets — 24(a)(i) below and above
71
+ 200 m, and 24(c) — are disjoint on their own predicates and never meet.
72
+ A future pair that lawfully combines is the evidence to reopen this
73
+ point; until one exists, the target sets are the declaration.
74
+
75
+ Three shapes were considered and not adopted. Restating each option's
76
+ scalar gate on the carrier's branch (30(d) split at 50 m) keeps "lights
77
+ only" literal at the cost of transcribing 30(b)'s condition into 27(b)(iv),
78
+ 27(f), 29(a) and 30(d), and 25(b)'s into 25(d)(ii), plus a CI check to
79
+ police the copies; the text redirects, it does not restate, and REQ-MODEL-5
80
+ puts a gate where its fact lives. Declaring the point-4 pairs as a relation
81
+ duplicates what the target sets say. A per-verb "reaches imports" flag
82
+ would be set the same way on every entry, and a field nothing varies is
83
+ decoration.
84
+
85
+ ## Consequences
86
+
87
+ - **No data changes.** The README relation table, REQ-MODEL-7 and the
88
+ `relations` notes in `data/applicability.json` say the four points in
89
+ this PR. `docs/identifiers.md` and the schema are untouched.
90
+ - **colregs-engine implements this in one issue, three deletions.** Its
91
+ modality-inferred exclusion path goes: `rel:excludes` is a co-occurrence
92
+ check on a display and nothing more. Its `includeApplies` goes: it
93
+ consulted one axis by name on `rel:includes` imports, the inverse of
94
+ point 3, and no import in the data reaches it on a coherent record —
95
+ every carrier either shares the source's position gate (23(b), 23(c),
96
+ 25(c), 28), gates its branch on it (24, 27(f), 29(a)), or reads
97
+ `fact:making_way`, which refines `position:underway` (26(b)(iii),
98
+ 26(c)(iii), 27(a)(iii), 27(b)(iii)). The availability test it already
99
+ applies to `one_of` options becomes the one rule for every import. Its
100
+ `docs/engine-notes.md` items 2–5 become pointers here. colregs-engine#33
101
+ items 1, 6 and 7 stay the engine's: presentation and output shape.
102
+ - **A `one_of` option's scalar gate is a normative read of the source
103
+ entry.** Editing 30(b)'s length gate changes what a vessel aground may
104
+ show. That is the intended coupling: one paragraph, one gate.
105
+ - **Cost to reverse.** Delete this file and restore two sentences; the
106
+ engine's deleted paths come back by revert. Pre-1.0, a revert.
package/docs/budgets.json CHANGED
@@ -23,6 +23,8 @@
23
23
  "docs/adr/0015-rule-ids-are-paragraph-keys.md": 210,
24
24
  "docs/adr/0016-encounter-roles-are-pooled-across-frames.md": 90,
25
25
  "docs/adr/0017-closed-vocabularies-are-prefixed-identifiers.md": 120,
26
+ "docs/adr/0018-jurisdiction-delta-is-a-merge-patch.md": 120,
27
+ "docs/adr/0019-relation-reach-and-import-reads.md": 110,
26
28
  "docs/timeline.md": 130,
27
29
  "docs/maritime-sources.md": 130,
28
30
  "docs/decisions.md": 40
package/docs/decisions.md CHANGED
@@ -3,4 +3,6 @@
3
3
  Rulings from `/sequence` sessions and other closed judgment calls. One line
4
4
  each: date, short name, the answer, a link to the argument.
5
5
 
6
+ - 2026-09-16 — docs ownership: colregs-engine's docs are sited wrong; colregs owns docs going forward, no new doc lands in colregs-engine. `normative-language.md` was already migrated the same day (commit fe0f8c2, #125); migration of the remaining three (`formal-methods-glossary.md`, `formal-methods-reading-list.md`, `engine-notes.md`) is not yet scheduled. Argued on kanban; no public writeup to link.
6
7
  - 2026-09-16 — own vs self: rename the subject segment `own` to `self` across the situation record (fact keys, precedence effect keys, facts.json, fixtures, docs/identifiers.md). Work spawned as [colregs#138](https://github.com/mark-brannan/colregs/issues/138).
8
+ - 2026-09-16 — Q-54, Rule 17's phases are monotone: once 17(a)(ii) permission or the 17(b) duty has arisen for the stand-on vessel, it does not fall back on belated give-way compliance. Argued in [colregs#72](https://github.com/mark-brannan/colregs/issues/72); recorded in `docs/requirements.md`.
@@ -181,7 +181,7 @@ is an enumerated fact, so its values follow the bare-fact-name rule above:
181
181
  Rule 13(d) is the reason history is a class and not a note. Once a vessel is
182
182
  overtaking, a subsequent alteration of the bearing does not make her a
183
183
  crossing vessel; the instantaneous geometry, read alone, says otherwise and
184
- hands the duty to the wrong vessel. So the latch is a fact:
184
+ hands the duty to the wrong vessel. So overtaking history is a fact:
185
185
 
186
186
  - `self:hist:was_overtaking` — this subject was, earlier in this encounter,
187
187
  an overtaking vessel with respect to the other.
@@ -433,6 +433,9 @@ implemented in the reference evaluator and asserted by the fixtures.
433
433
  words. It is deliberately not a light reference and does not resolve to
434
434
  one.
435
435
 
436
+ Not being an identifier says nothing about whether a vocabulary gets a
437
+ catalog label; see REQ-LANG-6.
438
+
436
439
  ## This does not reverse GATE-5
437
440
 
438
441
  GATE-5 declined a CI-enforced terminology glossary, permanently, for the
@@ -67,7 +67,7 @@ is Rules 11–18, III is Rule 19; which governs a pair is `INV-19a-scope`.
67
67
  # Rule 13 — Overtaking
68
68
 
69
69
  13(a) assigns a role, 13(b) deems the encounter, 13(c) resolves doubt, 13(d)
70
- latches the result against the geometry that follows.
70
+ persists the result against the geometry that follows.
71
71
 
72
72
  ### INV-13b — the overtaking deeming test
73
73
 
@@ -122,7 +122,7 @@ well as Rules 14 and 15.
122
122
  Settled by P4.2's role assignment: the override is or is not needed to keep
123
123
  "never both give-way" true.
124
124
 
125
- ### INV-13d — the latch: the classification survives the geometry
125
+ ### INV-13d — overtaking history: the classification survives the geometry
126
126
 
127
127
  `13(d)` · classification · temporal, a state to every later state of the
128
128
  encounter · `✎`
@@ -140,10 +140,11 @@ until self is finally past and clear, the encounter type of the pair is
140
140
  forbids: an overtaking drawn out on the bow reads *crossing*, and hands
141
141
  give-way to the wrong vessel.
142
142
  - **Undetermined term.** "Finally past and clear" has no fact and no threshold;
143
- the data's latch is consumer-cleared and never self-clears (`Q-47`), a trace
144
- model's is its segmentation, and no bearing or range is invented here.
145
- - **Readings in doubt.** Two: what arms the latch, `Q-51`; what it forbids,
146
- `Q-52`.
143
+ the data's overtaking history is consumer-cleared and never self-clears
144
+ (`Q-47`), a trace model's is its segmentation, and no bearing or range is
145
+ invented here.
146
+ - **Readings in doubt.** Two: what arms the overtaking history, `Q-51`; what it
147
+ forbids, `Q-52`.
147
148
  - Entries: `rule:13d`, reading `self`/`other:hist:was_overtaking` and no geometry; the
148
149
  `was_overtaking: false` gates on `rule:14b`, `rule:15a:crossing`, `rule:15a:keep_out_of_the_way`
149
150
  implement `Q-52`'s *broad* reading. Settled by `Q-51`, `Q-52`, then TLC on a
@@ -162,8 +163,8 @@ type alone cannot distinguish `Q-52`'s readings.
162
163
 
163
164
  - **State remembered.** As `INV-13d`.
164
165
  - Entries: `rule:13a`'s `any_of` second limb, `self:hist:was_overtaking: true` — role
165
- asserted from the latch directly, keeping the limbs separable. Settled by
166
- `Q-52`.
166
+ asserted from the overtaking history directly, keeping the limbs separable.
167
+ Settled by `Q-52`.
167
168
 
168
169
  ---
169
170
 
@@ -174,10 +175,10 @@ type alone cannot distinguish `Q-52`'s readings.
174
175
  `14(b)` reading 14(a)'s conditions · classification · single-state · `✎`
175
176
 
176
177
  **Invariant.** At any state at which two power-driven vessels are in sight,
177
- both underway, with risk of collision, and neither latched under `INV-13d`, a
178
- head-on situation is deemed to exist exactly when each sees the other ahead or
179
- nearly ahead: `self:geo:rel_bearing_deg` and `other:geo:rel_bearing_deg` each
180
- in [0°, 11.25°] ∪ [348.75°, 360°).
178
+ both underway, with risk of collision, and neither carrying overtaking history
179
+ under `INV-13d`, a head-on situation is deemed to exist exactly when each sees
180
+ the other ahead or nearly ahead: `self:geo:rel_bearing_deg` and
181
+ `other:geo:rel_bearing_deg` each in [0°, 11.25°] ∪ [348.75°, 360°).
181
182
 
182
183
  - **Both bearings, not one.** 14(b)'s lights limb is a statement about the
183
184
  *other* vessel's aspect; one bearing alone swallows part of the crossing
@@ -219,9 +220,9 @@ way; both are directed to act, and the duty is symmetric.
219
220
  `15(a)` · classification · single-state · `✎`
220
221
 
221
222
  **Invariant.** At any state at which two power-driven vessels are in sight,
222
- both underway, with risk of collision, and neither latched under `INV-13d`, the
223
- encounter type is `crossing` exactly when it is neither `overtaking` under
224
- `INV-13b` nor `head-on` under `INV-14b`.
223
+ both underway, with risk of collision, and neither carrying overtaking history
224
+ under `INV-13d`, the encounter type is `crossing` exactly when it is neither
225
+ `overtaking` under `INV-13b` nor `head-on` under `INV-14b`.
225
226
 
226
227
  - **Derived, not enumerated.** Enumerating the sector gives two statements of
227
228
  one boundary, and the partition's only failure mode is editing one;
@@ -462,11 +463,12 @@ is at each state in exactly one of three phases:
462
463
 
463
464
  Phase 3 dominates phase 2: an obligation is not qualified by a permission.
464
465
 
465
- - **Reading in doubt.** Monotone or not: `Q-54`.
466
- - **State remembered.** If monotone, one three-valued latch per stand-on vessel
467
- per encounter; if not, no latch but `INV-17c` still needs the phase of each
468
- action. TLC distinguishes them: under monotone a late-complying give-way
469
- vessel still leaves stand-on action permitted; under reversible it does not.
466
+ - **Reading ruled.** Monotone (`Q-54`): once phase 2 or 3 is reached for the
467
+ stand-on vessel in an encounter, it does not fall back on belated give-way
468
+ compliance.
469
+ - **State remembered.** One three-valued marker per stand-on vessel per
470
+ encounter, monotone: a late-complying give-way vessel still leaves stand-on
471
+ action permitted.
470
472
 
471
473
  ---
472
474
 
@@ -601,7 +603,7 @@ not jointly exhaustive (`INV-19a-third-state`); Rule 19 supplements Section I
601
603
 
602
604
  - **Temporal note.** The switch is a function of the current state; a pair may
603
605
  cross it and back inside one encounter, and no paragraph says what becomes of
604
- a 13(d) latch or a Rule 17 phase: `Q-55`.
606
+ a 13(d) overtaking history or a Rule 17 phase: `Q-55`.
605
607
  - **Undetermined term.** "In or near an area of restricted visibility" has no
606
608
  fact (3(l)'s atmospheric condition is nowhere in `data/facts.json`), so `rule:19a`
607
609
  selects Section III for any pair not in sight — wider — and records a gap.
@@ -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
 
@@ -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
@@ -1430,18 +1430,18 @@ nothing is blocked while open.
1430
1430
  give-way and Rule 16 binds her. *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
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
1433
+ - **Q-51** — **What arms 13(d)'s overtaking history?** *A:* 13(b)'s deeming, at the first
1434
1434
  state the geometry holds. *B:* 13(a)'s duty actually attaching. They differ
1435
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
1436
+ surface. Pencilled **B** ✎ (Solace, 2026-09-14): the overtaking history arms when the
1437
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
1438
+ history arms on risk of collision and then persists; no authority was found on
1439
1439
  overtaking geometry with no risk of collision. Free to reverse: four entries
1440
1440
  (`rule:13a`, `rule:13b`, `rule:13d`) and their fixtures, and nothing consumes the one
1441
1441
  state this changes the answer for. Settled by: a decision on Rule 13 itself,
1442
1442
  or Cockcroft & Lameijer on whether overtaking status needs risk of
1443
1443
  collision.
1444
- - **Q-52** — **What does 13(d)'s latch forbid?** *Narrow:* reclassification to
1444
+ - **Q-52** — **What does 13(d)'s overtaking history forbid?** *Narrow:* reclassification to
1445
1445
  *crossing* only, as the paragraph says, leaving head-on to Rule 14 on the
1446
1446
  geometry of the moment. *Broad:* the encounter stays an overtaking and no
1447
1447
  other Section II classification attaches. Both preserve the duty; they differ
@@ -1459,16 +1459,16 @@ nothing is blocked while open.
1459
1459
  flags any other alteration. No default (Rule 17 has no entry). Recommend
1460
1460
  **exception**: 17(a)(ii) describes an action, not a proviso, and leaves a
1461
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.
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
1474
  visibility. No default (nothing is temporal). Recommend **persisting**, which
@@ -923,6 +923,48 @@
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) and 23(a) are 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_i"
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
+ ]
926
968
  }
927
969
  ]
928
970
  }
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "colregs",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
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": {
@@ -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" },