colregs 0.3.0 → 0.3.1

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 (39) hide show
  1. package/README.md +8 -6
  2. package/data/applicability.json +219 -244
  3. package/data/facts.json +4 -4
  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 +1 -1
  13. package/docs/adr/0014-engine-interface-owned-by-colregs.md +103 -0
  14. package/docs/adr/0015-rule-ids-are-paragraph-keys.md +191 -0
  15. package/docs/budgets.json +5 -15
  16. package/docs/identifiers.md +49 -51
  17. package/docs/maritime-sources.md +58 -0
  18. package/docs/normative-language.md +103 -0
  19. package/docs/part-b-invariants.md +31 -29
  20. package/docs/requirements.md +80 -71
  21. package/fixtures/applicability-fixtures.json +190 -190
  22. package/fixtures/situation-fixtures.json +287 -287
  23. package/package.json +1 -1
  24. package/schema/applicability-fixtures.schema.json +4 -13
  25. package/schema/applicability.schema.json +27 -27
  26. package/schema/conduct-evaluation.schema.json +135 -0
  27. package/schema/display-evaluation.schema.json +146 -0
  28. package/schema/encounter-evaluation.schema.json +109 -0
  29. package/schema/evaluation.schema.json +149 -0
  30. package/schema/fact-record.schema.json +30 -0
  31. package/schema/i18n-catalog.schema.json +47 -0
  32. package/schema/operations.schema.json +124 -0
  33. package/schema/rule2-departure-finding.schema.json +72 -0
  34. package/schema/rule2-departure-model.schema.json +159 -0
  35. package/schema/situation-fixtures.schema.json +10 -146
  36. package/schema/situation.schema.json +72 -0
  37. package/schema/trace.schema.json +33 -0
  38. package/data/deprecated-identifiers.json +0 -7
  39. package/schema/deprecated-identifiers.schema.json +0 -29
@@ -0,0 +1,191 @@
1
+ # ADR 0015 — Rule ids are paragraph keys in the `rule:` namespace
2
+
3
+ Date: 2026-09-16
4
+ Status: accepted — Solace's ruling, 2026-09-16
5
+
6
+ ## Context
7
+
8
+ Issue #121 asked what an applicability entry should be called, and ruling C
9
+ was read as *names, not citations*. The first draft of this ADR wrote that
10
+ reading out: every id became a coined name in an `entry:` namespace —
11
+ `entry:anchored`, `entry:towing_mastheads_long_tow`, `entry:being_overtaken`.
12
+ It never landed and never shipped. Solace superseded that reading on
13
+ 2026-09-16 and this file is the replacement.
14
+
15
+ What the rename was buying was insulation: `cite` is a data field, revised
16
+ whenever the package reads the Rules better, and `14a` had already become
17
+ `14b` when the reading of *which paragraph deems a head-on* moved. An
18
+ identifier containing a data field gets renamed every time the field is.
19
+
20
+ What it cost is the vocabulary. An entry is a norm out of the Convention,
21
+ and the people who read these ids — and the consumers that store them
22
+ alongside a `cite` anyway — already have a name for every norm in the
23
+ package: the Rule. `rule:13b` is *Rule 13(b)* in the mariner's mouth. A
24
+ coined name is a second vocabulary beside that one, invented here,
25
+ unguessable from the Rules, and needing a lookup in this file to be read
26
+ back as the paragraph it came from. The insulation is worth one rename in
27
+ a pre-1.0 package; the second vocabulary is permanent. Ruled by Solace,
28
+ 2026-09-16.
29
+
30
+ ## Decision
31
+
32
+ 1. **The namespace noun is `rule:`.** It replaces `entry:` everywhere. The
33
+ overload with the Convention's Rules is the point, not an accident to
34
+ be designed around.
35
+ 2. **An id is `rule:<paragraph-slug>`.** The slug is the cite with its
36
+ punctuation dropped: rule number, paragraph letter attached, each roman
37
+ subparagraph joined by `_`. `27(a)(i)` → `27a_i`; `13(b)` → `13b`;
38
+ `11` → `11`; `23(a)(iii)-(iv)` → `23a_iii_iv`.
39
+ 3. **A bare id is the paragraph's principal norm.** Where the text yields
40
+ a further norm from the same paragraph, it takes a third segment after
41
+ a colon, named in the text's own words: `rule:24a_i` is 24(a)(i)'s two
42
+ masthead lights and `rule:24a_i:exceeds_200m` its three, the threshold
43
+ being the Convention's own, which is when a number may appear in a name.
44
+ 4. **Where one sentence fuses a deeming test and a duty, both norms are
45
+ named.** 15(a) gives `rule:15a:crossing` — the classification, effect
46
+ `encounter: crossing` — and `rule:15a:keep_out_of_the_way` — the
47
+ precedence, own give-way. `avoid_crossing_ahead` is reserved for 15(a)'s
48
+ second duty when the `conduct` shape exists; it is not in use.
49
+ 5. **A Convention subparagraph keeps its own cite.** 30(d)'s chapeau is
50
+ `rule:30d`, citing `30(d)`, importing the 30(a)/(b) anchor lights,
51
+ modality `shall`; the two all-round red lights are `rule:30d_i`, citing
52
+ `30(d)(i)`, modality `shall-if-practicable` because the paragraph says
53
+ "if practicable". `30(d)(i)` joins `rules.json` as a skeleton path with
54
+ this change. `rule:30d_ii` is reserved for the three balls, and lands
55
+ when day shapes do.
56
+ 6. **A jurisdiction override is named by its difference**, and jurisdiction
57
+ stays a field: `rule:30a:mooring_buoy` and `rule:30b:mooring_buoy`, both
58
+ `us/inland`. The name says what 33 CFR 90.5 adds, not who adds it.
59
+ 7. **13(b) is one symmetric entry.** The two entries that preceded it were
60
+ identical but for which subject's `geo:rel_bearing_deg` fell in
61
+ (112.5, 247.5), and both produced `encounter: overtaking`. `rule:13b`
62
+ reads the same sector object on either subject under `any_of` — the
63
+ pattern 13(d) already used over `hist:was_overtaking` — because the
64
+ encounter type belongs to the pair. One paragraph, one norm, one id.
65
+ 8. **Every other entry with a unique cite takes the bare slug of it.**
66
+ 9. **`represented_paragraphs` ids take the prefix too** — `2a` becomes
67
+ `rule:2a` — and keep their own list. They no longer need a schema
68
+ definition of their own: the id pattern is the same one.
69
+ 10. **The schema pattern is**
70
+ `^rule:[0-9]+[a-z]?(_[ivx]+)*(:[a-z][a-z0-9_]*)?$`.
71
+ **An id is opaque to a consumer.** It looks like a citation and is not
72
+ one: a consumer that wants the paragraph reads `cite`, and never splits
73
+ the id to find it. The resemblance is for the human reading a trace;
74
+ the `cite` field is the machine-readable link to the Convention, and it
75
+ is the field that moves when the package's reading of a paragraph moves.
76
+ 11. **The identifier-diff mechanism is removed, not left dormant.**
77
+ `retired_entry_ids`, `data/deprecated-identifiers.json`, its schema, the
78
+ dormant diff test and the retired-id half of the REQ-MODEL-10 collision
79
+ test all go. Nothing pre-1.0 is immutable, so there was nothing for the
80
+ registry to hold and no diff for the test to run; a mechanism that
81
+ cannot fire is a mechanism nobody maintains. It returns, if at all, with
82
+ the 1.0.0 tag that REQ-MODEL-10's baseline names. Ruled by Solace,
83
+ 2026-09-16.
84
+
85
+ ## The names
86
+
87
+ `was` is the `entry:` name from the superseded draft of this ADR, which
88
+ shipped in no release. The citation-derived ids that preceded *it* —
89
+ `24a-m2`, `23a1`, `30d-red` — were never names either and are not carried
90
+ forward.
91
+
92
+ | was | is | cite |
93
+ |---|---|---|
94
+ | `entry:power_forward_masthead` | `rule:23a_i` | 23(a)(i) |
95
+ | `entry:power_second_masthead` | `rule:23a_ii` | 23(a)(ii) |
96
+ | `entry:power_sidelights_sternlight` | `rule:23a_iii_iv` | 23(a)(iii)-(iv) |
97
+ | `entry:air_cushion_non_displacement` | `rule:23b` | 23(b) |
98
+ | `entry:wig_near_surface` | `rule:23c` | 23(c) |
99
+ | `entry:power_under_12m` | `rule:23d_i` | 23(d)(i) |
100
+ | `entry:power_under_7m_slow` | `rule:23d_ii` | 23(d)(ii) |
101
+ | `entry:towing_mastheads` | `rule:24a_i` | 24(a)(i) |
102
+ | `entry:towing_mastheads_long_tow` | `rule:24a_i:exceeds_200m` | 24(a)(i) |
103
+ | `entry:towing_light` | `rule:24a_ii_iv` | 24(a)(ii)-(iv) |
104
+ | `entry:pushing_composite_unit` | `rule:24b` | 24(b) |
105
+ | `entry:pushing_ahead_or_alongside` | `rule:24c` | 24(c) |
106
+ | `entry:being_towed` | `rule:24e` | 24(e) |
107
+ | `entry:sail_sidelights_sternlight` | `rule:25a` | 25(a) |
108
+ | `entry:sail_combined_lantern` | `rule:25b` | 25(b) |
109
+ | `entry:sail_red_over_green` | `rule:25c` | 25(c) |
110
+ | `entry:sail_under_7m` | `rule:25d_i` | 25(d)(i) |
111
+ | `entry:under_oars` | `rule:25d_ii` | 25(d)(ii) |
112
+ | `entry:trawling_green_over_white` | `rule:26b_i` | 26(b)(i) |
113
+ | `entry:trawling_masthead` | `rule:26b_ii` | 26(b)(ii) |
114
+ | `entry:trawling_making_way` | `rule:26b_iii` | 26(b)(iii) |
115
+ | `entry:fishing_red_over_white` | `rule:26c_i` | 26(c)(i) |
116
+ | `entry:fishing_outlying_gear` | `rule:26c_ii` | 26(c)(ii) |
117
+ | `entry:fishing_making_way` | `rule:26c_iii` | 26(c)(iii) |
118
+ | `entry:nuc_red_over_red` | `rule:27a_i` | 27(a)(i) |
119
+ | `entry:nuc_making_way` | `rule:27a_iii` | 27(a)(iii) |
120
+ | `entry:ram_red_white_red` | `rule:27b_i` | 27(b)(i) |
121
+ | `entry:ram_making_way` | `rule:27b_iii` | 27(b)(iii) |
122
+ | `entry:ram_anchored` | `rule:27b_iv` | 27(b)(iv) |
123
+ | `entry:ram_underwater_obstruction` | `rule:27d` | 27(d) |
124
+ | `entry:diving` | `rule:27e_i` | 27(e)(i) |
125
+ | `entry:mine_clearance` | `rule:27f` | 27(f) |
126
+ | `entry:cbd` | `rule:28` | 28 |
127
+ | `entry:pilot` | `rule:29a` | 29(a) |
128
+ | `entry:anchored` | `rule:30a` | 30(a) |
129
+ | `entry:anchored_under_50m` | `rule:30b` | 30(b) |
130
+ | `entry:anchored_deck_lights` | `rule:30c` | 30(c) |
131
+ | `entry:aground_anchor_lights` | `rule:30d` | 30(d) |
132
+ | `entry:aground_red_over_red` | `rule:30d_i` | 30(d)(i) |
133
+ | `entry:anchored_under_7m_not_near_channel` | `rule:30e` | 30(e) |
134
+ | `entry:any_visibility` | `rule:4` | 4 |
135
+ | `entry:in_sight` | `rule:11` | 11 |
136
+ | `entry:restricted_visibility` | `rule:19a` | 19(a) |
137
+ | `entry:power_gives_way_to_nuc` | `rule:18a_i` | 18(a)(i) |
138
+ | `entry:power_gives_way_to_ram` | `rule:18a_ii` | 18(a)(ii) |
139
+ | `entry:power_gives_way_to_fishing` | `rule:18a_iii` | 18(a)(iii) |
140
+ | `entry:power_gives_way_to_sail` | `rule:18a_iv` | 18(a)(iv) |
141
+ | `entry:sail_gives_way_to_nuc` | `rule:18b_i` | 18(b)(i) |
142
+ | `entry:sail_gives_way_to_ram` | `rule:18b_ii` | 18(b)(ii) |
143
+ | `entry:sail_gives_way_to_fishing` | `rule:18b_iii` | 18(b)(iii) |
144
+ | `entry:fishing_gives_way_to_nuc` | `rule:18c_i` | 18(c)(i) |
145
+ | `entry:fishing_gives_way_to_ram` | `rule:18c_ii` | 18(c)(ii) |
146
+ | `entry:avoid_impeding_cbd` | `rule:18d_i` | 18(d)(i) |
147
+ | `entry:wig_keeps_well_clear` | `rule:18f_i` | 18(f)(i) |
148
+ | `entry:not_impeded_remains_obliged` | `rule:8f_iii` | 8(f)(iii) |
149
+ | `entry:overtaking_gives_way` | `rule:13a` | 13(a) |
150
+ | `entry:narrow_channel_small_or_sail` | `rule:9b` | 9(b) |
151
+ | `entry:narrow_channel_fishing` | `rule:9c` | 9(c) |
152
+ | `entry:traffic_lane_fishing` | `rule:10i` | 10(i) |
153
+ | `entry:traffic_lane_small_or_sail` | `rule:10j` | 10(j) |
154
+ | `entry:steady_bearing` | `rule:7d_i` | 7(d)(i) |
155
+ | `entry:sail_port_tack_gives_way` | `rule:12a_i` | 12(a)(i) |
156
+ | `entry:sail_windward_gives_way` | `rule:12a_ii` | 12(a)(ii) |
157
+ | `entry:sail_port_tack_uncertain_gives_way` | `rule:12a_iii` | 12(a)(iii) |
158
+ | `entry:overtaking` | `rule:13b` | 13(b) |
159
+ | `entry:being_overtaken` | `rule:13b` | 13(b) |
160
+ | `entry:overtaking_until_past_and_clear` | `rule:13d` | 13(d) |
161
+ | `entry:head_on` | `rule:14b` | 14(b) |
162
+ | `entry:crossing` | `rule:15a:crossing` | 15(a) |
163
+ | `entry:crossing_gives_way` | `rule:15a:keep_out_of_the_way` | 15(a) |
164
+ | `entry:mooring_buoy` | `rule:30a:mooring_buoy` | 30(a) |
165
+ | `entry:mooring_buoy_under_50m` | `rule:30b:mooring_buoy` | 30(b) |
166
+
167
+ ## Consequences
168
+
169
+ - One pass, one commit: `data/applicability.json`, both fixture files,
170
+ `data/geometry.json`, `data/images.json`, `data/facts.json`,
171
+ `data/rules.json`, the schemas, the suite and the living docs. The
172
+ schema `$defs` that named the type follow the noun: `entryId` is
173
+ `ruleId`, `entryIdList` is `ruleIdList`.
174
+ - Two entries may still share a cite — `rule:24a_i` and
175
+ `rule:24a_i:exceeds_200m`, the two halves of 15(a), the two mooring-buoy
176
+ deltas. What they never share is an id, and the third segment says which
177
+ norm out of the paragraph the entry carries.
178
+ - Earlier ADRs and closed questions cite entries by the superseded names;
179
+ the table above is the map and stays in this file permanently. ADR 0006
180
+ carries a dated note that its identifier-diff half is gone.
181
+ - `represented_paragraphs` is now a second list of records that look like
182
+ entries, differing only in carrying no `when`. Folding it into `entries`
183
+ with a `care`/`meta` category is the obvious follow-up and is not done
184
+ here.
185
+ - Open, and not ruled: whether `fact:rule18_class` should be renamed. It
186
+ carries a rule number inside a fact name, which is the shape this ADR
187
+ moved *entry* ids towards and says nothing about for facts. Left visibly
188
+ open.
189
+ - An encounter with more than two vessels is issue #135; nothing in this
190
+ scheme is aimed at it, and a third subject would need a segment none of
191
+ these ids have.
package/docs/budgets.json CHANGED
@@ -2,6 +2,7 @@
2
2
  "$comment": "Prose budgets enforced by prose-budget (the engine in dotfiles .local/bin), run locally by npm test and in CI by the shared workflow. Raising a number or adding an exception is a deliberate, reviewable diff.",
3
3
  "lines": {
4
4
  "README.md": 360,
5
+ "docs/normative-language.md": 130,
5
6
  "AGENTS.md": 200,
6
7
  "CLAUDE.md": 10,
7
8
  "docs/requirements.md": 1500,
@@ -18,7 +19,10 @@
18
19
  "required": true,
19
20
  "pending": [],
20
21
  "docs/adr/0013-corpus-files-with-editions.md": 95,
21
- "docs/timeline.md": 130
22
+ "docs/adr/0014-engine-interface-owned-by-colregs.md": 110,
23
+ "docs/adr/0015-rule-ids-are-paragraph-keys.md": 210,
24
+ "docs/timeline.md": 130,
25
+ "docs/maritime-sources.md": 130
22
26
  },
23
27
  "json_prose": {
24
28
  "targets": [
@@ -55,14 +59,10 @@
55
59
  "data/applicability.json#/entries/62/note",
56
60
  "data/applicability.json#/entries/63/note",
57
61
  "data/applicability.json#/entries/64/note",
58
- "data/applicability.json#/entries/64/gap",
59
62
  "data/applicability.json#/entries/65/note",
60
- "data/applicability.json#/entries/65/gap",
61
63
  "data/applicability.json#/entries/66/note",
62
64
  "data/applicability.json#/entries/67/note",
63
65
  "data/applicability.json#/entries/68/note",
64
- "data/applicability.json#/entries/68/gap",
65
- "data/applicability.json#/entries/69/note",
66
66
  "data/facts.json#/derived/note",
67
67
  "data/facts.json#/derived/fact:rule18_class/note",
68
68
  "data/facts.json#/derived/fact:rule18_class/decode/1/note",
@@ -134,16 +134,6 @@
134
134
  ]
135
135
  },
136
136
  "grandfathered": [
137
- {
138
- "file": "data/applicability.json",
139
- "hash": "447b3984d57b",
140
- "match": "PR #24"
141
- },
142
- {
143
- "file": "data/applicability.json",
144
- "hash": "5ff9d39319b8",
145
- "match": "PR #24"
146
- },
147
137
  {
148
138
  "file": "docs/conventions.md",
149
139
  "hash": "d1f1ed997431",
@@ -17,10 +17,12 @@ 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.
24
26
 
25
27
  **Vocabulary identifiers carry a type prefix.** These names are this
26
28
  package's own — nothing in COLREGS calls anything `masthead` or `nuc`. They
@@ -35,6 +37,7 @@ resolves that collision by construction rather than by convention.
35
37
 
36
38
  | form | class | examples |
37
39
  |---|---|---|
40
+ | `rule:<paragraph-slug>` | applicability entries (`data/applicability.json`) | `rule:30a`, `rule:24a_i:exceeds_200m`, `rule:15a:keep_out_of_the_way` |
38
41
  | `light:<id>` | light definitions (`data/lights.json`) | `light:masthead`, `light:sidelight_starboard`, `light:all_round` |
39
42
  | `fact:<key>` | fact keys — the input vocabulary (`data/facts.json`) | `fact:activity`, `fact:length_m`, `fact:making_way`, `fact:on_mooring_buoy` |
40
43
  | `<fact>:<value>` | values of an enumerated fact | `activity:nuc`, `position:anchored`, `propulsion:sail`, `obstruction_side:port` |
@@ -257,21 +260,21 @@ of them; the departure from the table is recorded in ADR 0005's pencil log and
257
260
  in `Q-40`.
258
261
 
259
262
  **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
263
+ which is 3(c), so the three Rule 12 entries gate on `fact:propulsion` and not on the Rule
261
264
  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`
265
+ ranks the pair, its entry displaces Rule 12's: the three 18(b) entries and the two 18(c) entries
263
266
  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
267
+ except Rules 9, 10 and 13 and nothing else. `rule:13a` overrides them for the same
265
268
  reason it overrides Rule 18 — 13(a) is "notwithstanding" the rest of Sections
266
269
  I and II. The test that pins the relation asserts both reasons from
267
270
  `rules.json`, so the data cannot keep an override after losing the words.
268
271
 
269
272
  **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
273
+ vessels", which is 3(b), so `rule:15a:keep_out_of_the_way` gates on `fact:propulsion` and on
271
274
  no Rule 18 rank either — a vessel engaged in fishing, or not under command,
272
275
  whose machinery is in use is a power-driven vessel. It used to negate the four
273
276
  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
277
+ could not see the reason; the 18(a)(i)–(iii) entries, the two 18(c) entries and `rule:18f_i` now carry
275
278
  `rel:overrides` against it instead. The derived half of the test is what makes
276
279
  that checkable: a Rule 18 entry meets Rule 15 when it assigns a helm role and
277
280
  neither subject is gated to a sailing vessel, and every such entry must carry
@@ -292,53 +295,48 @@ that is Rule 18's partial order rather than a gap in the table.
292
295
  — keep well clear, and avoid impeding navigation. The vocabulary cannot
293
296
  separate them and does not pretend to.
294
297
 
295
- ### Two-subject entry ids
298
+ ### Two-subject rule ids
296
299
 
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.
300
+ A two-subject entry is keyed on its paragraph like any other (below):
301
+ `rule:18a_i` is 18(a)(i), `rule:9c` is 9(c), `rule:8f_iii` is 8(f)(iii). No
302
+ subject segment appears in an id: every entry is evaluated from own's side,
303
+ and where the paragraph classifies the pair rather than one vessel the entry
304
+ reads both subjects inside one predicate — `rule:13b` is 13(b) whichever
305
+ vessel is coming up. Where a paragraph's subject is disjunctive — 9(b) is
306
+ "a vessel of less than 20 metres in length **or** a sailing vessel" —
307
+ `any_of` carries the disjunction inside one entry, `rule:9b`.
305
308
 
306
- ## Entry ids
309
+ ## Rule ids
307
310
 
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:
311
+ An entry id is a paragraph key in the `rule:` namespace: `rule:` plus the
312
+ cite with its punctuation dropped. ADR 0015 (Solace, 2026-09-16) made the
313
+ change and carries the table from the ids it replaced; the rule for minting
314
+ a new one is here.
311
315
 
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 |
316
+ | paragraph | rule id | why that id |
324
317
  |---|---|---|
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.
318
+ | 30(a) | `rule:30a` | the bare slug of the cite: rule number, paragraph letter attached |
319
+ | 27(a)(i) | `rule:27a_i` | each roman subparagraph joined with `_` |
320
+ | 23(a)(iii)-(iv) | `rule:23a_iii_iv` | a span of subparagraphs, joined the same way |
321
+ | 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 |
322
+ | 15(a), the duty | `rule:15a:keep_out_of_the_way` | which half of a fused deeming-and-duty sentence this entry carries |
323
+ | 30(a), US inland | `rule:30a:mooring_buoy` | a jurisdiction delta named by its difference; the jurisdiction stays a field |
324
+
325
+ A bare id is the paragraph's principal norm. A third segment is added only
326
+ where the text yields a second norm from the same paragraph, and it is named
327
+ in the words of the text, not in what the entry produces. A number may appear
328
+ there only when the Convention states the threshold itself (`exceeds_200m` is
329
+ 24(a)(i)'s).
330
+
331
+ **The id is opaque.** It looks like a citation and it is not one: a consumer
332
+ that wants "Rule 24(a)(i)" reads `cite`, and never splits an id to find a
333
+ paragraph. `cite` is the field that moves when the package reads the Rules
334
+ better — `14a` became `14b` once already — and the id resembling it is a
335
+ convenience for the human reading a trace, nothing the data promises. Two
336
+ entries may share a cite; they never share an id.
337
+
338
+ `represented_paragraphs` take the same prefix and the same shape
339
+ (`rule:2a` for 2(a)). They are not entries and nothing references them.
342
340
 
343
341
  ## Derived facts
344
342
 
@@ -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