colregs 0.2.0 → 0.2.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.
@@ -76,7 +76,9 @@ Neither consumer lives in this repo.
76
76
  (Rule 28 "[Reserved]") means silence-means-inherit would apply
77
77
  international law where the national body deliberately has none, so no
78
78
  non-`intl` jurisdiction lands before an explicit suppression mechanism
79
- exists.
79
+ exists. A delta that only *adds* entries is exempt: it suppresses nothing,
80
+ so the Q-11 hazard cannot arise from it (ADR 0008, `30a-buoy`/`30b-buoy`).
81
+ A delta that suppresses or replaces an `intl` entry still waits.
80
82
  - **REQ-SCOPE-4** — Adding a jurisdiction MUST be additive. It MUST NOT require
81
83
  a schema change or edits to existing `intl` entries.
82
84
  - **REQ-SCOPE-5** — Geography that gates a rule (Great Lakes, Western Rivers,
@@ -93,7 +95,10 @@ Part C (Rules 20–31, lights and shapes) is v1. The structure MUST accommodate
93
95
  the rest without redesign.
94
96
 
95
97
  - **REQ-PART-1** — Part C lights MUST be complete for `intl` before any other
96
- part or jurisdiction is added.
98
+ part or jurisdiction is added, except a purely additive jurisdiction delta
99
+ recorded in an ADR — one that adds entries and edits or suppresses none
100
+ (ADR 0008). Such a delta is not a claim to model that jurisdiction, and
101
+ REQ-SCOPE-6's coverage statement MUST say what it does and does not cover.
97
102
  - **REQ-PART-2** — Day shapes MUST use the same entry model as lights, differing
98
103
  only in the fixture vocabulary they emit.
99
104
  - **REQ-PART-3** — Sound and light signals (Part D, Rules 32–37) SHOULD be
@@ -133,8 +138,11 @@ Four layers, each independently addressable.
133
138
  `activity:pilot` / `activity:diving`
134
139
  - `fact:position` ∈ `position:underway` / `position:anchored` /
135
140
  `position:aground` / `position:moored`
136
- plus `fact:making_way` as a boolean refining `fact:position=position:underway`,
137
- and numeric and boolean facts (`fact:length_m`, `fact:tow_length_m`,
141
+ plus `fact:making_way` as a boolean refining `fact:position=position:underway`
142
+ and `fact:on_mooring_buoy` as a boolean refining
143
+ `fact:position=position:moored` — made fast to a buoy, not to the shore;
144
+ the Convention prescribes lights for neither, and only a jurisdiction that
145
+ deems her at anchor reads it (ADR 0008) — and numeric and boolean facts (`fact:length_m`, `fact:tow_length_m`,
138
146
  `fact:max_speed_kn`, `fact:composite_unit`, and the education-only facts).
139
147
  Fact keys, and the values of the enumerated facts, carry a type prefix;
140
148
  `docs/identifiers.md` states the scheme and why citation-derived
@@ -151,7 +159,10 @@ Four layers, each independently addressable.
151
159
  - **REQ-MODEL-7** — Five relations MUST be supported:
152
160
  - `rel:includes` — import another entry's **lights only**, never its predicate;
153
161
  - `rel:in_lieu_of` — legal alternatives for the same fact record;
154
- - `rel:excludes` — mutual exclusion, including across rules;
162
+ - `rel:excludes` — mutual exclusion, including across rules; symmetric
163
+ (A excludes B implies B excludes A), and no participating entry MAY be
164
+ forceful (`shall`/`shall-if-practicable`) — a directed "this one
165
+ prevails" is `rel:overrides` (ADR 0007), not `rel:excludes`;
155
166
  - `rel:exempts` — one entry lifting another's obligation;
156
167
  - `rel:conditional_includes` — import or alternatives, gated on a predicate.
157
168
  The five are not interchangeable; README.md holds the working semantics.
@@ -364,7 +375,52 @@ towards, not because the shape is settled.
364
375
  whose threshold does not match the declared constant. `✎` pencil, with the
365
376
  rest of §4.1.
366
377
 
367
- ---
378
+ ### 4.2 Part B invariants
379
+
380
+ `docs/part-b-invariants.md` states what COLREGS requires of a *trace* of
381
+ situations, where an entry says which norms one state selects; it is a separate
382
+ document because its propositions are about COLREGS, not requirements on this
383
+ package, and would be misread under a `REQ-` id and an RFC 2119 MUST. Everything
384
+ here is **pencil** (`docs/conventions.md`) with §4.1.
385
+
386
+ - **REQ-INV-1** — Part B's steering and sailing rules MUST be recorded as prose
387
+ invariants in `docs/part-b-invariants.md`, each with a stable identifier,
388
+ each citing the paragraph path it comes from, and each precise enough that a
389
+ formalisation can be written from it without returning to the rule text.
390
+ - **REQ-INV-2** — An invariant identifier MUST be `INV-` followed by the
391
+ entry-id derivation of its paragraph path (`docs/identifiers.md`, "Entry
392
+ ids"), with a descriptive hyphenated suffix where one paragraph yields more
393
+ than one invariant. A suffix MUST name what distinguishes the invariant, and
394
+ MUST NOT be an ordinal. Identifiers MUST be stable and MUST NOT be reused; a
395
+ withdrawn invariant is struck through and kept, as a requirement is. Once a
396
+ formal specification cites an invariant identifier, its **Invariant.**
397
+ statement MUST NOT change in place; a changed statement is a new identifier
398
+ and the old one is struck through and kept.
399
+ - **REQ-INV-3** — Every paragraph path in `data/rules.json` within the range
400
+ the document covers MUST appear exactly once in its coverage table, mapped
401
+ either to an invariant identifier or to an explicit exclusion with a reason.
402
+ CI MUST fail on a paragraph in range that appears in neither, on one that
403
+ appears twice, and on an invariant whose citation does not resolve in
404
+ `data/rules.json`. Enforced by `test/data.test.mjs`.
405
+ - **REQ-INV-4** — An invariant that is temporal MUST state what state must be
406
+ remembered and over what window. "Temporal" means it relates two or more
407
+ states of a trace; a property of a single state MUST say so.
408
+ - **REQ-INV-5** — Where the rule text admits two readings, the document MUST
409
+ record both and MUST NOT choose. The choice is an open question in §11 and is
410
+ the maintainer's; a session MAY argue for a reading, and MUST NOT resolve one
411
+ by writing only its preferred half down. A proposition that is neither the
412
+ rule text, arithmetic over the rule text, nor a decision recorded as such
413
+ MUST NOT appear as an invariant.
414
+ - **REQ-INV-6** — An invariant that covers its paragraph while leaving a term
415
+ the Rules do not define MUST name that term in the document's list of
416
+ undetermined terms, so that "covered" is never read as "checkable". A numeric
417
+ stand-in for such a term MUST be a declared constant under
418
+ `situation.constants` (`REQ-CAT-9`), never a literal inside an invariant.
419
+ - **REQ-INV-7** — An invariant's **Invariant.** paragraph MUST be at most 120
420
+ words. This is a readability bound on the normative statement itself, not a
421
+ document-length budget (`docs/budgets.json` governs those); it keeps a single
422
+ invariant from growing into a paragraph a formalisation can't be checked
423
+ against sentence-by-sentence. Enforced by `test/data.test.mjs`.
368
424
 
369
425
  ## 5. Languages and localization
370
426
 
@@ -1318,3 +1374,76 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
1318
1374
  is closed — the definition a consumer would need is now in the data — and
1319
1375
  the relative quantities as derived facts stays open in the block's
1320
1376
  `settled_by`.
1377
+
1378
+ - **Q-49** — **Must `13d`'s effect cross the Section II/III boundary for Rule 19(d)(i) to be expressible?**
1379
+ - *broad* — drop `13d`'s `pair:geo:in_sight` gate so `encounter: overtaking` is visible in Section III; breaks the scope invariant.
1380
+ - *narrow* — `13d` stays gated; a 19(d)(i) entry reads `own:hist:was_overtaking`/`other:hist:was_overtaking` directly, as `14b` and `15a-crossing` already do.
1381
+ - Default: narrow (nothing changes).
1382
+ - Recommendation: narrow — the test named below shows the fact resolves out of sight
1383
+ (`Q-49: hist:was_overtaking resolves out of sight; 13d does not fire`, `test/data.test.mjs`).
1384
+
1385
+ ### From the Part B invariants (P4.1)
1386
+
1387
+ Eight places where the rule text admits two readings, recorded here under
1388
+ `REQ-INV-5`. Each is the maintainer's, and each names the data's default so
1389
+ nothing is blocked while open.
1390
+
1391
+ - **Q-50** — **Does risk of collision gate Rules 13 and 18?** 14(a) and 15(a)
1392
+ say "so as to involve risk of collision"; Rule 13 and Rule 18 do not.
1393
+ *Wide:* they attach whenever in sight and the geometry holds — Rule 11 is the
1394
+ only gate Section II states — so a vessel overtaking a mile clear abeam is
1395
+ give-way and Rule 16 binds her. *Narrow:* Section II's duties attach only with
1396
+ risk of collision; 14 and 15 are emphasis. Default **wide**: `13a`, `13b-*`,
1397
+ `13d` and every Rule 18 entry read `in_sight` only. No recommendation.
1398
+ - **Q-51** — **What arms 13(d)'s latch?** *A:* 13(b)'s deeming, at the first
1399
+ state the geometry holds. *B:* 13(a)'s duty actually attaching. They differ
1400
+ where the geometry holds but a condition on 13(a) does not — `Q-50`'s
1401
+ surface. Default **A**: `hist:was_overtaking` is "was, at some earlier point
1402
+ in this encounter, an overtaking vessel" and `13b-overtaking` is geometry.
1403
+ No recommendation; downstream of `Q-50`.
1404
+ - **Q-52** — **What does 13(d)'s latch forbid?** *Narrow:* reclassification to
1405
+ *crossing* only, as the paragraph says, leaving head-on to Rule 14 on the
1406
+ geometry of the moment. *Broad:* the encounter stays an overtaking and no
1407
+ other Section II classification attaches. Both preserve the duty; they differ
1408
+ on encounter type, which Rule 17's phases and 14(a) hang off. Default
1409
+ **broad**: `13d` yields `encounter: overtaking` from history alone, `14b` and
1410
+ `15a-crossing` gate on `was_overtaking: false`. No recommendation.
1411
+ - **Q-53** — **Does 17(a)(ii) suspend 17(a)(i)'s duty, or add an exception?**
1412
+ *Suspension:* "may, however" lifts the duty once non-compliance is apparent;
1413
+ a monitor then flags nothing. *Exception:* the duty stands and a departure is
1414
+ lawful only as action to avoid collision by her manoeuvre alone; a monitor
1415
+ flags any other alteration. No default (Rule 17 has no entry). Recommend
1416
+ **exception**: 17(a)(ii) describes an action, not a proviso, and leaves a
1417
+ monitor something to check — a drafting argument, not a source.
1418
+ - **Q-54** — **Are Rule 17's phases monotone?** *Latching:* one three-valued
1419
+ monotone latch per stand-on vessel per encounter; late compliance does not
1420
+ take the permission away. *Re-evaluating:* each phase is a predicate on the
1421
+ current state and the vessel may fall back — and must she then hold her *new*
1422
+ course and speed? TLC distinguishes them on a four-state trace, so settle
1423
+ before P4.2. No default. Recommend **latching**: one late alteration does not
1424
+ restore reliance — an argument from purpose, not a source.
1425
+ - **Q-55** — **What does a visibility transition do to Section II state?**
1426
+ Rule 11 and 19(a) switch on the current state; no paragraph says what
1427
+ becomes of a 13(d) latch or a Rule 17 phase. *Persisting:* they belong to the
1428
+ encounter and survive the fog. *Resetting:* Section II starts afresh on the
1429
+ geometry when sight is regained — the reclassification 13(d) forbids, via
1430
+ visibility. No default (nothing is temporal). Recommend **persisting**, which
1431
+ `INV-13d`'s "same encounter" is written for; likeliest surprising TLC trace.
1432
+ - **Q-56** — **The third visibility state: a hole, or closed by the model?**
1433
+ Not in sight *and* not in or near restricted visibility (clear weather, beyond
1434
+ visual range, radar contact) is outside Rule 11 and 19(a): Section I only.
1435
+ *Hole:* represent it; a fact for "in or near restricted visibility" is owed.
1436
+ *Closed:* treat not-in-sight as Section III. Default **closed**: `19a` drops
1437
+ the second conjunct, recorded as a `gap`. Recommend **hole**: the default is
1438
+ safe for a switching consumer and unsafe for a traceability claim.
1439
+ - **Q-57** — **The baseline for "keep her course and speed"?** 17(a)(i) fixes
1440
+ no instant. *Attachment:* course and speed when the role attached; any later
1441
+ change is a departure — checkable, occasionally absurd. *Steady state:* the
1442
+ vessel's settled condition, so a turn in progress may complete — seamanlike,
1443
+ and needs a definition of "settled" the Rules lack. No default. Recommend
1444
+ **attachment**, with the departure tolerance an explicit monitor parameter.
1445
+
1446
+ Two decisions taken in pencil, reversible in one edit: the invariants live in
1447
+ their own document under `REQ-INV-1`–`REQ-INV-7` (§4.2 says why), and the id
1448
+ scheme is `REQ-INV-2`'s — cheap to change until P4.2 cites an id from a TLA+
1449
+ module, expensive after.
@@ -190,6 +190,47 @@
190
190
  "30d-anchor"
191
191
  ]
192
192
  },
193
+ {
194
+ "name": "fishing, aground, 30 m",
195
+ "facts": {
196
+ "fact:propulsion": "propulsion:power",
197
+ "fact:activity": "activity:fishing",
198
+ "fact:position": "position:aground",
199
+ "fact:length_m": 30
200
+ },
201
+ "expect": [
202
+ "30d-anchor",
203
+ "30d-red"
204
+ ]
205
+ },
206
+ {
207
+ "name": "trawler, aground, 30 m",
208
+ "facts": {
209
+ "fact:propulsion": "propulsion:power",
210
+ "fact:activity": "activity:trawling",
211
+ "fact:position": "position:aground",
212
+ "fact:length_m": 30
213
+ },
214
+ "expect": [
215
+ "30d-anchor",
216
+ "30d-red"
217
+ ]
218
+ },
219
+ {
220
+ "name": "fishing at anchor, 30 m",
221
+ "facts": {
222
+ "fact:propulsion": "propulsion:power",
223
+ "fact:activity": "activity:fishing",
224
+ "fact:position": "position:anchored",
225
+ "fact:length_m": 30
226
+ },
227
+ "expect": [
228
+ "26c-id",
229
+ "30a",
230
+ "30b",
231
+ "30c"
232
+ ]
233
+ },
193
234
  {
194
235
  "name": "trawler 30 m making way",
195
236
  "facts": {
@@ -767,6 +808,72 @@
767
808
  "26c-id",
768
809
  "26c-mw"
769
810
  ]
811
+ },
812
+ {
813
+ "name": "yacht 11.6 m moored alongside a quay",
814
+ "facts": {
815
+ "fact:propulsion": "propulsion:power",
816
+ "fact:activity": "activity:none",
817
+ "fact:position": "position:moored",
818
+ "fact:on_mooring_buoy": false,
819
+ "fact:length_m": 11.6
820
+ },
821
+ "expect": []
822
+ },
823
+ {
824
+ "name": "same yacht made fast to a mooring buoy, US Inland",
825
+ "jurisdiction": "us/inland",
826
+ "facts": {
827
+ "fact:propulsion": "propulsion:power",
828
+ "fact:activity": "activity:none",
829
+ "fact:position": "position:moored",
830
+ "fact:on_mooring_buoy": true,
831
+ "fact:length_m": 11.6
832
+ },
833
+ "expect": [
834
+ "30a-buoy",
835
+ "30b-buoy"
836
+ ]
837
+ },
838
+ {
839
+ "name": "30b-buoy length boundary: on a mooring buoy at 49.9 m, US Inland",
840
+ "jurisdiction": "us/inland",
841
+ "facts": {
842
+ "fact:propulsion": "propulsion:power",
843
+ "fact:activity": "activity:none",
844
+ "fact:position": "position:moored",
845
+ "fact:on_mooring_buoy": true,
846
+ "fact:length_m": 49.9
847
+ },
848
+ "expect": [
849
+ "30a-buoy",
850
+ "30b-buoy"
851
+ ]
852
+ },
853
+ {
854
+ "name": "30b-buoy length boundary: on a mooring buoy at 50.0 m, US Inland",
855
+ "jurisdiction": "us/inland",
856
+ "facts": {
857
+ "fact:propulsion": "propulsion:power",
858
+ "fact:activity": "activity:none",
859
+ "fact:position": "position:moored",
860
+ "fact:on_mooring_buoy": true,
861
+ "fact:length_m": 50.0
862
+ },
863
+ "expect": [
864
+ "30a-buoy"
865
+ ]
866
+ },
867
+ {
868
+ "name": "same yacht made fast to a mooring buoy, International",
869
+ "facts": {
870
+ "fact:propulsion": "propulsion:power",
871
+ "fact:activity": "activity:none",
872
+ "fact:position": "position:moored",
873
+ "fact:on_mooring_buoy": true,
874
+ "fact:length_m": 11.6
875
+ },
876
+ "expect": []
770
877
  }
771
878
  ]
772
879
  }
@@ -259,6 +259,76 @@
259
259
  }
260
260
  }
261
261
  },
262
+ {
263
+ "name": "19(d)(i) fact reachability: an overtaking latch, out of sight",
264
+ "status": "illustrative",
265
+ "narrative": "The `13(d)` latch case above with `pair:geo:in_sight` flipped to `false` and own's latch already set. No Rule 19 entry exists yet to read it, so `expect` is empty; the Q-49 test reads the fact directly.",
266
+ "expect": [],
267
+ "situation": {
268
+ "own": {
269
+ "fact": {
270
+ "fact:propulsion": "propulsion:power",
271
+ "fact:activity": "activity:none",
272
+ "fact:position": "position:underway",
273
+ "fact:making_way": true,
274
+ "fact:length_m": 30
275
+ },
276
+ "kin": {
277
+ "kin:position": {
278
+ "latitude": 50.0,
279
+ "longitude": -1.4
280
+ },
281
+ "kin:heading_deg": 20,
282
+ "kin:sog_kn": 14,
283
+ "kin:rot_deg_min": 0,
284
+ "kin:dynamics": "dynamics:cargo"
285
+ },
286
+ "geo": {
287
+ "geo:rel_bearing_deg": 90
288
+ },
289
+ "hist": {
290
+ "hist:was_overtaking": true,
291
+ "hist:latched_at_s": 420
292
+ }
293
+ },
294
+ "other": {
295
+ "fact": {
296
+ "fact:propulsion": "propulsion:power",
297
+ "fact:activity": "activity:none",
298
+ "fact:position": "position:underway",
299
+ "fact:making_way": true,
300
+ "fact:length_m": 22
301
+ },
302
+ "kin": {
303
+ "kin:position": {
304
+ "latitude": 49.99877,
305
+ "longitude": -1.39475
306
+ },
307
+ "kin:heading_deg": 40,
308
+ "kin:sog_kn": 9,
309
+ "kin:rot_deg_min": 0,
310
+ "kin:dynamics": "dynamics:fishing"
311
+ },
312
+ "geo": {
313
+ "geo:rel_bearing_deg": 250
314
+ },
315
+ "hist": {
316
+ "hist:was_overtaking": false,
317
+ "hist:latched_at_s": null
318
+ }
319
+ },
320
+ "pair": {
321
+ "geo": {
322
+ "geo:range_m": 400,
323
+ "geo:bearing_change_deg_min": 24.51,
324
+ "geo:cpa_m": 350,
325
+ "geo:tcpa_s": -60,
326
+ "geo:in_sight": false,
327
+ "geo:risk_of_collision": true
328
+ }
329
+ }
330
+ }
331
+ },
262
332
  {
263
333
  "name": "18 matrix: own power-driven, other power-driven, in sight",
264
334
  "status": "binding",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colregs",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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
6
  "repository": {
@@ -3,6 +3,7 @@
3
3
  "$id": "https://github.com/mark-brannan/colregs/schema/applicability-fixtures.schema.json",
4
4
  "title": "fixtures/applicability-fixtures.json",
5
5
  "description": "The cross-implementation contract: fact record -> expected entry ids. Structure only -- see docs/adr/0006-json-schema-and-identifier-diff.md.",
6
+ "$comment": "The top-level `jurisdiction` is the corpus default; a case may name its own, and is then evaluated against `intl` plus that jurisdiction's deltas (REQ-SCOPE-3).",
6
7
  "type": "object",
7
8
  "additionalProperties": false,
8
9
  "required": ["jurisdiction", "cases"],
@@ -28,6 +29,7 @@
28
29
  "required": ["name", "facts", "expect"],
29
30
  "properties": {
30
31
  "name": { "type": "string", "minLength": 1 },
32
+ "jurisdiction": { "type": "string", "pattern": "^[a-z]+(/[a-z]+)*$" },
31
33
  "facts": {
32
34
  "type": "object",
33
35
  "minProperties": 1,