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
package/data/facts.json CHANGED
@@ -549,7 +549,7 @@
549
549
  ],
550
550
  "cite": "12(b)",
551
551
  "cite_pending": null,
552
- "note": "Which side this vessel has the wind on, which 12(b) defines as the side opposite that on which the mainsail is carried, or for a square-rigged vessel the side opposite the largest fore-and-aft sail. `wind_side:unknown` is not a missing value: it is 12(a)(iii)'s case, a vessel with the wind on the port side who cannot determine with certainty which side the other has it on, and entry 12a3 reads it. An absent fact would be silence, and silence satisfies nothing. Declared in `kin` rather than in the fact record because a display consumer never supplies it and the fact record does not change for Part B (REQ-CAT-4) -- but it is a sailing state and not kinematics, which is recorded as strain in Q-44 rather than fixed by widening the class.",
552
+ "note": "Which side this vessel has the wind on, which 12(b) defines as the side opposite that on which the mainsail is carried, or for a square-rigged vessel the side opposite the largest fore-and-aft sail. `wind_side:unknown` is not a missing value: it is 12(a)(iii)'s case, a vessel with the wind on the port side who cannot determine with certainty which side the other has it on, and entry rule:12a_iii reads it. An absent fact would be silence, and silence satisfies nothing. Declared in `kin` rather than in the fact record because a display consumer never supplies it and the fact record does not change for Part B (REQ-CAT-4) -- but it is a sailing state and not kinematics, which is recorded as strain in Q-44 rather than fixed by widening the class.",
553
553
  "actuable": false,
554
554
  "signalk": null
555
555
  }
@@ -562,7 +562,7 @@
562
562
  "unit": "deg",
563
563
  "cite": "13(b)",
564
564
  "cite_pending": null,
565
- "note": "Bearing of the *other* subject, 0-360 clockwise from this subject's own heading. Under `own` this is relative bearing; under `other` it is aspect. Rule 13(b)'s overtaking sector is `other:geo:rel_bearing_deg` in (112.5, 247.5) -- own is more than 22.5 degrees abaft the other vessel's beam. The two sector edges are declared once in `constants` below (`overtaking_sector_from_deg`, `overtaking_sector_to_deg`) and entries 13b-overtaking, 13b-overtaken and 15a-crossing all read them, so the sector and its complement cannot drift apart.",
565
+ "note": "Bearing of the *other* subject, 0-360 clockwise from this subject's own heading. Under `own` this is relative bearing; under `other` it is aspect. Rule 13(b)'s overtaking sector is `other:geo:rel_bearing_deg` in (112.5, 247.5) -- own is more than 22.5 degrees abaft the other vessel's beam. The two sector edges are declared once in `constants` below (`overtaking_sector_from_deg`, `overtaking_sector_to_deg`) and entries rule:13b and rule:15a:crossing both read them, so the sector and its complement cannot drift apart.",
566
566
  "actuable": false,
567
567
  "signalk": null
568
568
  },
@@ -590,7 +590,7 @@
590
590
  "unit": "deg/min",
591
591
  "cite": "7(d)(i)",
592
592
  "cite_pending": null,
593
- "note": "Rate of change of the compass bearing between the two vessels, signed. Rule 7(d)(i): a bearing that does not appreciably change means risk of collision exists. The Rules do not say what appreciably means; `constants.appreciable_bearing_change_deg_min` fixes it in pencil and entry 7d1 is the only reader.",
593
+ "note": "Rate of change of the compass bearing between the two vessels, signed. Rule 7(d)(i): a bearing that does not appreciably change means risk of collision exists. The Rules do not say what appreciably means; `constants.appreciable_bearing_change_deg_min` fixes it in pencil and entry rule:7d_i is the only reader.",
594
594
  "actuable": false,
595
595
  "signalk": null
596
596
  },
@@ -721,7 +721,7 @@
721
721
  "status": "pencil",
722
722
  "cite": null,
723
723
  "cite_pending": "7(d)(i)",
724
- "note": "Rule 7(d)(i) deems risk of collision to exist if the compass bearing of an approaching vessel 'does not appreciably change', and nowhere says what appreciably means. Read as a magnitude: entry 7d1 gates on the open interval (-1.0, 1.0) so a bearing drawing out either way is treated alike, because the paragraph asks whether the bearing changes and not which way.",
724
+ "note": "Rule 7(d)(i) deems risk of collision to exist if the compass bearing of an approaching vessel 'does not appreciably change', and nowhere says what appreciably means. Read as a magnitude: entry rule:7d_i gates on the open interval (-1.0, 1.0) so a bearing drawing out either way is treated alike, because the paragraph asks whether the bearing changes and not which way.",
725
725
  "settled_by": "A source that fixes the figure -- Cockcroft & Lameijer, or an administration's guidance -- or a sensitivity sweep in colregs-engine showing which values change a region finding. Until then it is one session's number and any session may change it."
726
726
  },
727
727
  "head_on_half_angle_deg": {
@@ -92,8 +92,8 @@
92
92
  "cite": "AnxI.2(f)(ii)",
93
93
  "light": "light:all_round",
94
94
  "applies_to_entries": [
95
- "27b-id",
96
- "28"
95
+ "rule:27b_i",
96
+ "rule:28"
97
97
  ],
98
98
  "text": "When it is impracticable to carry the all-round lights prescribed by Rule 27(b)(i) or Rule 28 below the masthead lights, they may be carried above the after masthead light(s) or vertically in between the forward masthead light(s) and after masthead light(s), provided that in the latter case the requirement of paragraph 3(c) shall be complied with."
99
99
  },
@@ -162,8 +162,8 @@
162
162
  {
163
163
  "cite": "AnxI.2(j)",
164
164
  "applies_to_entries": [
165
- "26b-id",
166
- "26c-id"
165
+ "rule:26b_i",
166
+ "rule:26c_i"
167
167
  ],
168
168
  "datum": "sidelights",
169
169
  "min_multiple_of_pair_spacing": 2,
@@ -172,7 +172,7 @@
172
172
  {
173
173
  "cite": "AnxI.2(k)",
174
174
  "applies_to_entries": [
175
- "30a"
175
+ "rule:30a"
176
176
  ],
177
177
  "datum": "after anchor light",
178
178
  "min_m": 4.5,
@@ -212,7 +212,7 @@
212
212
  {
213
213
  "cite": "AnxI.3(c)",
214
214
  "applies_to_entries": [
215
- "27b-id"
215
+ "rule:27b_i"
216
216
  ],
217
217
  "min_offset_from_centerline_m": 2,
218
218
  "text": "When the lights prescribed in Rule 27(b)(i) are placed vertically between the forward masthead light(s) and the after masthead light(s), these all-round lights shall be placed at a horizontal distance of not less than 2 meters from the fore and aft centerline of the vessel in the athwartship direction."
@@ -227,7 +227,7 @@
227
227
  {
228
228
  "cite": "AnxI.4(a)",
229
229
  "applies_to_entries": [
230
- "26c-gear"
230
+ "rule:26c_ii"
231
231
  ],
232
232
  "horizontal_from_identity_lights_m": {
233
233
  "min": 2,
@@ -239,7 +239,7 @@
239
239
  {
240
240
  "cite": "AnxI.4(b)",
241
241
  "applies_to_entries": [
242
- "27d"
242
+ "rule:27d"
243
243
  ],
244
244
  "horizontal_from_ram_lights_m": {
245
245
  "min": 2
@@ -0,0 +1,27 @@
1
+ {
2
+ "language": "en",
3
+ "provenance": {
4
+ "contributors": [
5
+ "mark-brannan"
6
+ ],
7
+ "reviewed_by": [
8
+ "mark-brannan"
9
+ ],
10
+ "review_date": "2026-09-13"
11
+ },
12
+ "strings": {
13
+ "light": {
14
+ "light:masthead": "Masthead light",
15
+ "light:sidelight_starboard": "Starboard sidelight",
16
+ "light:sidelight_port": "Port sidelight",
17
+ "light:sternlight": "Sternlight",
18
+ "light:towing": "Towing light",
19
+ "light:all_round": "All-round light"
20
+ },
21
+ "modality": {
22
+ "shall": "Required",
23
+ "may": "Permitted",
24
+ "shall-if-practicable": "Required if practicable"
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "language": "fi",
3
+ "provenance": {
4
+ "contributors": [
5
+ "claude-fable-5-1"
6
+ ],
7
+ "reviewed_by": [],
8
+ "review_date": "2026-09-15"
9
+ },
10
+ "strings": {
11
+ "light": {
12
+ "light:masthead": "Mastovalo",
13
+ "light:sidelight_starboard": "Oikea sivuvalo",
14
+ "light:sidelight_port": "Vasen sivuvalo",
15
+ "light:sternlight": "Perävalo",
16
+ "light:towing": "Hinausvalo",
17
+ "light:all_round": "Ympäri näköpiirin näkyvä valo"
18
+ },
19
+ "modality": {
20
+ "shall": "Pakollinen",
21
+ "may": "Sallittu",
22
+ "shall-if-practicable": "Pakollinen, jos mahdollista"
23
+ }
24
+ }
25
+ }
package/data/images.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "23(d)(ii)"
22
22
  ],
23
23
  "entries": [
24
- "23d2"
24
+ "rule:23d_ii"
25
25
  ],
26
26
  "depicts": "provision",
27
27
  "transcript": [
@@ -59,9 +59,9 @@
59
59
  "24(b)"
60
60
  ],
61
61
  "entries": [
62
- "23a1",
63
- "23a2",
64
- "24b"
62
+ "rule:23a_i",
63
+ "rule:23a_ii",
64
+ "rule:24b"
65
65
  ],
66
66
  "depicts": "provision",
67
67
  "transcript": [
@@ -94,7 +94,7 @@
94
94
  "23(a)(ii)"
95
95
  ],
96
96
  "entries": [
97
- "23a2"
97
+ "rule:23a_ii"
98
98
  ],
99
99
  "depicts": "exception",
100
100
  "transcript": [
@@ -127,7 +127,7 @@
127
127
  "23(b)"
128
128
  ],
129
129
  "entries": [
130
- "23b"
130
+ "rule:23b"
131
131
  ],
132
132
  "depicts": "provision",
133
133
  "transcript": [
@@ -160,7 +160,7 @@
160
160
  "23(d)(i)"
161
161
  ],
162
162
  "entries": [
163
- "23d1"
163
+ "rule:23d_i"
164
164
  ],
165
165
  "depicts": "provision",
166
166
  "transcript": [
@@ -215,8 +215,8 @@
215
215
  "24(a)(i)"
216
216
  ],
217
217
  "entries": [
218
- "24a-m2",
219
- "24a-rest"
218
+ "rule:24a_i",
219
+ "rule:24a_ii_iv"
220
220
  ],
221
221
  "depicts": "provision",
222
222
  "transcript": [
@@ -249,7 +249,7 @@
249
249
  "24(a)(v)"
250
250
  ],
251
251
  "entries": [
252
- "24a-m3"
252
+ "rule:24a_i:exceeds_200m"
253
253
  ],
254
254
  "depicts": "provision",
255
255
  "transcript": [
@@ -314,7 +314,7 @@
314
314
  "24(e)"
315
315
  ],
316
316
  "entries": [
317
- "24e"
317
+ "rule:24e"
318
318
  ],
319
319
  "depicts": "provision",
320
320
  "transcript": [
@@ -437,7 +437,7 @@
437
437
  "25(a)"
438
438
  ],
439
439
  "entries": [
440
- "25a"
440
+ "rule:25a"
441
441
  ],
442
442
  "depicts": "provision",
443
443
  "transcript": [
@@ -466,7 +466,7 @@
466
466
  "25(b)"
467
467
  ],
468
468
  "entries": [
469
- "25b"
469
+ "rule:25b"
470
470
  ],
471
471
  "depicts": "provision",
472
472
  "transcript": [
@@ -498,7 +498,7 @@
498
498
  "25(c)"
499
499
  ],
500
500
  "entries": [
501
- "25c"
501
+ "rule:25c"
502
502
  ],
503
503
  "depicts": "provision",
504
504
  "transcript": [
@@ -531,7 +531,7 @@
531
531
  "25(d)(i)"
532
532
  ],
533
533
  "entries": [
534
- "25d1"
534
+ "rule:25d_i"
535
535
  ],
536
536
  "depicts": "provision",
537
537
  "transcript": [
@@ -564,7 +564,7 @@
564
564
  "25(d)(ii)"
565
565
  ],
566
566
  "entries": [
567
- "25d2"
567
+ "rule:25d_ii"
568
568
  ],
569
569
  "depicts": "provision",
570
570
  "transcript": [
@@ -629,7 +629,7 @@
629
629
  "26(b)(i)"
630
630
  ],
631
631
  "entries": [
632
- "26b-id"
632
+ "rule:26b_i"
633
633
  ],
634
634
  "depicts": "provision",
635
635
  "transcript": [
@@ -665,7 +665,7 @@
665
665
  "26(b)(iii)"
666
666
  ],
667
667
  "entries": [
668
- "26b-mw"
668
+ "rule:26b_iii"
669
669
  ],
670
670
  "depicts": "provision",
671
671
  "transcript": [
@@ -701,7 +701,7 @@
701
701
  "26(c)(iii)"
702
702
  ],
703
703
  "entries": [
704
- "26c-mw"
704
+ "rule:26c_iii"
705
705
  ],
706
706
  "depicts": "provision",
707
707
  "transcript": [
@@ -740,7 +740,7 @@
740
740
  "27(a)(i)"
741
741
  ],
742
742
  "entries": [
743
- "27a-id"
743
+ "rule:27a_i"
744
744
  ],
745
745
  "depicts": "provision",
746
746
  "transcript": [
@@ -776,7 +776,7 @@
776
776
  "27(a)(iii)"
777
777
  ],
778
778
  "entries": [
779
- "27a-mw"
779
+ "rule:27a_iii"
780
780
  ],
781
781
  "depicts": "provision",
782
782
  "transcript": [
@@ -812,7 +812,7 @@
812
812
  "27(b)(iii)"
813
813
  ],
814
814
  "entries": [
815
- "27b-mw"
815
+ "rule:27b_iii"
816
816
  ],
817
817
  "depicts": "provision",
818
818
  "transcript": [
@@ -849,7 +849,7 @@
849
849
  "27(b)(iv)"
850
850
  ],
851
851
  "entries": [
852
- "27b-anc"
852
+ "rule:27b_iv"
853
853
  ],
854
854
  "depicts": "provision",
855
855
  "transcript": [
@@ -924,7 +924,7 @@
924
924
  "27(d)(ii)"
925
925
  ],
926
926
  "entries": [
927
- "27d"
927
+ "rule:27d"
928
928
  ],
929
929
  "depicts": "provision",
930
930
  "transcript": [
@@ -967,7 +967,7 @@
967
967
  "27(d)(ii)"
968
968
  ],
969
969
  "entries": [
970
- "27d"
970
+ "rule:27d"
971
971
  ],
972
972
  "depicts": "provision",
973
973
  "transcript": [
@@ -1069,7 +1069,7 @@
1069
1069
  "27(f)"
1070
1070
  ],
1071
1071
  "entries": [
1072
- "27f"
1072
+ "rule:27f"
1073
1073
  ],
1074
1074
  "depicts": "provision",
1075
1075
  "transcript": [
@@ -1106,7 +1106,7 @@
1106
1106
  "28"
1107
1107
  ],
1108
1108
  "entries": [
1109
- "28"
1109
+ "rule:28"
1110
1110
  ],
1111
1111
  "depicts": "provision",
1112
1112
  "transcript": [
@@ -1142,7 +1142,7 @@
1142
1142
  "29(a)(i)"
1143
1143
  ],
1144
1144
  "entries": [
1145
- "29a"
1145
+ "rule:29a"
1146
1146
  ],
1147
1147
  "depicts": "provision",
1148
1148
  "transcript": [
@@ -1175,7 +1175,7 @@
1175
1175
  "29(a)(iii)"
1176
1176
  ],
1177
1177
  "entries": [
1178
- "29a"
1178
+ "rule:29a"
1179
1179
  ],
1180
1180
  "depicts": "provision",
1181
1181
  "transcript": [
@@ -1210,7 +1210,7 @@
1210
1210
  "30(b)"
1211
1211
  ],
1212
1212
  "entries": [
1213
- "30b"
1213
+ "rule:30b"
1214
1214
  ],
1215
1215
  "depicts": "provision",
1216
1216
  "transcript": [
@@ -1246,7 +1246,7 @@
1246
1246
  "30(c)"
1247
1247
  ],
1248
1248
  "entries": [
1249
- "30a"
1249
+ "rule:30a"
1250
1250
  ],
1251
1251
  "depicts": "provision",
1252
1252
  "transcript": [
@@ -1281,7 +1281,7 @@
1281
1281
  "30(d)"
1282
1282
  ],
1283
1283
  "entries": [
1284
- "30d-red"
1284
+ "rule:30d_i"
1285
1285
  ],
1286
1286
  "depicts": "provision",
1287
1287
  "transcript": [
@@ -0,0 +1,79 @@
1
+ {
2
+ "note": "The engine interface colregs owns: each verb ADR 0011 and ADR 0012 name, what it reads, what it answers, its entry-id companion and the fixture cases that bind it (ADR 0014).",
3
+ "operations": {
4
+ "evaluateDisplay": {
5
+ "adr": "0011",
6
+ "inputs": [
7
+ {
8
+ "name": "facts",
9
+ "schema": "schema/fact-record.schema.json"
10
+ }
11
+ ],
12
+ "output": "schema/display-evaluation.schema.json",
13
+ "companion": {
14
+ "verb": "appliedDisplayEntries",
15
+ "output": "schema/evaluation.schema.json#/$defs/ruleIds"
16
+ },
17
+ "fixtures": [
18
+ {
19
+ "file": "fixtures/applicability-fixtures.json",
20
+ "case_inputs": [
21
+ "facts"
22
+ ]
23
+ }
24
+ ]
25
+ },
26
+ "evaluateEncounter": {
27
+ "adr": "0011",
28
+ "inputs": [
29
+ {
30
+ "name": "situation",
31
+ "schema": "schema/situation.schema.json"
32
+ }
33
+ ],
34
+ "output": "schema/encounter-evaluation.schema.json",
35
+ "companion": {
36
+ "verb": "appliedEncounterEntries",
37
+ "output": "schema/evaluation.schema.json#/$defs/ruleIds"
38
+ },
39
+ "fixtures": [
40
+ {
41
+ "file": "fixtures/situation-fixtures.json",
42
+ "case_inputs": [
43
+ "situation"
44
+ ]
45
+ }
46
+ ]
47
+ },
48
+ "evaluateConduct": {
49
+ "adr": "0012",
50
+ "inputs": [
51
+ {
52
+ "name": "trace",
53
+ "schema": "schema/trace.schema.json"
54
+ }
55
+ ],
56
+ "output": "schema/conduct-evaluation.schema.json",
57
+ "companion": {
58
+ "verb": "appliedConductEntries",
59
+ "output": "schema/evaluation.schema.json#/$defs/ruleIds"
60
+ },
61
+ "fixtures": []
62
+ },
63
+ "evaluateRule2Departure": {
64
+ "adr": "0012",
65
+ "inputs": [
66
+ {
67
+ "name": "situation",
68
+ "schema": "schema/situation.schema.json"
69
+ },
70
+ {
71
+ "name": "model",
72
+ "schema": "schema/rule2-departure-model.schema.json"
73
+ }
74
+ ],
75
+ "output": "schema/rule2-departure-finding.schema.json",
76
+ "fixtures": []
77
+ }
78
+ }
79
+ }
package/data/rules.json CHANGED
@@ -1199,6 +1199,11 @@
1199
1199
  "NRHB_30_dii.png"
1200
1200
  ]
1201
1201
  },
1202
+ "30(d)(i)": {
1203
+ "path": "30(d)(i)",
1204
+ "rule": "30",
1205
+ "jurisdiction": "intl"
1206
+ },
1202
1207
  "30(e)": {
1203
1208
  "path": "30(e)",
1204
1209
  "rule": "30",
package/data/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.3.0"
2
+ "version": "0.3.1"
3
3
  }
@@ -1,5 +1,7 @@
1
1
  # ADR 0006 — JSON Schema for structural validation, identifier diff for version discipline
2
2
 
3
+ The identifier-diff half is removed pre-1.0 by ADR 0015 (Solace, 2026-09-16); it returns, if at all, with the 1.0.0 tag.
4
+
3
5
  Date: 2026-09-04
4
6
  Status: accepted
5
7
 
@@ -167,7 +167,7 @@ resolution, and validation of the situation record.
167
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
168
  | `appliedEncounterEntries` as the fixture-replay companion | ✎ | the situation-fixture replay being written |
169
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 |
170
+ | `EncounterEvaluation` field set (§4); `categories` and `provenance` added 2026-09-16 beyond the block above, as `DisplayEvaluation` carries them — colregs-engine 0.1.5 built them and ADR 0014's `encounter-evaluation.schema.json` is now the shape | ✎ | building it; Q-35, Q-36, Q-43 in colregs |
171
171
  | `encounter` absent vs the ADR 0005 §5 status alphabet | ✎ | Q-43 |
172
172
  | `conduct` is a separate package, not a third verb | ✎ | superseded by ADR 0012: a third and fourth verb, in this package |
173
173
  | Geometry-consistency validation (REQ-VERIFY-8) in the engine's validator | ? | deciding whether it is data-suite-only |
@@ -0,0 +1,103 @@
1
+ # ADR 0014 — The engine interface is colregs' to own: an operations manifest and result schemas
2
+
3
+ Date: 2026-09-16
4
+ Status: proposed. Solace ordered options 2 and 3 built on 2026-09-16; the
5
+ register marks what settles each row.
6
+
7
+ ## Context
8
+
9
+ ADR 0011 and ADR 0012 fix the engine's verbs and result envelopes, in prose
10
+ and TypeScript blocks, in this repository. The only machine-readable copy
11
+ lives in colregs-engine's `src/types.ts`, so the interface is de facto
12
+ colregs-owned and de jure the engine's: a second engine, in any language,
13
+ would transcribe the ADRs by hand and drift the way the engine's own
14
+ generator was written to stop.
15
+
16
+ colregs already owns *shapes* in production: the engine compiles every
17
+ `schema/*.schema.json` into generated types and derives `FactRecord` and
18
+ `Situation` from `data/facts.json`. What it does not own is the *operations*
19
+ — which verb reads which input and answers which envelope — and the fixture
20
+ files bind to verbs only by convention.
21
+
22
+ Ten routes were surveyed on 2026-09-16 for making the interface a colregs
23
+ artefact, the Java sense of interface against implementation:
24
+
25
+ | # | route | what it gives, what it costs |
26
+ |---|---|---|
27
+ | 1 | result schemas only, verbs stay prose | shapes checkable, operations still hand-read |
28
+ | 2 | **operations manifest**: verb → inputs → result → companion, in JSON | language-neutral; any engine derives its own binding |
29
+ | 3 | **fixtures bound to verbs** in the manifest | the conformance replay becomes the behavioural contract |
30
+ | 4 | hand-written `.d.ts` in colregs | TypeScript-only; the transcription the engine's generator exists to avoid |
31
+ | 5 | `x-operation` keywords inside the existing schemas | option 2 spread over eleven files |
32
+ | 6 | OpenAPI / AsyncAPI | a projection generable *from* 2, not a source |
33
+ | 7 | Protobuf / Smithy | model operations natively; a toolchain a data-only package does not have |
34
+ | 8 | TypeSpec | one source projecting JSON Schema and OpenAPI; same toolchain cost |
35
+ | 9 | a separate `colregs-api` package | a third release train for two files |
36
+ | 10 | status quo | the drift above |
37
+
38
+ ## Decision
39
+
40
+ 1. **`data/operations.json` is the interface.** One operation per verb ADR
41
+ 0011 and ADR 0012 name, each with positional `inputs` (name and schema),
42
+ an `output` schema, the entry-id `companion` verb where one exists, and
43
+ the `fixtures` that exercise it. `schema/operations.schema.json` checks
44
+ its shape; `test/data.test.mjs` checks that every reference resolves.
45
+ 2. **Result envelopes are schemas under `schema/`:** `display-evaluation`,
46
+ `encounter-evaluation`, `conduct-evaluation`, `rule2-departure-finding`,
47
+ transcribed from the engine's `src/types.ts` at colregs-engine 0.1.5.
48
+ JSON-Schema-expressible only: `Record<EntryId, Modality>` becomes
49
+ `patternProperties`, the `EntryId` and `ParagraphCite` aliases become
50
+ `$defs`, the two deprecated camelCase aliases are marked `deprecated` and
51
+ optional. From here the schema is the normative statement of each
52
+ envelope; the TypeScript blocks in ADR 0011 §4 and ADR 0012 §2–4 are
53
+ illustrations of record, and an envelope changes here first, the engine
54
+ second.
55
+ 3. **Inputs get schemas too:** `fact-record`, `situation`, `trace`,
56
+ `rule2-departure-model`. The two fixture schemas `$ref` the first two
57
+ for a case's `facts` and `situation` instead of carrying a copy; a fixture
58
+ case is the verb's input, and the tests validate every bound case against
59
+ it.
60
+ 4. **Schema files compose by `$ref`,** relative to their `$id`
61
+ (`applicability.schema.json#/$defs/ruleId`). ADR 0006's "no cross-file
62
+ references" is about data references — cite to `rules.json` — which stay in
63
+ the tests; a `$ref` between two schema files is one shape reused, not a
64
+ data reference. The suite registers every schema by `$id` before compiling.
65
+ What every envelope shares — the `colregs` stamp, `provenance`, the
66
+ `ruleId` and `paragraphCite` vocabularies — is `schema/evaluation.schema.json`,
67
+ `$defs` only, so moving one later is never a two-repository change.
68
+ 5. **A fixture file is bound to a verb** by `fixtures[].file` and
69
+ `case_inputs`, one case key per positional input, with the answer under
70
+ `expect`: the companion's entry ids unless the binding names another
71
+ schema. A case's `status` and `jurisdiction` are the fixture file's own.
72
+ Every file under `fixtures/` must be bound; `evaluateConduct` and
73
+ `evaluateRule2Departure` bind nothing yet, and say so with an empty list.
74
+ 6. **Not in the manifest:** trailing options a binding accepts (`opts.data`),
75
+ which verbs are built, and what a verb throws. Build status is ADR 0011's
76
+ and ADR 0012's tables; errors are the binding's.
77
+
78
+ ## Consequences
79
+
80
+ - Ten new files under `schema/`. The engine's `generate-schema-types.ts`
81
+ throws on any schema its `ROOT_NAMES` does not list, so the next colregs
82
+ bump fails its build until colregs-engine#86 lands: emit
83
+ `interface ColregsEngine` from `operations.json`, `satisfies ColregsEngine`
84
+ at the engine's root, settle the `fact-record.ts` / `situation.ts`
85
+ collision with the types it derives from `facts.json` today, and validate
86
+ real output against these schemas for every fixture case.
87
+ - A YAML rendering of the manifest is lossless; XML is a generated view (JSON
88
+ Schema to XSD). Authoring the interface in TypeScript first is the one route
89
+ that forecloses both.
90
+ - The 2026-09-16 hand-off named the finding's schema `rule2-departure`; it
91
+ lands as `rule2-departure-finding` beside `rule2-departure-model`, since
92
+ the verb has two shapes to name.
93
+
94
+ ## Register
95
+
96
+ | item | level | what would settle it |
97
+ |---|---|---|
98
+ | Options 2 and 3, not 1 or 4–10 | ✎ | this ADR accepted |
99
+ | Manifest shape: positional `inputs`, `output`, `companion`, `fixtures[].case_inputs` and `expect` | ✎ | the engine generator consuming it; the first conduct or Rule 2 fixture |
100
+ | Envelope schemas transcribed from `types.ts`, envelope changes land here first | ✎ | the first envelope change after the follow-up |
101
+ | Cross-file `$ref` between schema files | ✎ | a consumer whose validator cannot register a schema set |
102
+ | Deprecated aliases optional in the schema, required in the engine | ✎ | the engine dropping them |
103
+ | `opts` and thrown errors stay out of the manifest | ✎ | a second binding needing either |