colregs 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/data/rules.json CHANGED
@@ -904,28 +904,41 @@
904
904
  "rule": "21",
905
905
  "rule_title": "Definitions",
906
906
  "jurisdiction": "intl",
907
- "text": "\"Masthead light\" means a white light placed over the fore and aft centerline of the vessel showing an unbroken light over an arc of the horizon of 225 degrees and so fixed as to show the light from right ahead to 22.5 degrees abaft the beam on either side of the vessel."
907
+ "text": "\"Masthead light\" means a white light placed over the fore and aft centerline of the vessel showing an unbroken light over an arc of the horizon of 225 degrees and so fixed as to show the light from right ahead to 22.5 degrees abaft the beam on either side of the vessel.",
908
+ "images": [
909
+ "mastheadarc.gif"
910
+ ]
908
911
  },
909
912
  "21(b)": {
910
913
  "path": "21(b)",
911
914
  "rule": "21",
912
915
  "rule_title": "Definitions",
913
916
  "jurisdiction": "intl",
914
- "text": "\"Sidelights\" means a green light on the starboard side and a red light on the port side each showing an unbroken light over an arc of the horizon of 112.5 degrees and so fixed as to show the light from right ahead to 22.5 degrees abaft the beam on its respective side. In a vessel of less than 20 meters in length the sidelights may be combined in one lantern carried on the fore and aft centerline of the vessel."
917
+ "text": "\"Sidelights\" means a green light on the starboard side and a red light on the port side each showing an unbroken light over an arc of the horizon of 112.5 degrees and so fixed as to show the light from right ahead to 22.5 degrees abaft the beam on its respective side. In a vessel of less than 20 meters in length the sidelights may be combined in one lantern carried on the fore and aft centerline of the vessel.",
918
+ "images": [
919
+ "portarc.gif",
920
+ "stbdarc.gif"
921
+ ]
915
922
  },
916
923
  "21(c)": {
917
924
  "path": "21(c)",
918
925
  "rule": "21",
919
926
  "rule_title": "Definitions",
920
927
  "jurisdiction": "intl",
921
- "text": "\"Sternlight\" means a white light placed as nearly as practicable at the stern showing an unbroken light over an arc of the horizon of 135 degrees and so fixed as to show the light 67.5 degrees from right aft on each side of the vessel."
928
+ "text": "\"Sternlight\" means a white light placed as nearly as practicable at the stern showing an unbroken light over an arc of the horizon of 135 degrees and so fixed as to show the light 67.5 degrees from right aft on each side of the vessel.",
929
+ "images": [
930
+ "sternarc.gif"
931
+ ]
922
932
  },
923
933
  "21(d)": {
924
934
  "path": "21(d)",
925
935
  "rule": "21",
926
936
  "rule_title": "Definitions",
927
937
  "jurisdiction": "intl",
928
- "text": "\"Towing light\" means a yellow light having the same characteristics as the \"sternlight\" defined in Rule 21(c)."
938
+ "text": "\"Towing light\" means a yellow light having the same characteristics as the \"sternlight\" defined in Rule 21(c).",
939
+ "images": [
940
+ "towingarc.gif"
941
+ ]
929
942
  },
930
943
  "21(e)": {
931
944
  "path": "21(e)",
@@ -1003,6 +1016,7 @@
1003
1016
  "jurisdiction": "intl",
1004
1017
  "text": "a second masthead light abaft of and higher than the forward one; except that a vessel of less than 50 meters in length shall not be obliged to exhibit such a light but may do so;",
1005
1018
  "images": [
1019
+ "NRHB_23_a.png",
1006
1020
  "NRHB_23_aii.png"
1007
1021
  ]
1008
1022
  },
@@ -1103,7 +1117,10 @@
1103
1117
  "rule": "24",
1104
1118
  "rule_title": "Towing and Pushing",
1105
1119
  "jurisdiction": "intl",
1106
- "text": "a towing light in a vertical line above the sternlight; and"
1120
+ "text": "a towing light in a vertical line above the sternlight; and",
1121
+ "images": [
1122
+ "towingarc.gif"
1123
+ ]
1107
1124
  },
1108
1125
  "24(a)(v)": {
1109
1126
  "path": "24(a)(v)",
@@ -1428,7 +1445,8 @@
1428
1445
  "jurisdiction": "intl",
1429
1446
  "text": "A vessel engaged in dredging or underwater operations, when restricted in her ability to maneuver, shall exhibit the lights and shapes prescribed in Rules 27(b)(i), (ii) and (iii) and shall in addition when an obstruction exists, exhibit:",
1430
1447
  "images": [
1431
- "NRHB_27_d.png"
1448
+ "NRHB_27_d.png",
1449
+ "NRHB_27_diii.png"
1432
1450
  ]
1433
1451
  },
1434
1452
  "27(d)(i)": {
@@ -1438,7 +1456,8 @@
1438
1456
  "jurisdiction": "intl",
1439
1457
  "text": "two all-round red lights or two balls in a vertical line to indicate the side on which the obstruction exists;",
1440
1458
  "images": [
1441
- "NRHB_27_d.png"
1459
+ "NRHB_27_d.png",
1460
+ "NRHB_27_diii.png"
1442
1461
  ]
1443
1462
  },
1444
1463
  "27(d)(ii)": {
@@ -1448,7 +1467,8 @@
1448
1467
  "jurisdiction": "intl",
1449
1468
  "text": "two all-round green lights or two diamonds in a vertical line to indicate the side on which another vessel may pass; and",
1450
1469
  "images": [
1451
- "NRHB_27_d.png"
1470
+ "NRHB_27_d.png",
1471
+ "NRHB_27_diii.png"
1452
1472
  ]
1453
1473
  },
1454
1474
  "27(d)(iii)": {
@@ -1456,10 +1476,7 @@
1456
1476
  "rule": "27",
1457
1477
  "rule_title": "Vessels Not Under Command or Restricted in Their Ability to Maneuver",
1458
1478
  "jurisdiction": "intl",
1459
- "text": "when at anchor, the lights or shapes prescribed in this paragraph instead of the lights or shapes prescribed in Rule 30.",
1460
- "images": [
1461
- "NRHB_27_diii.png"
1462
- ]
1479
+ "text": "when at anchor, the lights or shapes prescribed in this paragraph instead of the lights or shapes prescribed in Rule 30."
1463
1480
  },
1464
1481
  "27(e)": {
1465
1482
  "path": "27(e)",
@@ -1473,10 +1490,7 @@
1473
1490
  "rule": "27",
1474
1491
  "rule_title": "Vessels Not Under Command or Restricted in Their Ability to Maneuver",
1475
1492
  "jurisdiction": "intl",
1476
- "text": "Three all-round lights in a vertical line where they can best be seen. The highest and lowest of these lights shall be red and the middle light shall be white;",
1477
- "images": [
1478
- "NRHB_27_ei.png"
1479
- ]
1493
+ "text": "Three all-round lights in a vertical line where they can best be seen. The highest and lowest of these lights shall be red and the middle light shall be white;"
1480
1494
  },
1481
1495
  "27(e)(ii)": {
1482
1496
  "path": "27(e)(ii)",
@@ -1485,6 +1499,7 @@
1485
1499
  "jurisdiction": "intl",
1486
1500
  "text": "a rigid replica of the International Code flag \"A\" not less than 1 meter in height. Measures shall be taken to ensure its all-round visibility.",
1487
1501
  "images": [
1502
+ "NRHB_27_ei.png",
1488
1503
  "NRHB_27_eii.png"
1489
1504
  ]
1490
1505
  },
@@ -1573,7 +1588,7 @@
1573
1588
  "jurisdiction": "intl",
1574
1589
  "text": "A vessel at anchor shall exhibit where it can best be seen: (i) in the fore part, an all-round white light or one ball; (ii) at or near the stern and at a lower level than the light prescribed in Rule 30(a)(i), an all-round white light.",
1575
1590
  "images": [
1576
- "NRHB_30_a.png"
1591
+ "NRHB_30_d.png"
1577
1592
  ]
1578
1593
  },
1579
1594
  "30(b)": {
@@ -1581,14 +1596,20 @@
1581
1596
  "rule": "30",
1582
1597
  "rule_title": "Anchored Vessels and Vessels Aground",
1583
1598
  "jurisdiction": "intl",
1584
- "text": "A vessel of less than 50 meters in length may exhibit an all-round white light where it can best be seen instead of the lights prescribed in Rule 30(a)."
1599
+ "text": "A vessel of less than 50 meters in length may exhibit an all-round white light where it can best be seen instead of the lights prescribed in Rule 30(a).",
1600
+ "images": [
1601
+ "NRHB_30_a.png"
1602
+ ]
1585
1603
  },
1586
1604
  "30(c)": {
1587
1605
  "path": "30(c)",
1588
1606
  "rule": "30",
1589
1607
  "rule_title": "Anchored Vessels and Vessels Aground",
1590
1608
  "jurisdiction": "intl",
1591
- "text": "A vessel at anchor may, and a vessel of 100 meters and more in length shall, also use the available working or equivalent lights to illuminate her decks."
1609
+ "text": "A vessel at anchor may, and a vessel of 100 meters and more in length shall, also use the available working or equivalent lights to illuminate her decks.",
1610
+ "images": [
1611
+ "NRHB_30_d.png"
1612
+ ]
1592
1613
  },
1593
1614
  "30(d)": {
1594
1615
  "path": "30(d)",
@@ -1597,7 +1618,6 @@
1597
1618
  "jurisdiction": "intl",
1598
1619
  "text": "A vessel aground shall exhibit the lights prescribed in Rule 30(a) or (b) and in addition, if practicable, where they can best be seen: (i) two all-round red lights in a vertical line; (ii) three balls in a vertical line.",
1599
1620
  "images": [
1600
- "NRHB_30_d.png",
1601
1621
  "NRHB_30_dii.png"
1602
1622
  ]
1603
1623
  },
@@ -0,0 +1,3 @@
1
+ {
2
+ "version": "0.2.4"
3
+ }
@@ -1,7 +1,8 @@
1
1
  # ADR 0001 — Package name, and jurisdiction as a dimension
2
2
 
3
3
  Date: 2026-08-29
4
- Status: accepted; amended 2026-09-05 (licence terms verified, see Amendments)
4
+ Status: accepted; amended 2026-09-05 and 2026-09-09 (licence terms verified,
5
+ see Amendments). CEVNI's text stays blocked; ADR 0010 unblocks modelling it.
5
6
 
6
7
  ## Context
7
8
 
@@ -94,7 +95,7 @@ what it implies for which instrument supplies the Rules *text*.
94
95
  | Jurisdiction | Instrument (text source) | Delta | Licence, verified | Attribution to ship (REQ-PROV-3) |
95
96
  |---|---|---|---|---|
96
97
  | `us/inland` | 33 CFR 83, eCFR | large | 17 U.S.C. §105, public domain | none; credit USCG by custom |
97
- | `eu/cevni` | CEVNI Rev.6 (UNECE) | largest | **unverified** — unece.org unreachable from the checking host; the UN default terms are personal, non-commercial only. Blocked until written permission is obtained or a national transposition is chosen instead | — |
98
+ | `eu/cevni` | CEVNI Rev.6 (UNECE) | largest | **unverified** — unece.org unreachable from the checking host; the UN default terms are personal, non-commercial only. Text blocked until written permission or a national transposition; structure may be modelled now with the text withheld — ADR 0010 | — |
98
99
  | `ca/inland` | Collision Regulations, C.R.C. c.1416, Schedule 1 | moderate | Reproduction of Federal Law Order SI/97-5 | none; accuracy diligence required, and must not be represented as an official version |
99
100
  | `de/binnen` | SeeSchStrO (delta) + KVR, Anlage to SeeStrOV (text) | large | §5(1) UrhG, no copyright | none |
100
101
  | `uk` | SI 1996/75 (delta) + MSN 1781 (text) | near-zero | OGL v3.0, Crown copyright | "Contains public sector information licensed under the Open Government Licence v3.0." |
@@ -141,6 +142,9 @@ what it implies for which instrument supplies the Rules *text*.
141
142
  or a national transposition under an open licence (Germany's BinSchStrO
142
143
  under §5(1) UrhG, or the Netherlands' BPR) — the same corpus by a lawful
143
144
  route, at the cost of being a national delta rather than "CEVNI".
145
+ A third way opened later: ADR 0010 permits modelling CEVNI now, text
146
+ withheld. These two remedies restore the text; they no longer gate the
147
+ work.
144
148
  8. **IMO's own text is closed to this package.** The IMO website terms
145
149
  permit copying and adaptation "for the User's personal, non-commercial
146
150
  purposes" and state that "Reuse of the Materials for commercial purposes
@@ -192,3 +196,32 @@ is not made here.
192
196
  <https://www.un.org/en/about-us/terms-of-use>.
193
197
  - IMO: <https://www.imo.org/en/About/Conventions/Pages/COLREG.aspx>;
194
198
  <https://www.imo.org/en/About/Pages/IMO-Website-Terms-and-conditions-of-use.aspx>.
199
+
200
+ ### 2026-09-09 — Independent re-check (issue #75), plus per-language corpus sources (Q-7)
201
+
202
+ Re-fetched issue #75's primary sources; checked Q-7's three per-language
203
+ corpus candidates, previously unchecked. Q-6 (authenticity) verified
204
+ 2026-08-30.
205
+
206
+ #### Six jurisdictions — confirms 2026-09-05 verbatim, nothing changed
207
+
208
+ | Jurisdiction | Re-checked against | Result |
209
+ |---|---|---|
210
+ | `us` | [17 U.S.C. §105](https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title17-section105&num=0&edition=prelim) | Confirmed: "Copyright protection under this title is not available for any work of the United States Government." Public domain, as recorded. |
211
+ | `uk` | [OGL v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/) | Confirmed: copy/adapt/exploit commercially permitted; required attribution string matches; excludes third-party rights. As recorded. |
212
+ | `au` | [legislation.gov.au terms-of-use](https://www.legislation.gov.au/terms-of-use) | Confirmed CC BY 4.0; both attribution strings (unchanged/adapted) match verbatim. As recorded. |
213
+ | `de` | [§5 UrhG](https://www.gesetze-im-internet.de/urhg/__5.html) | Confirmed: §5(1) excludes Gesetze, Verordnungen, amtliche Erlasse und Bekanntmachungen from copyright outright; §5(2)'s conditions apply only to the other class of official works. As recorded. |
214
+ | `ca` | [Reproduction of Federal Law Order, SI/97-5](https://laws-lois.justice.gc.ca/eng/regulations/SI-97-5/page-1.html) | Confirmed: reproduction permitted "provided due diligence is exercised... and the reproduction is not represented as an official version." As recorded. |
215
+ | `eu/cevni` | [unece.org/copyright](https://unece.org/copyright) (403), [unece.org/general/copyright-notice](https://unece.org/general/copyright-notice) (403); [un.org copyright](https://www.un.org/en/about-us/copyright), [un.org terms-of-use](https://www.un.org/en/about-us/terms-of-use) | Both unece.org paths 403 again — independently reproduces "unreachable" rather than assuming it. UN default terms confirmed restrictive: "personal, non-commercial use... no right to resell, redistribute... or create derivative works." Still blocked, not merely unclear — no change. |
216
+
217
+ #### Per-language corpus sources (Q-7) — new ground, mixed result
218
+
219
+ | Source | Checked against | Verdict |
220
+ |---|---|---|
221
+ | BOE (`es`) | [boe.es/informacion/aviso_legal](https://www.boe.es/informacion/aviso_legal/index.php) (the path Q-7 guessed, `boe.es/aviso_legal/`, 404s) | Clean — permits the reuse REQ-PROV-2 needs. Commercial + non-commercial reuse; required attribution ("Fuente de los datos: Agencia Estatal Boletín Oficial del Estado", or "Basado en datos de..." for derivatives); consolidated texts must not be represented as official. Matches Q-7's guess; cheapest language to unblock. |
222
+ | UNTS (`en`/`fr`) | [un.org terms-of-use](https://www.un.org/en/about-us/terms-of-use), [un.org copyright](https://www.un.org/en/about-us/copyright), [treaties.un.org FAQ](https://treaties.un.org/pages/Overview.aspx?path=overview%2Ffaq%2Fpage1_en.xml) (no copyright content) | Ambiguous, new concern — no UNTS-specific rights page found; the only primary terms reachable were the UN's general "personal, non-commercial... no derivative works" terms, the same ones blocking CEVNI. Weakens rather than confirms Q-7's "UNTS deposit is likely lawful" assumption. Resolves via a UNTS-specific rights statement, written UN permission, or a national republication (the `de`/`uk`/`ca`/`us` pattern already used for `intl`). |
223
+ | Finlex (`fi`) | [finlex.fi](https://www.finlex.fi/en/), [finlex.fi/en/legislation](https://www.finlex.fi/en/legislation/), [Tekijänoikeuslaki 404/1961](https://www.finlex.fi/fi/laki/ajantasa/1961/19610404) | Inconclusive — no terms/copyright text was present in the fetched HTML (JS-rendered shells); §9's carve-out for *säädökset* could not be confirmed from primary text. A secondary source (Electronic Frontier Finland) reports a live, contested Finlex copyright claim over compiled statute text. Resolves via a JS-capable fetch of §9 and Finlex's actual ToS page. |
224
+
225
+ `es` clears REQ-PROV-2; `fi` and `en`/`fr` via UNTS stay open. Updating
226
+ `docs/requirements.md` Q-7 for this is left as a follow-up (concurrent
227
+ edits to that file are in flight elsewhere).
@@ -124,7 +124,7 @@ consumer's call (the spirit of REQ-CONS-3).
124
124
  ### Sketch (illustrative, not binding on filenames)
125
125
 
126
126
  ```text
127
- data/rules.json # skeleton: paths, rule numbers, gaps
127
+ data/rules.json # skeleton: paths, numbers, gaps, text_status
128
128
  data/text/intl.en-US.uscg.json # today's text, relabeled for what it is
129
129
  data/text/intl.fr.unts.json # authentic French, when licensed+landed
130
130
  data/text/intl.fi.finlex.json # Finnish national text, contributable
@@ -0,0 +1,47 @@
1
+ # ADR 0009 — `data/version.json` is the single version stamp for `data/`
2
+
3
+ Date: 2026-09-08
4
+ Status: accepted
5
+
6
+ ## Context
7
+
8
+ Nothing in this package's schema or data files carries a version anywhere a
9
+ consumer can check it against. colregs-engine's `Evaluation.colregs.version`
10
+ names the engine's own resolved npm dependency, not the `data` object it was
11
+ actually handed — so a caller evaluating against a stale cached fixture, a
12
+ monorepo with mismatched `colregs` versions, or applicability data
13
+ reconstructed by hand from an old export gets a result silently attributed
14
+ to whatever release happens to be resolved locally: not a crash, not a
15
+ warning, just a wrong-but-plausible `Evaluation`.
16
+
17
+ Two alternatives were considered and rejected:
18
+
19
+ - **A `version`/`dataVersion` field on each of the seven `data/*.json`
20
+ files.** The files already release as one unit — one npm version, one
21
+ `package.json` — so seven independent copies can only drift, never add
22
+ information.
23
+ - **Leaning on the npm package version alone (status quo).** That is
24
+ exactly what colregs-engine already does, and exactly what it cannot
25
+ verify against, because the installed package version and the data
26
+ actually passed into `evaluate()` are two different things once the data
27
+ crosses a process/cache/file boundary.
28
+
29
+ ## Decision
30
+
31
+ A single manifest, `data/version.json`, is the one source of truth:
32
+
33
+ - `{"version": "<semver>"}`, matching `package.json` at release time.
34
+ - `schema/version.schema.json` validates it, same pattern as every other
35
+ `data/*.json` file.
36
+ - `release-please-config.json`'s `extra-files` generic JSON updater keeps
37
+ it in sync on every release, the same way `.release-please-manifest.json`
38
+ already is — the version cannot go stale without the release itself
39
+ failing.
40
+
41
+ ## Consequences
42
+
43
+ - colregs-engine (or any consumer) can compare its resolved package version
44
+ against `data/version.json` at the point `evaluate()` receives `data`,
45
+ and throw or warn on a mismatch instead of silently reporting the wrong
46
+ version. That comparison is a colregs-engine change, tracked separately.
47
+ - `data/version.json` is never hand-edited; release-please owns it.
@@ -0,0 +1,123 @@
1
+ # ADR 0010 — A jurisdiction may ship without its rule text
2
+
3
+ Date: 2026-09-09
4
+ Status: accepted
5
+
6
+ ## Context
7
+
8
+ REQ-PROV-2 blocks a jurisdiction until its reproduction terms are checked
9
+ against the primary source. Three passes have now checked CEVNI's and come
10
+ back the same way: unece.org is unreachable from the checking hosts (403,
11
+ reproduced independently), and the UN's default terms grant personal,
12
+ non-commercial use with "no right to resell, redistribute, or create
13
+ derivative works" — see ADR 0001's 2026-09-05 and 2026-09-09 amendments. The
14
+ remedies named there — written permission from UN Publications, or a national
15
+ transposition such as Germany's BinSchStrO — are both slow, and one of them
16
+ is somebody else's decision.
17
+
18
+ Meanwhile `eu/cevni` has sat in the jurisdiction vocabulary
19
+ (requirements.md §2) as a value nothing may populate, and every session that
20
+ has looked at it has read "licence blocked" as "jurisdiction blocked" and
21
+ stopped.
22
+
23
+ That reading is wrong, and it is the cost here. What this package is for is
24
+ **evaluating rules correctly**: which entries apply, what modality they
25
+ carry, which gates they turn on. None of that needs a word of the rule text.
26
+ A licence forbidding reproduction of an expression does not forbid modelling
27
+ what the expression provides, any more than a copyright in a timetable
28
+ forbids knowing when the train leaves.
29
+
30
+ ## Decision
31
+
32
+ **A jurisdiction whose reproduction terms fail or cannot be verified MAY be
33
+ implemented as structure, with its text withheld.** REQ-PROV-2 continues to
34
+ govern *reproducing text*; it does not govern *modelling rules*. Structure
35
+ here means everything except the words: paragraph paths, rule numbers,
36
+ applicability entries, facts, gates, geometry, modality, and the relations
37
+ between a jurisdiction's paragraphs and the international ones.
38
+
39
+ Referencing a rule by number is not reproduction: a citation is a fact about
40
+ which provision is invoked, not its protected expression, and the package
41
+ needs nothing more than that to be correct.
42
+
43
+ **Mechanism.** `data/rules.json` gains a per-paragraph `text_status`:
44
+
45
+ | `text_status` | `text` | meaning |
46
+ |---|---|---|
47
+ | `verbatim` (default when absent) | required | today's state: the source's own words |
48
+ | `withheld` | MUST be absent | the paragraph is modelled; its text is not ours to ship |
49
+
50
+ A `withheld` paragraph carries a `withheld_reason` naming the licence
51
+ barrier, and its `rule_title` MUST be a neutral label written for this
52
+ package — "Overtaking", "Sound signals in reduced visibility" — never a
53
+ heading copied from the source. Where a withheld paragraph is substantively
54
+ the same provision as an international one, it carries `mirrors`, a path
55
+ into the `intl` ruleset; a consumer may then display the international text
56
+ **labelled as the international equivalent**, never as that jurisdiction's
57
+ text. CEVNI is largely a restatement of the Rules for inland waters, so
58
+ `mirrors` should cover most of it, leaving the genuine CEVNI-only provisions
59
+ as the short list that shows a placeholder.
60
+
61
+ ### ✎ What a withheld paragraph carries in place of text — pencil
62
+
63
+ **Ink is only that the text is withheld and the structure ships.** What
64
+ stands in for the text is deliberately *not* settled here, and this ADR does
65
+ not license a later session to settle it by inference. Any of the following
66
+ may be adopted, dropped or combined before a general release without another
67
+ ADR, and more than one may ship at once — they occupy different keys:
68
+
69
+ | option | key | note |
70
+ |---|---|---|
71
+ | **Redaction** — the citation alone | — | the floor; ships today, needs no decision |
72
+ | **Digest** — a hash of the normalised text, algorithm-qualified | `text_digest` | lets a holder of a lawful copy bind it to our structure, and catches a silent upstream amendment |
73
+ | **Slug** — key nouns and verbs lifted from the paragraph, lemmatised, English counterparts where the source is another language | `text_slug` | the straw man below |
74
+ | **`mirrors`** — the `intl` equivalent | `mirrors` | real, lawful text for the majority of CEVNI; silent exactly where CEVNI differs |
75
+
76
+ **Straw man for the slug**, so the option is designed against something
77
+ rather than argued about in the abstract: take the paragraph, keep the key
78
+ nouns and verbs, drop everything else, lemmatise, emit a sorted set —
79
+ `blue-board`, `overtake`, `sound-signal`. Deterministic, reproducible from
80
+ the same input, no word order, no syntax, no prose. Where the source is not
81
+ English, the terms map to their English counterparts, which is the same
82
+ vocabulary the applicability entries already use. Build it and judge it; it
83
+ may be replaced or dropped. See the research issue.
84
+
85
+ Nothing here turns on where the line between indexing and a derivative work
86
+ falls. That question is not this ADR's to answer and does not gate the work:
87
+ redaction ships today whatever the answer, and the slug is a straw man to be
88
+ built, investigated or thrown away later.
89
+
90
+ How a withheld paragraph reads on screen is likewise a display choice, not a
91
+ data one.
92
+
93
+ ## Consequences
94
+
95
+ - **No future session may record CEVNI as blocked.** It is unblocked for
96
+ structure as of this ADR, blocked for text until ADR 0001's licence
97
+ question is answered. The same route is open to any jurisdiction that
98
+ fails REQ-PROV-2 later; nothing here is CEVNI-specific.
99
+ - **A different bar still stands, and it is not this one.** REQ-SCOPE-3 and
100
+ Q-11 hold that no non-`intl` jurisdiction lands until an explicit
101
+ suppression mechanism exists, because silence-means-inherit would apply
102
+ international law where a national body deliberately has none. That is
103
+ about deltas, not licences; it survives this ADR untouched and is the live
104
+ blocker on CEVNI. Read the two together or the wrong one gets blamed.
105
+ - `schema/rules.schema.json` carries the conditional: `text` required unless
106
+ `text_status` is `withheld`, in which case it is forbidden and
107
+ `withheld_reason` is required. Only `withheld_reason` is refused on a
108
+ verbatim paragraph; `mirrors`, `text_digest` and `text_slug` are open to
109
+ any paragraph, defined but unwritten, so the pencil options above cannot be
110
+ foreclosed by `additionalProperties: false`. Additive under ADR 0006. Since
111
+ `data/rules.json` is entirely verbatim `intl`, nothing in the data
112
+ exercises that branch — the cases are asserted in `test/data.test.mjs`.
113
+ - **Nothing in evaluation may read `text`.** That is the invariant this
114
+ decision rests on, so it wants a test rather than a promise: fixtures and
115
+ the evaluator output envelope must stay green over a ruleset with every
116
+ `text` stripped.
117
+ - REQ-PROV-1 still binds: a withheld paragraph records its source, its
118
+ retrieval date and why the text is absent. Withholding is a provenance
119
+ record, not a gap in one — and not a `gaps` entry either, which keeps its
120
+ meaning of a paragraph we could not obtain at all.
121
+ - The licence question stays open where it already is. If permission or a
122
+ national transposition arrives, `withheld` paragraphs gain their text and
123
+ drop the key; no modelling is redone.
@@ -0,0 +1,173 @@
1
+ # ADR 0011 — The public API: one verb per input
2
+
3
+ Date: 2026-09-07
4
+ Origin: colregs-engine, where it was ADR 0001 until 2026-09-09. The
5
+ decision is about colregs-engine's public API; "this package" below is
6
+ colregs-engine. ADRs for the whole family live here (AGENTS.md).
7
+ Status: accepted. Ink and pencil are marked per item in the register at the
8
+ end, under colregs' `docs/conventions.md`.
9
+
10
+ ## Context
11
+
12
+ The engine shipped one entry point, `evaluateDisplay`, and its README promised
13
+ that colregs' `classification` and `precedence` categories would "get their own
14
+ entry points" — a promise made in passing while renaming, with no target to
15
+ build to. Meanwhile colregs' ADR 0005 (pencil, all of it) fixed the *input*
16
+ those categories read: a situation record in `data/facts.json` §`situation`,
17
+ exercised by `fixtures/situation-fixtures.json`, with some twenty two-subject
18
+ entries written against it. The data side exists; the engine side has no shape.
19
+
20
+ This ADR fixes the shape. It does not build it.
21
+
22
+ ## Decision
23
+
24
+ ### 1. Two inputs, two verbs
25
+
26
+ | input | verb | result | status |
27
+ |---|---|---|---|
28
+ | `FactRecord` — one vessel | `evaluateDisplay` | `DisplayEvaluation` | built |
29
+ | `Situation` — two vessels and the encounter | `evaluateEncounter` | `EncounterEvaluation` | target |
30
+
31
+ A verb is named for the thing it evaluates, in colregs' own vocabulary: a
32
+ *display* is what one vessel shows, an *encounter* is what colregs calls the
33
+ `pair` subject. The proposed pair `classifyEncounter` / `resolvePrecedence`
34
+ is **not** built, for three reasons that are about the data, not taste:
35
+
36
+ - **One predicate pass.** Every `scope`, `classification` and `precedence`
37
+ entry matches the same situation by the same walker, as colregs' reference
38
+ evaluator does; two verbs run it twice or make the caller carry the type on.
39
+ - **Relations cross categories.** Rule 18's precedence entries override Rule
40
+ 15's; entry `13a` overrides every Rule 18 entry. `rel:overrides` can only be
41
+ resolved with both sides applied in the same result.
42
+ - **Q-35 is open.** Whether a norm may read another norm's *effect* (8(f)(iii)
43
+ reads "a vessel whose passage is not to be impeded") is unsettled in colregs.
44
+ An API seam here would settle it by accident, in the wrong repository.
45
+
46
+ Each verb has a companion that returns only the matched entry ids —
47
+ `appliedDisplayEntries` today, `appliedEncounterEntries` to come — because
48
+ that is the fixture contract: both fixture files `expect` entry ids.
49
+
50
+ ### 2. `FactRecord` keeps its name
51
+
52
+ It is colregs' name for the per-vessel record (`own.fact` is "exactly the
53
+ record above, key for key"). ADR 0005 §2 has the situation wrap two fact
54
+ records; the fact record itself does not widen, and a display consumer never
55
+ sees a situation. A name that hinted at two vessels would describe the wrapper,
56
+ not the thing.
57
+
58
+ ### 3. The `Situation` type mirrors the fixture, not the predicate
59
+
60
+ colregs states the situation twice: nested by subject and class in
61
+ `facts.json` §`situation.record` and in every fixture case, and flat as
62
+ `own:fact:activity` inside predicates. The engine's public type is the
63
+ **nested** form. The flat form is the predicate namespace and stays internal
64
+ to the walker.
65
+
66
+ ```ts
67
+ interface Situation {
68
+ own: Subject;
69
+ other?: Subject;
70
+ pair?: Pair;
71
+ }
72
+ interface Subject { fact: FactRecord; kin?: Kinematics; geo?: DirectionalGeometry; hist?: History; }
73
+ interface Pair { geo?: PairGeometry; env?: Environment; }
74
+ ```
75
+
76
+ - `own` is required; `other`/`Subject.fact` follow colregs' own fixture schema
77
+ (`situation-fixtures.schema.json`, `0.2.0`) — `other` optional (Rule 19's
78
+ single-vessel scope needs no synthesized one), `fact` required. Every other
79
+ class, and every key inside a class, is optional — absent is absent.
80
+ - Keys keep their full colregs identifier (`'kin:heading_deg'`, not
81
+ `heading_deg`), as `FactRecord` keeps `'fact:length_m'`. A fixture's
82
+ `situation` object is then assignable to `Situation` unedited.
83
+ - `Kinematics`, `History`, the two geometries and `Environment` are
84
+ **generated** from `facts.json` §`situation` by the generator that produces
85
+ `FactRecord`, and checked by the same validator: a situation arriving as
86
+ JSON is rejected on the same terms as a fact record.
87
+ - `FactRecord` is reused, not copied, as `Subject.fact`.
88
+
89
+ ### 4. `EncounterEvaluation`, the target result
90
+
91
+ ```ts
92
+ type EntryId = string; // a colregs entry id: '13a', '18b'
93
+ type ParagraphCite = string; // a Rules paragraph cite: '17(c)'
94
+
95
+ interface EncounterEvaluation {
96
+ colregs: { version: string; source: 'resolved' | 'caller' };
97
+ applied: EntryId[];
98
+ scope: EntryId[];
99
+ encounter?: 'head-on' | 'crossing' | 'overtaking' | 'none';
100
+ risk_of_collision: { asserted: boolean; by: EntryId[] };
101
+ roles: { own: SubjectRole[]; other: SubjectRole[] };
102
+ overridden: { id: EntryId; by: EntryId }[];
103
+ modalities: Record<EntryId, Modality>;
104
+ }
105
+ interface SubjectRole { role: Role; by: EntryId; }
106
+ ```
107
+
108
+ `EntryId` and `ParagraphCite` are both `string`, marking which vocabulary a
109
+ field holds: `applied` and `overridden` cite entries, ADR 0012's `phase` and
110
+ `breaches` cite paragraphs. Field names are snake_case with unit suffixes
111
+ throughout, as colregs' own keys are (`kin:heading_deg`).
112
+
113
+ `colregs` and `applied` mean what they mean on `DisplayEvaluation`. Roles are
114
+ a *set* per subject, each citing the entry that assigned it: colregs' own
115
+ suite pins a sailing vessel meeting a CBD vessel as holding `stand-on` and
116
+ `shall-not-impede` at once (Q-36), and a result type that could hold one role
117
+ would have to lie. `risk_of_collision` carries its grounds because 7(a) lets an
118
+ entry add a ground and never deny one. `encounter` is absent when no
119
+ classification entry fired, conflating "no encounter" with "cannot say" (Q-43);
120
+ ADR 0005 §5's status alphabet is the fix, and is not part of this shape yet.
121
+
122
+ ### 5. What this shape does not evaluate
123
+
124
+ Scoped to `evaluateDisplay`/`evaluateEncounter` as defined here, not a ceiling
125
+ on the package; kinematic and temporal evaluation gets two verbs in ADR 0012.
126
+
127
+ - **`conduct`.** Rules 8, 13(a)'s action, 14(a), 16, 17: what a vessel shall
128
+ *do*. Their predicates read a trace — 8(b)'s "readily apparent" alteration,
129
+ 17(a)(ii)'s "as soon as it becomes apparent" — and colregs declares
130
+ `kin:rot_deg_min` read "by a conduct monitor, not by a predicate at a point".
131
+ A different input (a trace: a sequence of situations) and a different tool
132
+ (STL/TLA+): `evaluateConduct`, ADR 0012.
133
+ - **`care` and `meta`.** Rules 2(a) and 2(b) are in colregs'
134
+ `represented_paragraphs` registry precisely so nothing computes them.
135
+ - **The Rule 2 region solver** (R0/R1/R2, research-ontology labels, not API).
136
+ Research under `research/`; its runtime face is `evaluateRule2Departure`,
137
+ ADR 0012.
138
+ - **Time.** Freshness, hysteresis and the 13(d) latch's clock are the caller's;
139
+ the engine receives `hist:*` as facts and does not maintain them.
140
+ - **Choosing.** No verb picks a display or a role; every lawful answer is
141
+ returned (REQ-MODEL-8).
142
+
143
+ In scope and expected of `evaluateEncounter`: derived facts (`fact:rule18_class`
144
+ is computed before matching, as colregs specifies), `rel:overrides`
145
+ resolution, and validation of the situation record.
146
+
147
+ ## Consequences
148
+
149
+ - The README paragraph promising separate classification and precedence
150
+ entry points is replaced by a pointer here.
151
+ - The next PR builds `Situation` generation and validation; the one after
152
+ `appliedEncounterEntries` against `situation-fixtures.json`; only then
153
+ `evaluateEncounter`. Composition decisions the data leaves open go in
154
+ `docs/engine-notes.md`, as the display ones did.
155
+ - `colregs-engine/schema` stays the home of mirrored colregs shapes; `Situation`
156
+ and `EncounterEvaluation` are engine vocabulary, exported from the root.
157
+
158
+ ## Register
159
+
160
+ | item | level | what would settle it |
161
+ |---|---|---|
162
+ | Two verbs, one per input; no `classifyEncounter` / `resolvePrecedence` | ink | — |
163
+ | `evaluateDisplay`, `appliedDisplayEntries`, `DisplayEvaluation`, `opts.data`, `colregs.source` | ink | — |
164
+ | `FactRecord` keeps its name | ink | — |
165
+ | `Situation` nested by subject and class, generated from `facts.json` | ink | — |
166
+ | Verb name `evaluateEncounter`; result name `EncounterEvaluation` | ✎ | colregs renaming the `pair` subject or the `encounter` effect |
167
+ | `own` required, `other`/`Subject.fact` per colregs 0.2.0's fixture schema | ✎ | revised 2026-09-07 from "own/other both required"; Mark to confirm before ink |
168
+ | `appliedEncounterEntries` as the fixture-replay companion | ✎ | the situation-fixture replay being written |
169
+ | Field names snake_case with unit suffixes across both ADRs; `EntryId`/`ParagraphCite` alias `string` for ids and cites | ✎ | the rename's alias window closing; a consumer arguing the compiler should enforce the two apart |
170
+ | `EncounterEvaluation` field set (§4) | ✎ | building it; Q-35, Q-36, Q-43 in colregs |
171
+ | `encounter` absent vs the ADR 0005 §5 status alphabet | ✎ | Q-43 |
172
+ | `conduct` is a separate package, not a third verb | ✎ | superseded by ADR 0012: a third and fourth verb, in this package |
173
+ | Geometry-consistency validation (REQ-VERIFY-8) in the engine's validator | ? | deciding whether it is data-suite-only |