colregs 0.3.0 → 0.3.2

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.
Files changed (44) hide show
  1. package/README.md +13 -11
  2. package/data/applicability.json +478 -503
  3. package/data/facts.json +22 -22
  4. package/data/geometry.json +8 -8
  5. package/data/i18n/en.json +27 -0
  6. package/data/i18n/fi.json +25 -0
  7. package/data/images.json +32 -32
  8. package/data/operations.json +79 -0
  9. package/data/rules.json +5 -0
  10. package/data/version.json +1 -1
  11. package/docs/adr/0006-json-schema-and-identifier-diff.md +2 -0
  12. package/docs/adr/0011-api-shape.md +7 -7
  13. package/docs/adr/0012-trace-and-rule2-departure-api.md +2 -2
  14. package/docs/adr/0014-engine-interface-owned-by-colregs.md +103 -0
  15. package/docs/adr/0015-rule-ids-are-paragraph-keys.md +191 -0
  16. package/docs/adr/0016-encounter-roles-are-pooled-across-frames.md +82 -0
  17. package/docs/adr/0017-closed-vocabularies-are-prefixed-identifiers.md +105 -0
  18. package/docs/budgets.json +8 -15
  19. package/docs/decisions.md +6 -0
  20. package/docs/identifiers.md +121 -109
  21. package/docs/maritime-sources.md +58 -0
  22. package/docs/normative-language.md +103 -0
  23. package/docs/part-b-invariants.md +48 -45
  24. package/docs/requirements.md +147 -138
  25. package/fixtures/applicability-fixtures.json +190 -190
  26. package/fixtures/situation-fixtures.json +870 -599
  27. package/package.json +1 -1
  28. package/schema/applicability-fixtures.schema.json +4 -13
  29. package/schema/applicability.schema.json +77 -77
  30. package/schema/conduct-evaluation.schema.json +135 -0
  31. package/schema/display-evaluation.schema.json +146 -0
  32. package/schema/encounter-evaluation.schema.json +109 -0
  33. package/schema/evaluation.schema.json +149 -0
  34. package/schema/fact-record.schema.json +30 -0
  35. package/schema/facts.schema.json +4 -4
  36. package/schema/i18n-catalog.schema.json +47 -0
  37. package/schema/operations.schema.json +124 -0
  38. package/schema/rule2-departure-finding.schema.json +72 -0
  39. package/schema/rule2-departure-model.schema.json +159 -0
  40. package/schema/situation-fixtures.schema.json +30 -127
  41. package/schema/situation.schema.json +72 -0
  42. package/schema/trace.schema.json +33 -0
  43. package/data/deprecated-identifiers.json +0 -7
  44. package/schema/deprecated-identifiers.schema.json +0 -29
@@ -17,10 +17,23 @@ citation: `27(a)(i)` is what a mariner, a lawyer and a court all write, and
17
17
  what a consumer stores when it records why a light was shown. Prefixing it
18
18
  would put a package-local token in front of a reference that belongs to the
19
19
  Convention rather than to this repository, and would make a stored citation
20
- unreadable outside the tool that stored it. Entry ids are derived from
21
- paragraph paths (below) and inherit the same transparency for the same
22
- reason. Paragraph-keying is argued in ADR 0001 and required by
23
- REQ-MODEL-4; nothing here reopens either.
20
+ unreadable outside the tool that stored it. Rule ids sit beside this class
21
+ rather than in it: `rule:13b` is *shaped* like the cite it was minted from,
22
+ but it is a name in a namespace and a consumer reads the paragraph out of
23
+ `cite`, never out of the id (ADR 0015).
24
+ Paragraph-keying is argued in ADR 0001 and required by REQ-MODEL-4; nothing
25
+ here reopens either.
26
+
27
+ **Jurisdiction values sit beside paragraph paths in this class, and for the
28
+ same reason.** `intl` and `us/inland` are not names this package coined:
29
+ jurisdiction is a coordinate with REQ-SCOPE-2's own `<body>/<waters>`
30
+ grammar, its left segment borrowed from ISO 3166, the whole value doubling
31
+ as a corpus key and a `data/text/` filesystem path — its sibling axis,
32
+ `language`, is a bare BCP 47 tag for the same reason. A jurisdiction value
33
+ is immutable under REQ-MODEL-10 like any identifier here — renaming
34
+ `us/inland` would break every stored provenance and corpus path — it just
35
+ carries no prefix, because the grammar that owns it already keeps it stable
36
+ and collision-free (ADR 0017).
24
37
 
25
38
  **Vocabulary identifiers carry a type prefix.** These names are this
26
39
  package's own — nothing in COLREGS calls anything `masthead` or `nuc`. They
@@ -29,16 +42,25 @@ collided in it: `towing` was simultaneously a light id (Rule 21(d)) and an
29
42
  `activity` value (Rule 24(a)), so a consumer holding the string `towing`
30
43
  could not say what it was a name *for* without knowing which field it came
31
44
  out of. The prefix makes the namespace part of the identifier, which
32
- resolves that collision by construction rather than by convention.
45
+ resolves that collision by construction rather than by convention. The same
46
+ shape recurs inside the closed vocabularies themselves: `shall-not-impede`
47
+ names both a modality and a role, and `none` names both a role and an
48
+ encounter — resolved the identical way, `modality:shall-not-impede` and
49
+ `role:shall-not-impede` being two names rather than one (ADR 0017).
33
50
 
34
51
  ## The scheme
35
52
 
36
53
  | form | class | examples |
37
54
  |---|---|---|
55
+ | `rule:<paragraph-slug>` | applicability entries (`data/applicability.json`) | `rule:30a`, `rule:24a_i:exceeds_200m`, `rule:15a:keep_out_of_the_way` |
38
56
  | `light:<id>` | light definitions (`data/lights.json`) | `light:masthead`, `light:sidelight_starboard`, `light:all_round` |
39
57
  | `fact:<key>` | fact keys — the input vocabulary (`data/facts.json`) | `fact:activity`, `fact:length_m`, `fact:making_way`, `fact:on_mooring_buoy` |
40
58
  | `<fact>:<value>` | values of an enumerated fact | `activity:nuc`, `position:anchored`, `propulsion:sail`, `obstruction_side:port` |
41
59
  | `rel:<name>` | the five relation verbs (`data/applicability.json`) | `rel:includes`, `rel:in_lieu_of`, `rel:exempts` |
60
+ | `modality:<value>` | modality values (`data/applicability.json` `modalities`) | `modality:shall`, `modality:may` |
61
+ | `role:<value>` | effect role values (`data/applicability.json` `effects.roles`) | `role:give-way`, `role:none` |
62
+ | `encounter:<value>` | effect encounter values (`data/applicability.json` `effects.encounters`) | `encounter:head-on`, `encounter:none` |
63
+ | `category:<value>` | entry category values (`data/applicability.json` `categories`) | `category:precedence`, `category:display` |
42
64
 
43
65
  The prefix names the namespace the identifier lives in. For a fact *value*
44
66
  that namespace is the fact itself, written bare: `activity:nuc`, not
@@ -69,9 +91,9 @@ for a better idea, logging the change. What would settle it: the first
69
91
  two-subject entry — Rule 18 — actually being written against it. This
70
92
  section answers `Q-28`.
71
93
 
72
- A `display` entry reads one vessel. A `classification` or `precedence`
73
- entry reads two, and needs to say *whose* `fact:activity` it means. The form
74
- is three segments:
94
+ A `category:display` entry reads one vessel. A `category:classification` or
95
+ `category:precedence` entry reads two, and needs to say *whose*
96
+ `fact:activity` it means. The form is three segments:
75
97
 
76
98
  ```
77
99
  <subject>:<class>:<key>
@@ -79,14 +101,14 @@ is three segments:
79
101
 
80
102
  | segment | values |
81
103
  |---|---|
82
- | subject | `own`, `other`, `pair` |
104
+ | subject | `self`, `other`, `pair` |
83
105
  | class | `fact`, `kin`, `geo`, `hist` |
84
106
  | key | the identifier as it already exists, or a new one in a new class |
85
107
 
86
- `own:fact:activity`, `other:kin:heading_deg`, `pair:geo:in_sight`,
87
- `own:hist:was_overtaking`.
108
+ `self:fact:activity`, `other:kin:heading_deg`, `pair:geo:in_sight`,
109
+ `self:hist:was_overtaking`.
88
110
 
89
- **A key with no subject segment means `own:`.** This is the whole of the
111
+ **A key with no subject segment means `self:`.** This is the whole of the
90
112
  backward-compatibility story and it is why the subject is a *prefix* rather
91
113
  than a change to the fact keys. `fact:activity` still spells `fact:activity`
92
114
  and still denotes what it always denoted, so every predicate in
@@ -94,14 +116,14 @@ and still denotes what it always denoted, so every predicate in
94
116
  `fixtures/applicability-fixtures.json` and every stored citation a consumer
95
117
  holds stays correct unedited — `REQ-MODEL-10` is satisfied by construction
96
118
  rather than by a migration. The alternative shapes were a suffix
97
- (`fact:activity:own`), which buries the thing you are scanning for at the
119
+ (`fact:activity:self`), which buries the thing you are scanning for at the
98
120
  end of a variable-length name, and per-subject fact keys
99
121
  (`fact:own_activity`), which would double the fact vocabulary and repoint
100
122
  nothing but would leave two names for one concept forever. Prefixing is the
101
123
  only one of the three where the existing vocabulary is a strict subset of
102
124
  the new one.
103
125
 
104
- The cost, stated so nobody rediscovers it: `own`, `other` and `pair` are now
126
+ The cost, stated so nobody rediscovers it: `self`, `other` and `pair` are now
105
127
  reserved at the head of the identifier space, and no fact, light or relation
106
128
  may ever be named one of them. That is the price of a subject segment that
107
129
  is not itself prefixed, and it is cheap — the three words are not candidate
@@ -109,9 +131,9 @@ names for anything this package models.
109
131
 
110
132
  ### The three subjects
111
133
 
112
- `own` is the vessel the rule addresses; `other` is the vessel it is in an
134
+ `self` is the vessel the rule addresses; `other` is the vessel it is in an
113
135
  encounter with. **`pair` is the encounter itself**, and it exists because
114
- some facts belong to neither vessel: range is one number, not own's number
136
+ some facts belong to neither vessel: range is one number, not self's number
115
137
  and the other's. Putting `geo:range_m` under both subjects would create two
116
138
  identifiers for one quantity and a class of bug — the two disagreeing —
117
139
  that has no meaning.
@@ -122,21 +144,21 @@ Geometry splits on whether the quantity is symmetric between the vessels:
122
144
 
123
145
  | fact | subject | |
124
146
  |---|---|---|
125
- | `geo:rel_bearing_deg` | `own` / `other` | bearing of the *other* subject, clockwise from this subject's heading |
147
+ | `geo:rel_bearing_deg` | `self` / `other` | bearing of the *other* subject, clockwise from this subject's heading |
126
148
  | `geo:range_m` | `pair` | |
127
149
  | `geo:bearing_change_deg_min` | `pair` | Rule 7(d)(i)'s steady bearing |
128
150
  | `geo:cpa_m`, `geo:tcpa_s` | `pair` | |
129
151
  | `geo:in_sight` | `pair` | Rule 3(k), symmetric because the rule defines it that way |
130
152
 
131
153
  The directional row is where the namespace earns its keep.
132
- `own:geo:rel_bearing_deg` is relative bearing — where the other vessel is
133
- off own's bow. `other:geo:rel_bearing_deg` is the same fact read from the
154
+ `self:geo:rel_bearing_deg` is relative bearing — where the other vessel is
155
+ off self's bow. `other:geo:rel_bearing_deg` is the same fact read from the
134
156
  other side, which is **aspect**. So aspect gets no identifier of its own: it
135
157
  is a subject swap, not a second fact. Rule 13(b)'s overtaking sector is then
136
- `other:geo:rel_bearing_deg` in (112.5, 247.5) — own more than 22.5° abaft
158
+ `other:geo:rel_bearing_deg` in (112.5, 247.5) — self more than 22.5° abaft
137
159
  the other vessel's beam — written once, in the units the rule itself uses.
138
- Swapping `own` and `other` throughout a predicate reverses the encounter,
139
- which is exactly the operation a `precedence` rule needs and the reason to
160
+ Swapping `self` and `other` throughout a predicate reverses the encounter,
161
+ which is exactly the operation a `category:precedence` rule needs and the reason to
140
162
  prefer a subject namespace over two parallel vocabularies.
141
163
 
142
164
  The directional and the pair geometry are redundant with `kin:` wherever both
@@ -150,7 +172,7 @@ and a record that omits the kinematics is unchecked rather than wrong.
150
172
 
151
173
  `kin:` is the kinematic class ADR 0005 introduces — `kin:position`,
152
174
  `kin:heading_deg`, `kin:sog_kn`, `kin:rot_deg_min`, `kin:dynamics`. It takes
153
- `own`/`other` only; there is no kinematic state of a pair. `kin:dynamics`
175
+ `self`/`other` only; there is no kinematic state of a pair. `kin:dynamics`
154
176
  is an enumerated fact, so its values follow the bare-fact-name rule above:
155
177
  `dynamics:tanker`, not `kin:dynamics:tanker`.
156
178
 
@@ -161,12 +183,12 @@ overtaking, a subsequent alteration of the bearing does not make her a
161
183
  crossing vessel; the instantaneous geometry, read alone, says otherwise and
162
184
  hands the duty to the wrong vessel. So the latch is a fact:
163
185
 
164
- - `own:hist:was_overtaking` — this subject was, earlier in this encounter,
186
+ - `self:hist:was_overtaking` — this subject was, earlier in this encounter,
165
187
  an overtaking vessel with respect to the other.
166
- - `own:hist:latched_at_s` — how long ago that attached, for a `conduct`
188
+ - `self:hist:latched_at_s` — how long ago that attached, for a `category:conduct`
167
189
  monitor. A predicate at a point does not read it.
168
190
 
169
- History is directional — it is *own* that was overtaking — so it takes a
191
+ History is directional — it is *self* that was overtaking — so it takes a
170
192
  subject segment like the fact record does, and never `pair`.
171
193
 
172
194
  ### What this does not do
@@ -180,41 +202,41 @@ keys would have broken.
180
202
  ## Effects `✎`
181
203
 
182
204
  **Pencil** (`docs/conventions.md`): ADR 0005 puts the whole two-subject shape
183
- there. What would settle it: a second family of `precedence` paragraphs —
205
+ there. What would settle it: a second family of `category:precedence` paragraphs —
184
206
  Rules 12, 14 and 15 — written against it. **Written, and it held with one
185
- addition**: Rules 7(d) and 13–15 are the first `classification` entries, and a
207
+ addition**: Rules 7(d) and 13–15 are the first `category:classification` entries, and a
186
208
  classification produces neither a role nor a section, so the table below grows
187
- a third row. Rule 12 turned out to be `precedence` and not `classification`
209
+ a third row. Rule 12 turned out to be `category:precedence` and not `category:classification`
188
210
  (below). This section answers the data half of `Q-27` and is required by
189
211
  `REQ-CAT-8`.
190
212
 
191
- A `display` entry produces `lights`. A `scope` or `precedence` entry produces
213
+ A `category:display` entry produces `lights`. A `category:scope` or `category:precedence` entry produces
192
214
  an **effect**, and the shape of the effect is fixed by the category:
193
215
 
194
216
  | category | effect |
195
217
  |---|---|
196
- | `scope` | `{"part", "section", "applies_rules"}` — which section of which Part governs, and the rules it contains |
197
- | `precedence` | `{"own": <role>, "other": <role>}` — one role per subject |
198
- | `classification` | `{"encounter": <encounter>}` **or** `{"risk_of_collision": true}` — exactly one key |
218
+ | `category:scope` | `{"part", "section", "applies_rules"}` — which section of which Part governs, and the rules it contains |
219
+ | `category:precedence` | `{"self": <role>, "other": <role>}` — one role per subject |
220
+ | `category:classification` | `{"encounter": <encounter>}` **or** `{"risk_of_collision": true}` — exactly one key |
199
221
 
200
- Five roles, a closed set: `give-way`, `stand-on`, `shall-not-impede`,
201
- `keep-clear`, `none`. They are declared in `data/applicability.json` under
202
- `effects`, and they are **not identifiers** — like modality and jurisdiction
203
- values they are a closed vocabulary of their own, outside what `REQ-MODEL-10`
204
- binds.
222
+ Five roles, a closed set: `role:give-way`, `role:stand-on`,
223
+ `role:shall-not-impede`, `role:keep-clear`, `role:none`. They are declared
224
+ in `data/applicability.json` under `effects`, and, like modality and
225
+ category, they are identifiers: prefixed closed vocabularies `REQ-MODEL-10`
226
+ binds (ADR 0017).
205
227
 
206
228
  ### Encounters, and why a classification effect has two shapes
207
229
 
208
- Four encounters, a closed set like the roles: `head-on` (Rule 14), `crossing`
209
- (Rule 15), `overtaking` (Rule 13) and `none`. They are declared in
210
- `data/applicability.json` under `effects.encounters` and, like the roles, they
211
- are not identifiers.
230
+ Four encounters, a closed set like the roles: `encounter:head-on` (Rule 14),
231
+ `encounter:crossing` (Rule 15), `encounter:overtaking` (Rule 13) and
232
+ `encounter:none`. They are declared in `data/applicability.json` under
233
+ `effects.encounters` and, like the roles, they are identifiers.
212
234
 
213
- A `classification` effect carries **exactly one key**, and which key it is
235
+ A `category:classification` effect carries **exactly one key**, and which key it is
214
236
  depends on which question the paragraph answers. Rule 7(d)(i) answers *does
215
237
  risk of collision exist* and produces `{"risk_of_collision": true}`; Rules 13,
216
238
  14 and 15 answer *what kind of encounter is this* and produce an `encounter`.
217
- ADR 0005 gives both questions to `classification` — "relative geometry,
239
+ ADR 0005 gives both questions to `category:classification` — "relative geometry,
218
240
  history → encounter type, risk of collision" — and the two do not merge. A
219
241
  single shape would have made every encounter entry state a risk it does not
220
242
  decide, and 15(a)'s crossing test reads `pair:geo:risk_of_collision` as an
@@ -223,7 +245,7 @@ input rather than producing it.
223
245
  There is no `{"risk_of_collision": false}` and there never will be. 7(a) makes
224
246
  risk a judgement on all available means and deems it to exist in any doubt, so
225
247
  an entry can add a ground for risk and nothing in this package can deny one.
226
- `none` is declared as an encounter for the completeness of the vocabulary and
248
+ `encounter:none` is declared for the completeness of the vocabulary and
227
249
  no entry produces it: an encounter type is asserted by a paragraph, and the
228
250
  absence of one is the absence of an entry rather than an entry with a null
229
251
  value.
@@ -239,17 +261,17 @@ subjects' bearings in half-degree steps and asserts exactly one encounter at
239
261
  each of the 518 400 points; the Alloy version of the same property lives in
240
262
  `colregs-engine`.
241
263
 
242
- ### Rule 12 is `precedence`, not `classification`
264
+ ### Rule 12 is `category:precedence`, not `category:classification`
243
265
 
244
266
  ADR 0005 §1 and the proposal's first-cut table file Rule 12 under
245
- `classification`. It is `precedence` here, for the reason `Q-37` gives for
267
+ `category:classification`. It is `category:precedence` here, for the reason `Q-37` gives for
246
268
  13(a): **12(a) produces a role, and a classification effect has nowhere to put
247
269
  one.** "One of them shall keep out of the way of the other" is give-way and
248
270
  stand-on in the effect vocabulary that already exists, and it is not an
249
271
  encounter type — two sailing vessels meeting are still in a head-on, a
250
272
  crossing or an overtaking, and Rule 12 says which of them gives way rather
251
273
  than which kind of meeting it is. Rule 12 has no deeming paragraph at all:
252
- 12(b) defines the windward side and is a `definition`, so it is the cite on the
274
+ 12(b) defines the windward side and is a `category:definition`, so it is the cite on the
253
275
  `kin:wind_side` fact rather than an entry.
254
276
 
255
277
  The category is `Q-14`'s to settle paragraph by paragraph and this is two more
@@ -257,88 +279,83 @@ of them; the departure from the table is recorded in ADR 0005's pencil log and
257
279
  in `Q-40`.
258
280
 
259
281
  **Who governs over Rule 12.** 12(a)'s subjects are "two sailing vessels",
260
- which is 3(c), so `12a1`–`12a3` gate on `fact:propulsion` and not on the Rule
282
+ which is 3(c), so the three Rule 12 entries gate on `fact:propulsion` and not on the Rule
261
283
  18 rank — a fishing vessel under sail is a sailing vessel. Where Rule 18 also
262
- ranks the pair, its entry displaces Rule 12's: `18b1`–`18b3` and `18c1`–`18c2`
284
+ ranks the pair, its entry displaces Rule 12's: the three 18(b) entries and the two 18(c) entries
263
285
  carry `rel:overrides` against all three, because Rule 18's opening words
264
- except Rules 9, 10 and 13 and nothing else. `13a` overrides them for the same
286
+ except Rules 9, 10 and 13 and nothing else. `rule:13a` overrides them for the same
265
287
  reason it overrides Rule 18 — 13(a) is "notwithstanding" the rest of Sections
266
288
  I and II. The test that pins the relation asserts both reasons from
267
289
  `rules.json`, so the data cannot keep an override after losing the words.
268
290
 
269
291
  **And over Rule 15, the same way.** 15(a)'s subjects are "two power-driven
270
- vessels", which is 3(b), so `15a-give-way` gates on `fact:propulsion` and on
292
+ vessels", which is 3(b), so `rule:15a:keep_out_of_the_way` gates on `fact:propulsion` and on
271
293
  no Rule 18 rank either — a vessel engaged in fishing, or not under command,
272
294
  whose machinery is in use is a power-driven vessel. It used to negate the four
273
295
  ranks in its own predicate, which said the same thing in the one place a test
274
- could not see the reason; `18a1`–`18a3`, `18c1`–`18c2` and `18f1` now carry
296
+ could not see the reason; the 18(a)(i)–(iii) entries, the two 18(c) entries and `rule:18f_i` now carry
275
297
  `rel:overrides` against it instead. The derived half of the test is what makes
276
298
  that checkable: a Rule 18 entry meets Rule 15 when it assigns a helm role and
277
299
  neither subject is gated to a sailing vessel, and every such entry must carry
278
300
  the override, so a Rule 18 paragraph added later cannot join Rule 15 silently.
279
301
 
280
- **The effect names both subjects, and that is the point.** A `precedence`
281
- entry is evaluated from own's side, so 18(a)(i) says own gives way *and* the
282
- other vessel stands on. Writing only own's half would lose Rule 17, which
302
+ **The effect names both subjects, and that is the point.** A `category:precedence`
303
+ entry is evaluated from self's side, so 18(a)(i) says self gives way *and* the
304
+ other vessel stands on. Writing only self's half would lose Rule 17, which
283
305
  attaches to the counterpart of a give-way duty and to nothing else. So
284
- `stand-on` appears only opposite `give-way`, and the counterpart of
285
- `shall-not-impede` is always `none` — that is 8(f)(iii) in the data: a vessel
286
- whose passage is not to be impeded acquires no privilege by it. `none` is
306
+ `role:stand-on` appears only opposite `role:give-way`, and the counterpart of
307
+ `role:shall-not-impede` is always `role:none` — that is 8(f)(iii) in the data: a vessel
308
+ whose passage is not to be impeded acquires no privilege by it. `role:none` is
287
309
  written rather than omitted, because a norm that confers nothing on a subject
288
- is a finding and not an absence: NUC against RAM is `none` on both sides, and
310
+ is a finding and not an absence: NUC against RAM is `role:none` on both sides, and
289
311
  that is Rule 18's partial order rather than a gap in the table.
290
312
 
291
- `keep-clear` is one role for the two duties 18(e) and 18(f)(i) impose together
313
+ `role:keep-clear` is one role for the two duties 18(e) and 18(f)(i) impose together
292
314
  — keep well clear, and avoid impeding navigation. The vocabulary cannot
293
315
  separate them and does not pretend to.
294
316
 
295
- ### Two-subject entry ids
317
+ ### Two-subject rule ids
296
318
 
297
- Entry ids stay citation-derived, exactly as below: `18a1` is 18(a)(i), `9c` is
298
- 9(c), `8f3` is 8(f)(iii). Where a paragraph's subject is disjunctive — 9(b) is
299
- "a vessel of less than 20 metres in length **or** a sailing vessel", and a
300
- `when` is a conjunction — the paragraph takes two entries and the suffix names
301
- the half: `9b-small` and `9b-sail`, `10j-small` and `10j-sail`. That is the
302
- same rule the `-m2`/`-mw`/`-anc` suffixes below follow: name what
303
- distinguishes them, in terms a reader with the rule text in front of them can
304
- find.
319
+ A two-subject entry is keyed on its paragraph like any other (below):
320
+ `rule:18a_i` is 18(a)(i), `rule:9c` is 9(c), `rule:8f_iii` is 8(f)(iii). No
321
+ subject segment appears in an id: every entry is evaluated from self's side,
322
+ and where the paragraph classifies the pair rather than one vessel the entry
323
+ reads both subjects inside one predicate — `rule:13b` is 13(b) whichever
324
+ vessel is coming up. Where a paragraph's subject is disjunctive — 9(b) is
325
+ "a vessel of less than 20 metres in length **or** a sailing vessel" —
326
+ `any_of` carries the disjunction inside one entry, `rule:9b`.
305
327
 
306
- ## Entry ids
328
+ ## Rule ids
307
329
 
308
- An entry id is derived from the paragraph path its entry cites, lowercased
309
- with the parentheses dropped and roman sub-paragraph numerals written as
310
- arabic digits:
330
+ An entry id is a paragraph key in the `rule:` namespace: `rule:` plus the
331
+ cite with its punctuation dropped. ADR 0015 (Solace, 2026-09-16) made the
332
+ change and carries the table from the ids it replaced; the rule for minting
333
+ a new one is here.
311
334
 
312
- | paragraph path | entry id |
313
- |---|---|
314
- | Rule 28 (one paragraph) | `28` |
315
- | `23(b)` | `23b` |
316
- | `25(d)(ii)` | `25d2` |
317
- | `23(a)(iii)`–`(iv)`, one entry | `23a34` |
318
-
319
- Where one paragraph produces more than one entry, a hyphenated suffix names
320
- what distinguishes them. The suffixes are **not** drawn from a single
321
- scheme, because the paragraphs they split do not divide on a single axis:
322
-
323
- | suffix | means | example |
335
+ | paragraph | rule id | why that id |
324
336
  |---|---|---|
325
- | `-m2` / `-m3` | two or three masthead lights | `24a-m2`, `24a-m3` |
326
- | `-rest` | the remainder of the rule's requirements once the split ones are taken out | `24a-rest` |
327
- | `-id` | the identity lights: the all-round group that says *what the vessel is* | `26b-id`, `27a-id`, `27b-id` |
328
- | `-mast` | the masthead light the paragraph adds on top of the identity lights | `26b-mast` |
329
- | `-mw` | the making-way half of a rule that lights differently when moving through the water | `26b-mw`, `27a-mw`, `27b-mw` |
330
- | `-gear` | the light indicating the direction of outlying gear | `26c-gear` |
331
- | `-anc` | the at-anchor branch | `27b-anc` |
332
- | `-anchor` / `-red` | 30(d)'s two halves: the anchor lights it requires, and the two red all-round lights of a vessel aground | `30d-anchor`, `30d-red` |
333
-
334
- This was reviewed and kept as it stands. The alternative — a uniform
335
- ordinal suffix, `27a-1` / `27a-2` — would be self-consistent and completely
336
- opaque: it tells a reader with the rule text in front of them nothing, in
337
- exchange for no gain to a machine, which only ever compares entry ids for
338
- equality. `24a-m2` / `24a-m3` are worth calling out in particular, because
339
- the two-or-three masthead split is stated in 24(a)(i) itself — the
340
- cardinality is in the law, not a modelling convenience of this package, and
341
- the suffix names something the reader can go and find.
337
+ | 30(a) | `rule:30a` | the bare slug of the cite: rule number, paragraph letter attached |
338
+ | 27(a)(i) | `rule:27a_i` | each roman subparagraph joined with `_` |
339
+ | 23(a)(iii)-(iv) | `rule:23a_iii_iv` | a span of subparagraphs, joined the same way |
340
+ | 24(a)(i), tow over 200 m | `rule:24a_i:exceeds_200m` | a further norm out of the same paragraph, named in the text's own words |
341
+ | 15(a), the duty | `rule:15a:keep_out_of_the_way` | which half of a fused deeming-and-duty sentence this entry carries |
342
+ | 30(a), US inland | `rule:30a:mooring_buoy` | a jurisdiction delta named by its difference; the jurisdiction stays a field |
343
+
344
+ A bare id is the paragraph's principal norm. A third segment is added only
345
+ where the text yields a second norm from the same paragraph, and it is named
346
+ in the words of the text, not in what the entry produces. A number may appear
347
+ there only when the Convention states the threshold itself (`exceeds_200m` is
348
+ 24(a)(i)'s).
349
+
350
+ **The id is opaque.** It looks like a citation and it is not one: a consumer
351
+ that wants "Rule 24(a)(i)" reads `cite`, and never splits an id to find a
352
+ paragraph. `cite` is the field that moves when the package reads the Rules
353
+ better — `14a` became `14b` once already — and the id resembling it is a
354
+ convenience for the human reading a trace, nothing the data promises. Two
355
+ entries may share a cite; they never share an id.
356
+
357
+ `represented_paragraphs` take the same prefix and the same shape
358
+ (`rule:2a` for 2(a)). They are not entries and nothing references them.
342
359
 
343
360
  ## Derived facts
344
361
 
@@ -393,11 +410,6 @@ implemented in the reference evaluator and asserted by the fixtures.
393
410
 
394
411
  ## What is not an identifier
395
412
 
396
- - **Modality values** (`shall`, `may`, `shall-if-practicable`,
397
- `conditional`, `exempt`) and **jurisdiction values** (`intl`,
398
- `us/inland`) are their own closed vocabularies, defined in §2 of the
399
- requirements and not part of the identifier space REQ-MODEL-10 binds. So are
400
- the **role** and **encounter** values of an effect.
401
413
  - **Constants** — `situation.constants` in `data/facts.json`: the numbers a
402
414
  Part B predicate needs and the Rules do not always give
403
415
  (`appreciable_bearing_change_deg_min`, `head_on_half_angle_deg`, the two
@@ -412,7 +424,7 @@ implemented in the reference evaluator and asserted by the fixtures.
412
424
  data is addressed by. Only the values inside them can be identifiers, and
413
425
  where they are (`also_activity` holds an activity value) they are prefixed.
414
426
  `not` and `any_of` are the second pair of words reserved at the head of an
415
- identifier space, after `own`/`other`/`pair`: they appear where a fact key
427
+ identifier space, after `self`/`other`/`pair`: they appear where a fact key
416
428
  appears, so no fact may ever be named either. The cost is the same and as
417
429
  cheap — every fact key carries a class prefix (`fact:`, `geo:`, `kin:`,
418
430
  `hist:`, `env:`) and neither word could be one.
@@ -0,0 +1,58 @@
1
+ # Maritime sources — case law and incident analysis
2
+
3
+ Decisions and commentary on how the Rules are conventionally read, and
4
+ collision forensics showing how they fail in the water.
5
+
6
+ A case is evidence that a reading is or is not conventional. It never becomes
7
+ a proposition in `docs/part-b-invariants.md`, which holds no maritime doctrine
8
+ by design. Consult these where a `Q-` in `docs/requirements.md` §11 turns on a
9
+ question the rule text leaves open.
10
+
11
+ ## Case law
12
+
13
+ - **Monford Management Ltd v Afina Navigation Ltd ("KIVELI" c/w "AFINA I") [2025] EWHC 1185 (Admlty)**. Bryan J, Admiralty Court; permission to appeal refused, [2025] EWHC 1210. When a Section II classification arms and how long it persists — `Q-51` and `Q-52`. Held: Rule 14 applies once risk of collision arises, not on geometry alone, and risk of collision was found on Rule 7(d)(i) steady bearing plus an unsafe CPA; once armed, the classification persists until the risk has passed, unaffected by later course changes. The court rejected the submission that a head-on at C-22 had become a crossing by C-6 as the bearing opened. Reaches Rule 13 by analogy only; silent on overtaking.
14
+ <https://caselaw.nationalarchives.gov.uk/ewhc/admlty/2025/1185> · case note by Nigel Cooper KC, counsel for AFINA I: <https://www.quadrantchambers.com/sites/default/files/2025-05/avoiding_a_head-on_collision_-_it_is_not_just_about_the_side_lights.pdf>
15
+ - **Evergreen Marine (UK) Ltd v Nautical Challenge Ltd ("Ever Smart" / "Alexandra 1") [2021] UKSC 6**. Frames Section II as a scheme about steady-bearing *collision* situations, with Rule 13 inside that taxonomy ([56]–[57]); leans against treating an engaged rule as inapplicable ([68]); describes Rule 17's obligations as qualified stages, predicates on the current state, with keep-course-and-speed accommodating manoeuvres such as slowing to pick up a pilot ([61]–[62]). Not asked when Rule 13 arms, and did not decide it. Separately, at [60] and [66]–[67], settles `Q-23`'s asymmetry: Rule 2(a) is a standing responsibility clause that authorises nothing, while Rule 2(b) is a conjunctive test — special circumstance *and* immediate danger — and rejects Rule 2 as a gap-filler for the steering rules.
16
+ <https://caselaw.nationalarchives.gov.uk/uksc/2021/6>
17
+ - **Crowley Marine Services Inc. v. Maritrans Inc., 447 F.3d 719 (9th Cir. 2006)**. `Q-23`: the burden of justifying a Rule 2(b) departure falls on the departing vessel, and the departure must respond to an immediate danger already created — a pre-emptive departure does not qualify (n.6).
18
+ <https://cdn.ca9.uscourts.gov/datastore/opinions/2006/05/08/0435724.pdf>
19
+
20
+ ## Commentary and guidance
21
+
22
+ - **Kemp — *When Do Collision Regulations Begin to Apply?* (Journal of Navigation)**. A judicial split on the antecedent question: some decisions hold the steering and sailing rules begin at risk of collision, others that they apply just before it, risk of collision being the thing to be avoided.
23
+ <https://www.cambridge.org/core/journals/journal-of-navigation/article/abs/when-do-collision-regulations-begin-to-apply/E6DBCD8A6ABC43FA88B5E6CB3ABF807C>
24
+ - **eCOLREGs — overtaking and crossing on the high seas**. States the broad reading of Rule 13(d) as conventional: an overtaking vessel "maintains overtaking status and cannot transition into a crossing or head-on situation until completely past and clear".
25
+ <https://advanced.ecolregs.com/index.php?option=com_k2&view=item&id=172>
26
+ - **Nautical Institute — *Action by the Stand-On Vessel*** (Seaways case study). The stand-on vessel's stages as taught; does not reach whether they are reversible.
27
+ <https://www.nautinst.org/resources-page/200115-action-by-the-stand-on-vessel.html>
28
+ - **USCG Navigation Rules (Amalgamated)**. <https://www.navcen.uscg.gov/navigation-rules-amalgamated>
29
+
30
+ ## Marine incident analysis
31
+
32
+ Collision forensics. Radar misinterpretation, mismatched turn decisions and
33
+ ambiguous give-way/stand-on roles are what the Rules are written against.
34
+
35
+ - Garzke, Simpson — *The Loss of Andrea Doria: A Marine Forensic Analysis* (Marine Technology Society Journal 46(6), 2012). Reconstructs the 1956 Andrea Doria–Stockholm collision from radar, navigation and rules-of-the-road evidence.
36
+ <https://www.ingentaconnect.com/content/mts/mtsj/2012/00000046/00000006/art00008> · <https://onepetro.org/JSPD/article/26/02/98/172277/The-Loss-of-Andrea-Doria-A-Marine-Forensic>
37
+ - British Wreck Commissioner (Lord Mersey) — *Report on the Loss of the Titanic* (1912). Excessive speed through a known ice field despite wireless ice warnings — a Rule 6 case, not give-way/stand-on.
38
+ <https://www.titanicinquiry.org/BOTInq/BOTReport/botRep01.php>
39
+ - Halpern — *Strangers on the Horizon: Titanic and Californian – A Forensic Approach* (2019). Reconstruction of the Titanic–Californian near-encounter: lookout, distress-signal and stand-on/give-way failures. Book only.
40
+ <https://www.amazon.com/STRANGERS-HORIZON-Californian-Forensic-Approach/dp/1702121984>
41
+ - MAIB (for the Isle of Man Ship Registry) — *Report on the investigation of
42
+ the collision between the bulk carrier Polesie and the general cargo ship
43
+ Verity* (Report No 5/2026, February 2026). German Bight TSS, 24 October
44
+ 2023; *Verity* sank with five fatalities. Analysis covers Rules 5, 6, 7, 8,
45
+ 15, 16 and 17(a)(ii)/(b) only — it does not reach Rule 2(b), correcting an
46
+ earlier claim that it paired 17(b) with 2(b) at closest quarters.
47
+ <https://www.bahamasmaritime.com/wp-content/uploads/2026/02/2026-5-Polesie-Verity-ReportAndAnnexes.pdf>
48
+ - IMO GISIS Marine Casualties and Incidents module. Not a paper but a source class: the mandatory-reporting database of marine safety investigation reports. Ground truth for real COLREGS-relevant incidents.
49
+ <https://www.imo.org/en/OurWork/IIIS/Pages/Marine-Safety-Investigation-reports.aspx>
50
+
51
+ ## Known gaps
52
+
53
+ - `Q-23`: *The Bywell Castle* and *Boy Andrew v St Rognvald* are unread, and no
54
+ case was found holding a Rule 2(b) departure justified on draught,
55
+ manoeuvrability, shoal water, a lee shore, set, visibility or sea state.
56
+ - Two standard texts — Cockcroft & Lameijer, *A Guide to the Collision Avoidance Rules*, and Farwell's *Rules of the Nautical Road* — are not online. Either may settle how the stages of a close-quarters encounter are divided, and whether they are treated as irreversible.
57
+ - Several of the questions in `docs/requirements.md` §11 appear unlitigated: overtaking geometry with no risk of collision, an overtaking situation becoming a head-on, resumption of course by a stand-on vessel that has acted, and the fate of accumulated Section II state across a visibility transition.
58
+ - BAILII refuses automated access. Use the National Archives Find Case Law service: <https://caselaw.nationalarchives.gov.uk/>
@@ -0,0 +1,103 @@
1
+ # Normative language — how "shall", "may" and friends are used here
2
+
3
+ Status: **ink**, 2026-09-05. Reviewed and accepted by the maintainer; this is the
4
+ standing decision until an ADR supersedes it.
5
+
6
+ ## The decision
7
+
8
+ Two vocabularies, kept apart on purpose:
9
+
10
+ 1. **Our own requirements** (`docs/requirements.md` here, and any spec in
11
+ this repo or in colregs-engine) use **MUST / MUST NOT / SHOULD / SHOULD NOT / MAY** in capitals,
12
+ with the meaning given by [RFC 2119] as clarified by [RFC 8174]: only
13
+ the capitalised words carry that meaning. Lower-case "must" or "should" in our prose is
14
+ ordinary English.
15
+ 2. **The data follows the Convention.** The `modality` field in
16
+ `data/applicability.json` holds a lower-case token derived from the
17
+ Convention's own verb for that paragraph: `shall`, `may`, `shall-not`,
18
+ `shall-if-practicable`, `shall-not-impede`, `conditional`, `exempt`.
19
+ These are *not* RFC 2119 keywords. They are normalised from treaty text,
20
+ and the verbatim paragraph sits next to them in `data/rules.json` so a
21
+ reader can check the token against the words. Two tokens are not verbs:
22
+ `conditional` means the verb itself turns on a fact, and the entry's
23
+ `modality_by` table says which verb applies when (Rule 23(a)(ii) is
24
+ `shall` at 50 m and above, `may` below); `exempt` means the paragraph
25
+ lifts a duty another paragraph imposes, named by `rel:exempts` (Rule
26
+ 30(e) for small vessels at anchor). Use `conditional` only when the
27
+ Convention states the threshold in the paragraph; use `exempt` only when
28
+ the paragraph's verb is "shall not be required" or equivalent.
29
+
30
+ So a capitalised MUST is a claim about the package. A lower-case `shall` in
31
+ the data is a claim about what COLREGS says. Nothing in the repo maps one
32
+ onto the other.
33
+
34
+ ## Where COLREGS will surprise an RFC reader
35
+
36
+ If you learned obligation words from RFCs, three things about the
37
+ Convention are counterintuitive. The data model follows the Convention,
38
+ not the RFC, on each.
39
+
40
+ - **There is no SHOULD tier.** RFC 2119 gives you a recommended-but-waivable
41
+ level. COLREGS uses "should" once in the Rules (8(b); the Annexes are not checked) and
42
+ nowhere defines it as a weaker rank of duty. Instead the Convention
43
+ **softens a duty with a condition on it, not with a weaker verb**:
44
+ "so far as possible", "if the circumstances of the case admit", "if
45
+ practicable". The data carries that as `shall-if-practicable`, a
46
+ qualified obligation, rather than inventing a `should`. One token covers
47
+ several phrasings ("so far as possible", "if the circumstances of the
48
+ case admit"); whether the exact qualifier deserves its own field beside
49
+ `modality` is question Q-31, still open.
50
+ - **`may` is a lawful alternative, not an optional extra.** In RFC 2119 a
51
+ MAY is something nobody may depend on. In COLREGS a `may` display is one
52
+ of several complete, lawful options, and the paragraph says how it
53
+ relates to the others. Rule 25(b) lets a small sailing vessel *combine*
54
+ the 25(a) lights into one masthead lantern, so it replaces 25(a) and the
55
+ data records that with `rel:in_lieu_of`. Rule 25(c) lets any sailing
56
+ vessel show red-over-green *in addition to* 25(a), so it is
57
+ `rel:includes`, and it may not be shown with the 25(b) lantern, so the
58
+ two `rel:excludes` each other. The data keeps every lawful option with
59
+ its own modality and never picks one.
60
+ - **`shall not impede` is its own kind of duty.** It has no RFC analogue.
61
+ It is weaker than "shall keep out of the way", and Rule 8(f) says the
62
+ other vessel keeps all her own duties too. Where a paragraph's verb is
63
+ "shall not impede" (Rules 9(b), 9(c), 10(i), 10(j)) that is its
64
+ `modality`; the same duty is also recorded as an `effect` on the vessel,
65
+ so a paragraph with a different verb can still impose it (Rule 18(d)(i)).
66
+
67
+ `shall not` (a prohibition) and `shall` (an obligation) mean what an RFC
68
+ reader expects.
69
+
70
+ ## What we defer to, and for what
71
+
72
+ | Question | Defer to |
73
+ |---|---|
74
+ | What MUST / SHOULD / MAY mean in our own specs | [RFC 2119], [RFC 8174]; the W3C's [RfcKeywords] page shows how other standards cite them |
75
+ | What `shall` / `may` / `shall not` mean in the data | The paragraph text in `data/rules.json`; no external standard redefines it |
76
+ | Which edition of COLREGS, and which amendments, the data encodes | `docs/adr/0001` and the provenance requirements (`REQ-PROV-*`); this note says nothing about editions |
77
+ | How standards bodies read `shall`/`should`/`may`/`can` in their own documents | [ISO/IEC Directives, Part 2], Clause 7. Not adopted here; listed because marine-standards readers will assume it |
78
+
79
+ The gap between ISO's `should` (a recommendation) and COLREGS's conditional
80
+ `shall` is exactly the first surprise above. None of this is legal advice:
81
+ the data records what the text says, not how a court would read it.
82
+
83
+ ## For reviewers
84
+
85
+ A pull request that adds or changes an entry is held to this note. Check:
86
+
87
+ - the `modality` token matches the paragraph's own verb, and a
88
+ practicability phrase ("so far as possible", "if the circumstances of the
89
+ case admit", "if practicable") becomes `shall-if-practicable`, never a
90
+ new `should`;
91
+ - a `may` entry says how it relates to the displays it is an alternative
92
+ to: `rel:in_lieu_of` only where the paragraph replaces another display,
93
+ `rel:includes` where it adds to one, `rel:excludes` where the two may not
94
+ be shown together;
95
+ - a "shall not impede" paragraph carries the `shall-not-impede` effect,
96
+ whatever its `modality`;
97
+ - new prose in `requirements.md` capitalises the RFC keywords it means and
98
+ leaves ordinary "must" and "should" in lower case.
99
+
100
+ [RFC 2119]: https://www.rfc-editor.org/rfc/rfc2119
101
+ [RFC 8174]: https://www.rfc-editor.org/rfc/rfc8174
102
+ [RfcKeywords]: https://www.w3.org/wiki/RfcKeywords
103
+ [ISO/IEC Directives, Part 2]: https://www.iso.org/sites/directives/current/part2/index.xhtml