colregs 0.3.2 → 0.3.3
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/README.md +16 -8
- package/data/applicability.json +25 -6
- package/data/i18n/en.json +71 -5
- package/data/i18n/fi.json +2 -2
- package/data/lights.json +10 -20
- package/data/version.json +1 -1
- package/docs/adr/0010-text-withheld-jurisdictions.md +2 -0
- package/docs/adr/0018-jurisdiction-delta-is-a-merge-patch.md +102 -0
- package/docs/adr/0019-relation-reach-and-import-reads.md +106 -0
- package/docs/budgets.json +2 -0
- package/docs/decisions.md +2 -0
- package/docs/identifiers.md +4 -1
- package/docs/part-b-invariants.md +23 -21
- package/docs/requirements.md +65 -65
- package/fixtures/applicability-fixtures.json +42 -0
- package/package.json +4 -1
- package/schema/applicability.schema.json +15 -0
- package/schema/i18n-catalog.schema.json +25 -4
- package/schema/lights.schema.json +2 -3
package/README.md
CHANGED
|
@@ -56,7 +56,10 @@ lights and shapes, Part D sound and light signals, and Annex I geometry.
|
|
|
56
56
|
[`docs/requirements.md`](docs/requirements.md) is the numbered contract for
|
|
57
57
|
all of it. One case the Convention cannot state, a vessel made fast to a
|
|
58
58
|
mooring buoy, lives only under `us/inland`
|
|
59
|
-
([ADR 0008](docs/adr/0008-mooring-buoy-modifier.md))
|
|
59
|
+
([ADR 0008](docs/adr/0008-mooring-buoy-modifier.md)); two the Inland Rules
|
|
60
|
+
deliberately lack, Rule 28 and 23(d)(ii), are tombstoned there
|
|
61
|
+
([ADR 0018](docs/adr/0018-jurisdiction-delta-is-a-merge-patch.md)). That is
|
|
62
|
+
the whole of `us/inland` today: four records, not a model of the Inland Rules.
|
|
60
63
|
|
|
61
64
|
## The layers
|
|
62
65
|
|
|
@@ -104,7 +107,12 @@ same thing.
|
|
|
104
107
|
|
|
105
108
|
**Jurisdiction is a dimension, not a fork.** Every record carries a
|
|
106
109
|
`jurisdiction`: `intl`, or `<country-or-body>/<waters>` as a delta on it.
|
|
107
|
-
Entries a jurisdiction doesn't override are inherited, not restated.
|
|
110
|
+
Entries a jurisdiction doesn't override are inherited, not restated. The
|
|
111
|
+
delta is an [RFC 7396](https://www.rfc-editor.org/rfc/rfc7396) merge patch
|
|
112
|
+
over `intl` keyed by entry id: the jurisdiction's own entries are its keys,
|
|
113
|
+
its `suppressions` are the keys set to `null`, and silence inherits
|
|
114
|
+
([ADR 0018](docs/adr/0018-jurisdiction-delta-is-a-merge-patch.md)). Any
|
|
115
|
+
conformant merge-patch library reproduces the resolved rule set.
|
|
108
116
|
|
|
109
117
|
**Predicates, not enumerations.** Gates are `fact:length_m < 7`, never a
|
|
110
118
|
pre-built list of configurations. Enumerated tables are where prior art
|
|
@@ -136,12 +144,12 @@ paragraphs prevails is a relation.
|
|
|
136
144
|
|
|
137
145
|
| relation | meaning |
|
|
138
146
|
|---|---|
|
|
139
|
-
| `rel:includes` | import the referenced entry's
|
|
140
|
-
| `rel:conditional_includes` | import lights when the stated `when` holds; `one_of` is a set of legal alternatives |
|
|
141
|
-
| `rel:in_lieu_of` | this entry's lights replace the referenced entries' lights |
|
|
142
|
-
| `rel:excludes` | must not be shown together: a pick-one between alternatives, never one obligation vetoing another |
|
|
143
|
-
| `rel:exempts` | the referenced requirement does not apply (30(e)) |
|
|
144
|
-
| `rel:overrides` | this paragraph's requirement prevails over the referenced one's when both apply (Rule 18; Rule 26(a) over Rule 30's anchor lights) |
|
|
147
|
+
| `rel:includes` | import the referenced entry's lights, their modality and its scalar gates — never its axes (ADR 0019) |
|
|
148
|
+
| `rel:conditional_includes` | import lights when the stated `when` holds; `one_of` is a set of legal alternatives, exactly one per display, or none under a `may` carrier |
|
|
149
|
+
| `rel:in_lieu_of` | this entry's lights replace the referenced entries' lights; two entries replacing overlapping sets are alternatives to each other |
|
|
150
|
+
| `rel:excludes` | must not be shown together: a pick-one between alternatives, never one obligation vetoing another; a constraint on one display, never a removal |
|
|
151
|
+
| `rel:exempts` | the referenced requirement does not apply (30(e)); reaches an entry in force, never an import |
|
|
152
|
+
| `rel:overrides` | this paragraph's requirement prevails over the referenced one's when both apply (Rule 18; Rule 26(a) over Rule 30's anchor lights); reaches an entry in force, never an import |
|
|
145
153
|
|
|
146
154
|
Modality is `modality:shall`, `modality:may`, `modality:shall-if-practicable`, `modality:shall-not`,
|
|
147
155
|
`modality:shall-not-impede`, or `modality:conditional` with a `modality_by` table when it turns
|
package/data/applicability.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"conditions": "night (Rule 20(b): sunset to sunrise), and by 20(c) in restricted visibility by day. Day shapes are not in this release.",
|
|
3
3
|
"relations": {
|
|
4
|
-
"rel:includes": "import another entry's lights
|
|
5
|
-
"rel:conditional_includes": "import lights when the stated `when` holds; `one_of` is a set of legal alternatives among which the vessel picks one",
|
|
6
|
-
"rel:in_lieu_of": "this entry's lights replace the referenced entries' lights",
|
|
7
|
-
"rel:excludes": "the referenced entries must not be shown at the same time",
|
|
8
|
-
"rel:exempts": "the referenced entries are not required",
|
|
9
|
-
"rel:overrides": "the superiority relation: the referencing norm's obligation displaces the referenced norm's when both apply"
|
|
4
|
+
"rel:includes": "import another entry's lights, their modality and its scalar gates, never its axes (ADR 0019)",
|
|
5
|
+
"rel:conditional_includes": "import lights when the stated `when` holds; `one_of` is a set of legal alternatives among which the vessel picks exactly one, or none under a `may` carrier (ADR 0019)",
|
|
6
|
+
"rel:in_lieu_of": "this entry's lights replace the referenced entries' lights; two entries whose targets intersect are alternatives to each other (ADR 0019)",
|
|
7
|
+
"rel:excludes": "the referenced entries must not be shown at the same time; a constraint on one display, never a removal (ADR 0019)",
|
|
8
|
+
"rel:exempts": "the referenced entries are not required; reaches an entry in force, never an import (ADR 0019)",
|
|
9
|
+
"rel:overrides": "the superiority relation: the referencing norm's obligation displaces the referenced norm's when both apply; reaches an entry in force, never an import (ADR 0019)"
|
|
10
10
|
},
|
|
11
11
|
"modalities": {
|
|
12
12
|
"modality:shall": "mandatory",
|
|
@@ -110,6 +110,11 @@
|
|
|
110
110
|
"cite": "14(a)",
|
|
111
111
|
"what": "When two power-driven vessels are meeting on reciprocal or nearly reciprocal courses so as to involve risk of collision each shall alter her course to starboard so that each shall pass on the port side of the other.",
|
|
112
112
|
"why": "`category:conduct`, not `category:classification`: the paragraph states the situation in words and prescribes the action, and 14(b) is the paragraph that deems the situation to exist -- entry rule:14b cites it. The action is monitored over a trace rather than evaluated at a point (ADR 0005 sec. 1), and no conduct entry shape exists yet. Q-45."
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"cite": "28",
|
|
116
|
+
"what": "Under us/inland a record carrying activity:cbd selects nothing: rule:28 is suppressed and the Rule 23(a) entries it imported read activity:none.",
|
|
117
|
+
"why": "'Constrained by her draft' is not an Inland status, so the record is out of vocabulary there and the package does not pick a reading for it; a consumer that decodes SignalK for inland waters maps it to activity:none (ADR 0018)."
|
|
113
118
|
}
|
|
114
119
|
],
|
|
115
120
|
"represented_paragraphs": [
|
|
@@ -2176,5 +2181,19 @@
|
|
|
2176
2181
|
"modality": "modality:may",
|
|
2177
2182
|
"note": "The under-50 m alternative, on the same 33 CFR 90.5 deeming that brings rule:30a:mooring_buoy in. It is a separate entry because widening rule:30b's predicate would edit an intl entry to carry a national delta, which REQ-SCOPE-4 forbids."
|
|
2178
2183
|
}
|
|
2184
|
+
],
|
|
2185
|
+
"suppressions": [
|
|
2186
|
+
{
|
|
2187
|
+
"jurisdiction": "us/inland",
|
|
2188
|
+
"suppresses": "rule:28",
|
|
2189
|
+
"cite": "28",
|
|
2190
|
+
"why": "33 CFR 83.28 is [Reserved]: 'constrained by her draft' is not an Inland Rules status, so the Rule 28 signal is deliberately absent under us/inland rather than untranscribed (Q-11, ADR 0018)."
|
|
2191
|
+
},
|
|
2192
|
+
{
|
|
2193
|
+
"jurisdiction": "us/inland",
|
|
2194
|
+
"suppresses": "rule:23d_ii",
|
|
2195
|
+
"cite": "23(d)(ii)",
|
|
2196
|
+
"why": "33 CFR 83.23(d) is a single paragraph with no counterpart to the under-7 m, 7 kn exception of 23(d)(ii); the exception is deliberately absent under us/inland (Q-11, ADR 0018)."
|
|
2197
|
+
}
|
|
2179
2198
|
]
|
|
2180
2199
|
}
|
package/data/i18n/en.json
CHANGED
|
@@ -7,21 +7,87 @@
|
|
|
7
7
|
"reviewed_by": [
|
|
8
8
|
"mark-brannan"
|
|
9
9
|
],
|
|
10
|
-
"review_date": "2026-09-
|
|
10
|
+
"review_date": "2026-09-16"
|
|
11
11
|
},
|
|
12
12
|
"strings": {
|
|
13
|
-
"
|
|
13
|
+
"lights": {
|
|
14
14
|
"light:masthead": "Masthead light",
|
|
15
|
+
"light:sidelights": "Sidelights",
|
|
15
16
|
"light:sidelight_starboard": "Starboard sidelight",
|
|
16
17
|
"light:sidelight_port": "Port sidelight",
|
|
17
18
|
"light:sternlight": "Sternlight",
|
|
18
19
|
"light:towing": "Towing light",
|
|
19
|
-
"light:all_round": "All-round light"
|
|
20
|
+
"light:all_round": "All-round light",
|
|
21
|
+
"light:flashing": "Flashing light",
|
|
22
|
+
"light:torch": "Electric torch or lighted lantern",
|
|
23
|
+
"light:deck_lights": "Working or equivalent lights"
|
|
20
24
|
},
|
|
21
|
-
"
|
|
25
|
+
"modalities": {
|
|
22
26
|
"modality:shall": "Required",
|
|
23
27
|
"modality:may": "Permitted",
|
|
24
|
-
"modality:shall-if-practicable": "Required if practicable"
|
|
28
|
+
"modality:shall-if-practicable": "Required if practicable",
|
|
29
|
+
"modality:conditional": "Conditional",
|
|
30
|
+
"modality:exempt": "Exempt",
|
|
31
|
+
"modality:shall-not": "Prohibited",
|
|
32
|
+
"modality:shall-not-impede": "Must not impede"
|
|
33
|
+
},
|
|
34
|
+
"roles": {
|
|
35
|
+
"role:give-way": "Give way",
|
|
36
|
+
"role:stand-on": "Stand on",
|
|
37
|
+
"role:shall-not-impede": "Must not impede",
|
|
38
|
+
"role:keep-clear": "Keep clear",
|
|
39
|
+
"role:none": "No role"
|
|
40
|
+
},
|
|
41
|
+
"encounters": {
|
|
42
|
+
"encounter:head-on": "Head-on",
|
|
43
|
+
"encounter:crossing": "Crossing",
|
|
44
|
+
"encounter:overtaking": "Overtaking",
|
|
45
|
+
"encounter:none": "No encounter"
|
|
46
|
+
},
|
|
47
|
+
"jurisdictions": {
|
|
48
|
+
"intl": "International",
|
|
49
|
+
"us/inland": "US Inland"
|
|
50
|
+
},
|
|
51
|
+
"facts": {
|
|
52
|
+
"propulsion:power": "Power-driven",
|
|
53
|
+
"propulsion:sail": "Sailing",
|
|
54
|
+
"propulsion:oars": "Oars",
|
|
55
|
+
"activity:none": "None",
|
|
56
|
+
"activity:fishing": "Fishing",
|
|
57
|
+
"activity:trawling": "Trawling",
|
|
58
|
+
"activity:towing": "Towing",
|
|
59
|
+
"activity:pushing": "Pushing",
|
|
60
|
+
"activity:being_towed": "Being towed",
|
|
61
|
+
"activity:nuc": "Not under command",
|
|
62
|
+
"activity:ram": "Restricted in her ability to manoeuvre",
|
|
63
|
+
"activity:ram_underwater": "Restricted in her ability to manoeuvre, engaged in underwater operations",
|
|
64
|
+
"activity:cbd": "Constrained by her draught",
|
|
65
|
+
"activity:mine": "Engaged in minesweeping",
|
|
66
|
+
"activity:pilot": "Engaged on pilotage duty",
|
|
67
|
+
"activity:diving": "Engaged in diving operations",
|
|
68
|
+
"position:underway": "Underway",
|
|
69
|
+
"position:anchored": "At anchor",
|
|
70
|
+
"position:aground": "Aground",
|
|
71
|
+
"position:moored": "Moored",
|
|
72
|
+
"rule18_class:nuc": "Not under command",
|
|
73
|
+
"rule18_class:ram": "Restricted in ability to manoeuvre",
|
|
74
|
+
"rule18_class:fishing": "Fishing",
|
|
75
|
+
"rule18_class:wig": "Wing-in-ground craft",
|
|
76
|
+
"rule18_class:cbd": "Constrained by draught",
|
|
77
|
+
"rule18_class:sail": "Sailing vessel",
|
|
78
|
+
"rule18_class:power": "Power-driven vessel",
|
|
79
|
+
"dynamics:tanker": "Tanker",
|
|
80
|
+
"dynamics:cargo": "Cargo ship",
|
|
81
|
+
"dynamics:ferry": "Ferry",
|
|
82
|
+
"dynamics:fishing": "Fishing vessel",
|
|
83
|
+
"dynamics:yacht": "Yacht",
|
|
84
|
+
"dynamics:rib": "RIB",
|
|
85
|
+
"dynamics:unknown": "Unknown",
|
|
86
|
+
"obstruction_side:port": "Port",
|
|
87
|
+
"obstruction_side:starboard": "Starboard",
|
|
88
|
+
"wind_side:port": "Port",
|
|
89
|
+
"wind_side:starboard": "Starboard",
|
|
90
|
+
"wind_side:unknown": "Unknown"
|
|
25
91
|
}
|
|
26
92
|
}
|
|
27
93
|
}
|
package/data/i18n/fi.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"review_date": "2026-09-15"
|
|
9
9
|
},
|
|
10
10
|
"strings": {
|
|
11
|
-
"
|
|
11
|
+
"lights": {
|
|
12
12
|
"light:masthead": "Mastovalo",
|
|
13
13
|
"light:sidelight_starboard": "Oikea sivuvalo",
|
|
14
14
|
"light:sidelight_port": "Vasen sivuvalo",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"light:towing": "Hinausvalo",
|
|
17
17
|
"light:all_round": "Ympäri näköpiirin näkyvä valo"
|
|
18
18
|
},
|
|
19
|
-
"
|
|
19
|
+
"modalities": {
|
|
20
20
|
"modality:shall": "Pakollinen",
|
|
21
21
|
"modality:may": "Sallittu",
|
|
22
22
|
"modality:shall-if-practicable": "Pakollinen, jos mahdollista"
|
package/data/lights.json
CHANGED
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"lights": {
|
|
10
10
|
"light:masthead": {
|
|
11
|
-
"
|
|
12
|
-
"colregs_term": "masthead light",
|
|
11
|
+
"term": "masthead light",
|
|
13
12
|
"cite": "21(a)",
|
|
14
13
|
"color": "white",
|
|
15
14
|
"character": "steady",
|
|
@@ -22,8 +21,7 @@
|
|
|
22
21
|
"rule21": true
|
|
23
22
|
},
|
|
24
23
|
"light:sidelights": {
|
|
25
|
-
"
|
|
26
|
-
"colregs_term": "sidelights",
|
|
24
|
+
"term": "sidelights",
|
|
27
25
|
"cite": "21(b)",
|
|
28
26
|
"character": "steady",
|
|
29
27
|
"composite": true,
|
|
@@ -36,8 +34,7 @@
|
|
|
36
34
|
"rule21": true
|
|
37
35
|
},
|
|
38
36
|
"light:sidelight_starboard": {
|
|
39
|
-
"
|
|
40
|
-
"colregs_term": "sidelight",
|
|
37
|
+
"term": "sidelight",
|
|
41
38
|
"cite": "21(b)",
|
|
42
39
|
"color": "green",
|
|
43
40
|
"character": "steady",
|
|
@@ -50,8 +47,7 @@
|
|
|
50
47
|
"rule21": true
|
|
51
48
|
},
|
|
52
49
|
"light:sidelight_port": {
|
|
53
|
-
"
|
|
54
|
-
"colregs_term": "sidelight",
|
|
50
|
+
"term": "sidelight",
|
|
55
51
|
"cite": "21(b)",
|
|
56
52
|
"color": "red",
|
|
57
53
|
"character": "steady",
|
|
@@ -64,8 +60,7 @@
|
|
|
64
60
|
"rule21": true
|
|
65
61
|
},
|
|
66
62
|
"light:sternlight": {
|
|
67
|
-
"
|
|
68
|
-
"colregs_term": "sternlight",
|
|
63
|
+
"term": "sternlight",
|
|
69
64
|
"cite": "21(c)",
|
|
70
65
|
"color": "white",
|
|
71
66
|
"character": "steady",
|
|
@@ -78,8 +73,7 @@
|
|
|
78
73
|
"rule21": true
|
|
79
74
|
},
|
|
80
75
|
"light:towing": {
|
|
81
|
-
"
|
|
82
|
-
"colregs_term": "towing light",
|
|
76
|
+
"term": "towing light",
|
|
83
77
|
"cite": "21(d)",
|
|
84
78
|
"color": "yellow",
|
|
85
79
|
"character": "steady",
|
|
@@ -92,8 +86,7 @@
|
|
|
92
86
|
"rule21": true
|
|
93
87
|
},
|
|
94
88
|
"light:all_round": {
|
|
95
|
-
"
|
|
96
|
-
"colregs_term": "all-round light",
|
|
89
|
+
"term": "all-round light",
|
|
97
90
|
"cite": "21(e)",
|
|
98
91
|
"color": null,
|
|
99
92
|
"color_note": "colour is fixed by the citing paragraph, not by Rule 21",
|
|
@@ -106,8 +99,7 @@
|
|
|
106
99
|
"rule21": true
|
|
107
100
|
},
|
|
108
101
|
"light:flashing": {
|
|
109
|
-
"
|
|
110
|
-
"colregs_term": "flashing light",
|
|
102
|
+
"term": "flashing light",
|
|
111
103
|
"cite": "21(f)",
|
|
112
104
|
"color": null,
|
|
113
105
|
"color_note": "colour is fixed by the citing paragraph",
|
|
@@ -122,8 +114,7 @@
|
|
|
122
114
|
"rule21": true
|
|
123
115
|
},
|
|
124
116
|
"light:torch": {
|
|
125
|
-
"
|
|
126
|
-
"colregs_term": "electric torch or lighted lantern",
|
|
117
|
+
"term": "electric torch or lighted lantern",
|
|
127
118
|
"cite": "25(d)(i)",
|
|
128
119
|
"color": "white",
|
|
129
120
|
"character": "shown in sufficient time to prevent collision",
|
|
@@ -133,8 +124,7 @@
|
|
|
133
124
|
"note": "Not a fixed navigation light: it is held ready at hand and shown on approach. It has no prescribed arc, range or position."
|
|
134
125
|
},
|
|
135
126
|
"light:deck_lights": {
|
|
136
|
-
"
|
|
137
|
-
"colregs_term": "working or equivalent lights",
|
|
127
|
+
"term": "working or equivalent lights",
|
|
138
128
|
"cite": "30(c)",
|
|
139
129
|
"color": null,
|
|
140
130
|
"character": "steady",
|
package/data/version.json
CHANGED
|
@@ -102,6 +102,8 @@ data one.
|
|
|
102
102
|
international law where a national body deliberately has none. That is
|
|
103
103
|
about deltas, not licences; it survives this ADR untouched and is the live
|
|
104
104
|
blocker on CEVNI. Read the two together or the wrong one gets blamed.
|
|
105
|
+
*(ADR 0018 has since supplied that mechanism; the bar is now "land with
|
|
106
|
+
your tombstones", not "wait".)*
|
|
105
107
|
- `schema/rules.schema.json` carries the conditional: `text` required unless
|
|
106
108
|
`text_status` is `withheld`, in which case it is forbidden and
|
|
107
109
|
`withheld_reason` is required. Only `withheld_reason` is refused on a
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# ADR 0018 — A jurisdiction delta is an RFC 7396 merge patch, and a tombstone is its `null`
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-16
|
|
4
|
+
Status: proposed — merging this PR is the ruling; a revert undoes it
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
REQ-SCOPE-3 says a jurisdiction is a delta over `intl`: entries it does not
|
|
9
|
+
mention are inherited. Q-11 found that unsafe as stated. 33 CFR 83 leaves
|
|
10
|
+
Rule 28 "[Reserved]" and has no counterpart to 23(d)(ii), so a `us/inland`
|
|
11
|
+
delta that was merely silent there would inherit `rule:28` and assert an
|
|
12
|
+
international obligation the Inland Rules deliberately do not carry. Both
|
|
13
|
+
absences are verified against the primary source (Cornell LII's copy of
|
|
14
|
+
33 CFR 83: "§ 83.28 [Reserved] (Rule 28)"; 83.23(d) is a single paragraph
|
|
15
|
+
with no subparagraphs). Q-11 asked for tombstones — "this entry deliberately
|
|
16
|
+
does not exist here", distinguishable from "not yet transcribed" — and held
|
|
17
|
+
every non-`intl` jurisdiction until the mechanism existed. Issue #45 asked,
|
|
18
|
+
separately, which standard encoding the delta should follow.
|
|
19
|
+
|
|
20
|
+
Both questions have been waiting on a second jurisdiction to force them.
|
|
21
|
+
Issue #137 records that nothing was proposing one. `us/inland` already exists
|
|
22
|
+
with two add-only entries under ADR 0008's carve-out, and the two absences
|
|
23
|
+
above are the counterexample that carve-out was left open for: a delta that
|
|
24
|
+
must suppress, not only add.
|
|
25
|
+
|
|
26
|
+
## Decision
|
|
27
|
+
|
|
28
|
+
1. **The delta semantics are RFC 7396, JSON Merge Patch.** A jurisdiction's
|
|
29
|
+
delta over `intl` is, in meaning, a merge-patch document keyed by entry
|
|
30
|
+
id: present keys are that jurisdiction's own entries, `null` keys are its
|
|
31
|
+
tombstones, and every key the patch does not mention is inherited. Any
|
|
32
|
+
conformant merge-patch implementation applied to the `intl` entries by
|
|
33
|
+
id reproduces the jurisdiction's resolved rule set. RFC 6902 is not
|
|
34
|
+
adopted: an operation script with JSON Pointer paths would duplicate the
|
|
35
|
+
id addressing `docs/identifiers.md` already provides.
|
|
36
|
+
|
|
37
|
+
2. **Storage stays columnar; the patch is derived.** Entries keep their
|
|
38
|
+
`jurisdiction` field in the one `entries[]` table, as ADR 0008 laid them
|
|
39
|
+
down. Tombstones are a second top-level table, `suppressions[]`, one
|
|
40
|
+
record per (jurisdiction, entry): `jurisdiction`, `suppresses` (the
|
|
41
|
+
`intl` entry id), `cite` (the paragraph that has no counterpart) and
|
|
42
|
+
`why` (one sentence and the source). `test/data.test.mjs` builds the
|
|
43
|
+
merge patch from those two tables, applies it with a literal RFC 7396
|
|
44
|
+
implementation, and asserts the result equals the evaluator's
|
|
45
|
+
jurisdiction filter. That test is the statement that storage and
|
|
46
|
+
semantics agree; if they ever diverge, the test names which.
|
|
47
|
+
|
|
48
|
+
3. **Replace is suppress plus add.** A jurisdiction that reads a paragraph
|
|
49
|
+
differently tombstones the `intl` entry and adds its own, under its own
|
|
50
|
+
id (ADR 0015 — the ids are distinct because the norms are). There is no
|
|
51
|
+
third operation and no in-place edit of an `intl` entry: REQ-SCOPE-4
|
|
52
|
+
holds.
|
|
53
|
+
|
|
54
|
+
4. **The array caveat is accepted and irrelevant here.** Merge Patch
|
|
55
|
+
replaces an array wholesale. The patch is keyed at entry granularity, so
|
|
56
|
+
the only arrays it ever touches are inside a whole entry being added, and
|
|
57
|
+
an entry is small. A jurisdiction never patches one element of an
|
|
58
|
+
inherited entry's `lights`; it replaces the entry (point 3).
|
|
59
|
+
|
|
60
|
+
5. **Only an inherited entry can be tombstoned.** A tombstone naming a
|
|
61
|
+
jurisdiction's own entry, or another jurisdiction's, is rejected by test:
|
|
62
|
+
the first is a deletion, the second was never in force.
|
|
63
|
+
|
|
64
|
+
6. **A tombstone is verified the way an entry is.** Each needs one fixture
|
|
65
|
+
under its jurisdiction whose facts match the suppressed entry's predicate
|
|
66
|
+
and whose expectation omits it, and one `intl` fixture showing the entry
|
|
67
|
+
in force at the base (REQ-VERIFY-3, both sides).
|
|
68
|
+
|
|
69
|
+
7. **Two tombstones land with this ADR**, both verified: `us/inland`
|
|
70
|
+
suppresses `rule:28` (Rule 28 "[Reserved]") and `rule:23d_ii` (no
|
|
71
|
+
counterpart to the under-7 m, 7 kn exception). They are enough to
|
|
72
|
+
exercise inherit and delete; no verified replace case exists in Part C
|
|
73
|
+
yet, so that branch of point 3 is stated, schema-ready and unexercised.
|
|
74
|
+
|
|
75
|
+
## Consequences
|
|
76
|
+
|
|
77
|
+
- **Q-11 is answered and its hold is lifted.** REQ-SCOPE-3's "no non-`intl`
|
|
78
|
+
jurisdiction lands before an explicit suppression mechanism exists" is
|
|
79
|
+
discharged by this mechanism; a jurisdiction lands with its tombstones or
|
|
80
|
+
it does not land. ADR 0008's add-only carve-out survives as the trivial
|
|
81
|
+
case: a delta with no tombstones is a merge patch with no `null`s.
|
|
82
|
+
- **Issue #45 is closed by this ADR.** README names the RFC beside the
|
|
83
|
+
jurisdiction paragraph so a consumer knows which library semantics
|
|
84
|
+
reproduce the inheritance.
|
|
85
|
+
- **A suppressed entry can leave a record out of vocabulary.** Under
|
|
86
|
+
`us/inland` a record carrying `activity:cbd` now selects nothing:
|
|
87
|
+
`rule:28` is gone and the Rule 23(a) entries it imported read
|
|
88
|
+
`activity:none`. That is the honest answer — "constrained by her draft" is
|
|
89
|
+
not an Inland status — and the package does not pick a reading. It is
|
|
90
|
+
recorded in `known_omissions` and pinned by a fixture. Whether the fact
|
|
91
|
+
vocabulary should carry per-jurisdiction membership so a consumer's
|
|
92
|
+
decode can be checked is a new question, not decided here.
|
|
93
|
+
- **`rule:23d_i` cites a path Inland does not spell.** 33 CFR 83.23(d) has
|
|
94
|
+
no `(i)`; the content sits at bare `83.23(d)`. The entry is inherited
|
|
95
|
+
correctly — the norm is the same — and the citation-spelling question is
|
|
96
|
+
GATE-1's, untouched here.
|
|
97
|
+
- **GATE-1, Q-10 and Q-8 are not taken.** Q-11 named a "second-jurisdiction
|
|
98
|
+
bundle"; this ADR takes only the item that gated data. The others stay
|
|
99
|
+
open on their own triggers.
|
|
100
|
+
- **Cost to reverse.** Delete `suppressions[]` from data, schema and the
|
|
101
|
+
three tests; the two fixtures fail and are deleted with them. No consumer
|
|
102
|
+
reads the table yet. Pre-1.0, a revert.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# ADR 0019 — What a relation reaches, and what an import reads
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-16
|
|
4
|
+
Status: proposed — merging this PR is the ruling; a revert undoes it
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
REQ-MODEL-7 and the README define the six relation verbs by meaning and
|
|
9
|
+
leave composition to the consumer (REQ-CONS-3). colregs-engine#33 checked
|
|
10
|
+
the seven composition decisions its evaluator makes where the data is
|
|
11
|
+
silent. ADR 0007 settled the largest, direction: a directed "this one
|
|
12
|
+
prevails" is `rel:overrides`, and `rel:excludes` is pick-one between
|
|
13
|
+
alternatives. Three readings of the verbs remain stated only in
|
|
14
|
+
colregs-engine's `docs/engine-notes.md` (items 2, 3 and 5) and pinned only
|
|
15
|
+
by its tests: how far a relation reaches, what a `one_of` yields, and which
|
|
16
|
+
part of a referenced entry's predicate an import consults. Each is a
|
|
17
|
+
reading of the Rules, and the package that carries the Rules should say it.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
1. **Displacing relations reach entries in force; composing relations
|
|
22
|
+
reach what is exhibited.** An entry is *in force* when its own predicate
|
|
23
|
+
holds. `rel:overrides` and `rel:exempts` act on entries in force and on
|
|
24
|
+
nothing else: an import is not in force, it is a light set its carrier
|
|
25
|
+
exhibits, and the carrier is what a displacing relation must name.
|
|
26
|
+
`rel:in_lieu_of`, `rel:excludes`, `rel:includes` and
|
|
27
|
+
`rel:conditional_includes` act on what one display may contain, imports
|
|
28
|
+
included, and remove nothing from the set of entries in force. Direction
|
|
29
|
+
is carried by the verb, never by modality: an override fires from a
|
|
30
|
+
forceful entry (ADR 0007), an exemption from a `modality:exempt` one,
|
|
31
|
+
and `rel:excludes` fires from nowhere — it is symmetric and already
|
|
32
|
+
barred to forceful entries by CI (`test/data.test.mjs`, "rel:excludes is
|
|
33
|
+
reciprocated"). A consumer that infers a veto from an excluder's modality
|
|
34
|
+
is reading a relation the data does not carry.
|
|
35
|
+
|
|
36
|
+
2. **A `one_of` yields exactly one option per display, or none when the
|
|
37
|
+
carrier is `modality:may`.** Under a `may` carrier the chosen option is
|
|
38
|
+
exhibited in lieu of the carrier's own lights, and the none choice
|
|
39
|
+
exhibits them (25(d)(ii): the sailing lights, or failing them the torch).
|
|
40
|
+
An option already in force by its own predicate discharges the set: the
|
|
41
|
+
carrier imports nothing, and the option composes as the entry it is. A
|
|
42
|
+
vessel restricted in her ability to manoeuvre at anchor shows 30(a) or
|
|
43
|
+
30(b) because 30(a) and 30(b) apply to her, not because 27(b)(iv)
|
|
44
|
+
redirects her to them. Every choice is returned (REQ-MODEL-8).
|
|
45
|
+
|
|
46
|
+
3. **An import reads the referenced entry's lights, their modality, and
|
|
47
|
+
its scalar gates — never its axes.** An import is a `rel:includes`, the
|
|
48
|
+
`rel:includes` of a `rel:conditional_includes` branch, or a `one_of`
|
|
49
|
+
option. `facts.json`'s `axes` and `modifiers` are the field: a key
|
|
50
|
+
declared there is the vessel's situation, and the carrier's redirect has
|
|
51
|
+
already placed her in it — 30(d) sends a vessel aground to "the lights
|
|
52
|
+
prescribed in paragraph (a) or (b)", and that she is not at anchor is the
|
|
53
|
+
premise of the redirect, not a gate on it. A key not declared there is a
|
|
54
|
+
fact the redirect does not alter, and the referenced entry's condition
|
|
55
|
+
on it binds: 30(b)'s "less than 50 metres" binds a vessel aground as it
|
|
56
|
+
binds one at anchor, and 25(b)'s "less than 20 metres" binds a vessel
|
|
57
|
+
under oars. Modality is the referenced entry's, resolved against the
|
|
58
|
+
vessel (Rule 28's three reds are `may`; the Rule 23 lights it imports
|
|
59
|
+
stay `shall`). A gate the carrier needs that the source does not carry is
|
|
60
|
+
written on the carrier's branch `when`, as 27(f) and 29(a) do; nothing
|
|
61
|
+
is inferred from a source's axes in either direction. REQ-MODEL-7's
|
|
62
|
+
"lights only, never its predicate" is amended to say this.
|
|
63
|
+
|
|
64
|
+
4. **Two entries in force whose `rel:in_lieu_of` targets intersect are
|
|
65
|
+
alternatives to each other** and never share a display. This is an
|
|
66
|
+
invariant of the verb, not a declared pair list. The two pairs the data
|
|
67
|
+
can hold in force together read that way in the text: 23(d)(ii)'s
|
|
68
|
+
display is 23(d)(i)'s with the sidelights made practicable-only, and
|
|
69
|
+
25(d)(i)'s torch is what a vessel shows *if she does not* exhibit (a) or
|
|
70
|
+
(b). The Rule 24 entries that share targets — 24(a)(i) below and above
|
|
71
|
+
200 m, and 24(c) — are disjoint on their own predicates and never meet.
|
|
72
|
+
A future pair that lawfully combines is the evidence to reopen this
|
|
73
|
+
point; until one exists, the target sets are the declaration.
|
|
74
|
+
|
|
75
|
+
Three shapes were considered and not adopted. Restating each option's
|
|
76
|
+
scalar gate on the carrier's branch (30(d) split at 50 m) keeps "lights
|
|
77
|
+
only" literal at the cost of transcribing 30(b)'s condition into 27(b)(iv),
|
|
78
|
+
27(f), 29(a) and 30(d), and 25(b)'s into 25(d)(ii), plus a CI check to
|
|
79
|
+
police the copies; the text redirects, it does not restate, and REQ-MODEL-5
|
|
80
|
+
puts a gate where its fact lives. Declaring the point-4 pairs as a relation
|
|
81
|
+
duplicates what the target sets say. A per-verb "reaches imports" flag
|
|
82
|
+
would be set the same way on every entry, and a field nothing varies is
|
|
83
|
+
decoration.
|
|
84
|
+
|
|
85
|
+
## Consequences
|
|
86
|
+
|
|
87
|
+
- **No data changes.** The README relation table, REQ-MODEL-7 and the
|
|
88
|
+
`relations` notes in `data/applicability.json` say the four points in
|
|
89
|
+
this PR. `docs/identifiers.md` and the schema are untouched.
|
|
90
|
+
- **colregs-engine implements this in one issue, three deletions.** Its
|
|
91
|
+
modality-inferred exclusion path goes: `rel:excludes` is a co-occurrence
|
|
92
|
+
check on a display and nothing more. Its `includeApplies` goes: it
|
|
93
|
+
consulted one axis by name on `rel:includes` imports, the inverse of
|
|
94
|
+
point 3, and no import in the data reaches it on a coherent record —
|
|
95
|
+
every carrier either shares the source's position gate (23(b), 23(c),
|
|
96
|
+
25(c), 28), gates its branch on it (24, 27(f), 29(a)), or reads
|
|
97
|
+
`fact:making_way`, which refines `position:underway` (26(b)(iii),
|
|
98
|
+
26(c)(iii), 27(a)(iii), 27(b)(iii)). The availability test it already
|
|
99
|
+
applies to `one_of` options becomes the one rule for every import. Its
|
|
100
|
+
`docs/engine-notes.md` items 2–5 become pointers here. colregs-engine#33
|
|
101
|
+
items 1, 6 and 7 stay the engine's: presentation and output shape.
|
|
102
|
+
- **A `one_of` option's scalar gate is a normative read of the source
|
|
103
|
+
entry.** Editing 30(b)'s length gate changes what a vessel aground may
|
|
104
|
+
show. That is the intended coupling: one paragraph, one gate.
|
|
105
|
+
- **Cost to reverse.** Delete this file and restore two sentences; the
|
|
106
|
+
engine's deleted paths come back by revert. Pre-1.0, a revert.
|
package/docs/budgets.json
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"docs/adr/0015-rule-ids-are-paragraph-keys.md": 210,
|
|
24
24
|
"docs/adr/0016-encounter-roles-are-pooled-across-frames.md": 90,
|
|
25
25
|
"docs/adr/0017-closed-vocabularies-are-prefixed-identifiers.md": 120,
|
|
26
|
+
"docs/adr/0018-jurisdiction-delta-is-a-merge-patch.md": 120,
|
|
27
|
+
"docs/adr/0019-relation-reach-and-import-reads.md": 110,
|
|
26
28
|
"docs/timeline.md": 130,
|
|
27
29
|
"docs/maritime-sources.md": 130,
|
|
28
30
|
"docs/decisions.md": 40
|
package/docs/decisions.md
CHANGED
|
@@ -3,4 +3,6 @@
|
|
|
3
3
|
Rulings from `/sequence` sessions and other closed judgment calls. One line
|
|
4
4
|
each: date, short name, the answer, a link to the argument.
|
|
5
5
|
|
|
6
|
+
- 2026-09-16 — docs ownership: colregs-engine's docs are sited wrong; colregs owns docs going forward, no new doc lands in colregs-engine. `normative-language.md` was already migrated the same day (commit fe0f8c2, #125); migration of the remaining three (`formal-methods-glossary.md`, `formal-methods-reading-list.md`, `engine-notes.md`) is not yet scheduled. Argued on kanban; no public writeup to link.
|
|
6
7
|
- 2026-09-16 — own vs self: rename the subject segment `own` to `self` across the situation record (fact keys, precedence effect keys, facts.json, fixtures, docs/identifiers.md). Work spawned as [colregs#138](https://github.com/mark-brannan/colregs/issues/138).
|
|
8
|
+
- 2026-09-16 — Q-54, Rule 17's phases are monotone: once 17(a)(ii) permission or the 17(b) duty has arisen for the stand-on vessel, it does not fall back on belated give-way compliance. Argued in [colregs#72](https://github.com/mark-brannan/colregs/issues/72); recorded in `docs/requirements.md`.
|
package/docs/identifiers.md
CHANGED
|
@@ -181,7 +181,7 @@ is an enumerated fact, so its values follow the bare-fact-name rule above:
|
|
|
181
181
|
Rule 13(d) is the reason history is a class and not a note. Once a vessel is
|
|
182
182
|
overtaking, a subsequent alteration of the bearing does not make her a
|
|
183
183
|
crossing vessel; the instantaneous geometry, read alone, says otherwise and
|
|
184
|
-
hands the duty to the wrong vessel. So
|
|
184
|
+
hands the duty to the wrong vessel. So overtaking history is a fact:
|
|
185
185
|
|
|
186
186
|
- `self:hist:was_overtaking` — this subject was, earlier in this encounter,
|
|
187
187
|
an overtaking vessel with respect to the other.
|
|
@@ -433,6 +433,9 @@ implemented in the reference evaluator and asserted by the fixtures.
|
|
|
433
433
|
words. It is deliberately not a light reference and does not resolve to
|
|
434
434
|
one.
|
|
435
435
|
|
|
436
|
+
Not being an identifier says nothing about whether a vocabulary gets a
|
|
437
|
+
catalog label; see REQ-LANG-6.
|
|
438
|
+
|
|
436
439
|
## This does not reverse GATE-5
|
|
437
440
|
|
|
438
441
|
GATE-5 declined a CI-enforced terminology glossary, permanently, for the
|
|
@@ -67,7 +67,7 @@ is Rules 11–18, III is Rule 19; which governs a pair is `INV-19a-scope`.
|
|
|
67
67
|
# Rule 13 — Overtaking
|
|
68
68
|
|
|
69
69
|
13(a) assigns a role, 13(b) deems the encounter, 13(c) resolves doubt, 13(d)
|
|
70
|
-
|
|
70
|
+
persists the result against the geometry that follows.
|
|
71
71
|
|
|
72
72
|
### INV-13b — the overtaking deeming test
|
|
73
73
|
|
|
@@ -122,7 +122,7 @@ well as Rules 14 and 15.
|
|
|
122
122
|
Settled by P4.2's role assignment: the override is or is not needed to keep
|
|
123
123
|
"never both give-way" true.
|
|
124
124
|
|
|
125
|
-
### INV-13d —
|
|
125
|
+
### INV-13d — overtaking history: the classification survives the geometry
|
|
126
126
|
|
|
127
127
|
`13(d)` · classification · temporal, a state to every later state of the
|
|
128
128
|
encounter · `✎`
|
|
@@ -140,10 +140,11 @@ until self is finally past and clear, the encounter type of the pair is
|
|
|
140
140
|
forbids: an overtaking drawn out on the bow reads *crossing*, and hands
|
|
141
141
|
give-way to the wrong vessel.
|
|
142
142
|
- **Undetermined term.** "Finally past and clear" has no fact and no threshold;
|
|
143
|
-
the data's
|
|
144
|
-
model's is its segmentation, and no bearing or range is
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
the data's overtaking history is consumer-cleared and never self-clears
|
|
144
|
+
(`Q-47`), a trace model's is its segmentation, and no bearing or range is
|
|
145
|
+
invented here.
|
|
146
|
+
- **Readings in doubt.** Two: what arms the overtaking history, `Q-51`; what it
|
|
147
|
+
forbids, `Q-52`.
|
|
147
148
|
- Entries: `rule:13d`, reading `self`/`other:hist:was_overtaking` and no geometry; the
|
|
148
149
|
`was_overtaking: false` gates on `rule:14b`, `rule:15a:crossing`, `rule:15a:keep_out_of_the_way`
|
|
149
150
|
implement `Q-52`'s *broad* reading. Settled by `Q-51`, `Q-52`, then TLC on a
|
|
@@ -162,8 +163,8 @@ type alone cannot distinguish `Q-52`'s readings.
|
|
|
162
163
|
|
|
163
164
|
- **State remembered.** As `INV-13d`.
|
|
164
165
|
- Entries: `rule:13a`'s `any_of` second limb, `self:hist:was_overtaking: true` — role
|
|
165
|
-
asserted from the
|
|
166
|
-
`Q-52`.
|
|
166
|
+
asserted from the overtaking history directly, keeping the limbs separable.
|
|
167
|
+
Settled by `Q-52`.
|
|
167
168
|
|
|
168
169
|
---
|
|
169
170
|
|
|
@@ -174,10 +175,10 @@ type alone cannot distinguish `Q-52`'s readings.
|
|
|
174
175
|
`14(b)` reading 14(a)'s conditions · classification · single-state · `✎`
|
|
175
176
|
|
|
176
177
|
**Invariant.** At any state at which two power-driven vessels are in sight,
|
|
177
|
-
both underway, with risk of collision, and neither
|
|
178
|
-
head-on situation is deemed to exist exactly when each sees
|
|
179
|
-
nearly ahead: `self:geo:rel_bearing_deg` and
|
|
180
|
-
in [0°, 11.25°] ∪ [348.75°, 360°).
|
|
178
|
+
both underway, with risk of collision, and neither carrying overtaking history
|
|
179
|
+
under `INV-13d`, a head-on situation is deemed to exist exactly when each sees
|
|
180
|
+
the other ahead or nearly ahead: `self:geo:rel_bearing_deg` and
|
|
181
|
+
`other:geo:rel_bearing_deg` each in [0°, 11.25°] ∪ [348.75°, 360°).
|
|
181
182
|
|
|
182
183
|
- **Both bearings, not one.** 14(b)'s lights limb is a statement about the
|
|
183
184
|
*other* vessel's aspect; one bearing alone swallows part of the crossing
|
|
@@ -219,9 +220,9 @@ way; both are directed to act, and the duty is symmetric.
|
|
|
219
220
|
`15(a)` · classification · single-state · `✎`
|
|
220
221
|
|
|
221
222
|
**Invariant.** At any state at which two power-driven vessels are in sight,
|
|
222
|
-
both underway, with risk of collision, and neither
|
|
223
|
-
encounter type is `crossing` exactly when it is neither
|
|
224
|
-
`INV-13b` nor `head-on` under `INV-14b`.
|
|
223
|
+
both underway, with risk of collision, and neither carrying overtaking history
|
|
224
|
+
under `INV-13d`, the encounter type is `crossing` exactly when it is neither
|
|
225
|
+
`overtaking` under `INV-13b` nor `head-on` under `INV-14b`.
|
|
225
226
|
|
|
226
227
|
- **Derived, not enumerated.** Enumerating the sector gives two statements of
|
|
227
228
|
one boundary, and the partition's only failure mode is editing one;
|
|
@@ -462,11 +463,12 @@ is at each state in exactly one of three phases:
|
|
|
462
463
|
|
|
463
464
|
Phase 3 dominates phase 2: an obligation is not qualified by a permission.
|
|
464
465
|
|
|
465
|
-
- **Reading
|
|
466
|
-
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
vessel still leaves stand-on
|
|
466
|
+
- **Reading ruled.** Monotone (`Q-54`): once phase 2 or 3 is reached for the
|
|
467
|
+
stand-on vessel in an encounter, it does not fall back on belated give-way
|
|
468
|
+
compliance.
|
|
469
|
+
- **State remembered.** One three-valued marker per stand-on vessel per
|
|
470
|
+
encounter, monotone: a late-complying give-way vessel still leaves stand-on
|
|
471
|
+
action permitted.
|
|
470
472
|
|
|
471
473
|
---
|
|
472
474
|
|
|
@@ -601,7 +603,7 @@ not jointly exhaustive (`INV-19a-third-state`); Rule 19 supplements Section I
|
|
|
601
603
|
|
|
602
604
|
- **Temporal note.** The switch is a function of the current state; a pair may
|
|
603
605
|
cross it and back inside one encounter, and no paragraph says what becomes of
|
|
604
|
-
a 13(d)
|
|
606
|
+
a 13(d) overtaking history or a Rule 17 phase: `Q-55`.
|
|
605
607
|
- **Undetermined term.** "In or near an area of restricted visibility" has no
|
|
606
608
|
fact (3(l)'s atmospheric condition is nowhere in `data/facts.json`), so `rule:19a`
|
|
607
609
|
selects Section III for any pair not in sight — wider — and records a gap.
|
package/docs/requirements.md
CHANGED
|
@@ -72,15 +72,16 @@ Neither consumer lives in this repo.
|
|
|
72
72
|
- **REQ-SCOPE-3** — A jurisdiction MUST be expressible as a *delta*: entries
|
|
73
73
|
absent from a jurisdiction's data inherit from `intl`. A jurisdiction MUST
|
|
74
74
|
NOT require restating the whole body of rules. Inheritance is "unless
|
|
75
|
-
suppressed", not unconditional
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
A delta that
|
|
75
|
+
suppressed", not unconditional: verified Inland structure (Rule 28
|
|
76
|
+
"[Reserved]") means silence-means-inherit would apply international law
|
|
77
|
+
where the national body deliberately has none, so a jurisdiction MUST
|
|
78
|
+
tombstone each inherited entry it deliberately lacks in `suppressions[]`
|
|
79
|
+
(ADR 0018; Q-11). The delta is an RFC 7396 merge patch over `intl` by
|
|
80
|
+
entry id — own entries present, tombstones `null`, silence inherits — and
|
|
81
|
+
a test proves the stored tables equal to that patch applied. This binds
|
|
82
|
+
whether a jurisdiction's text ships or is withheld (REQ-PROV-2, ADR 0010
|
|
83
|
+
are separate). A delta that only *adds* entries has no tombstones (ADR
|
|
84
|
+
0008); one that replaces an `intl` entry tombstones it and adds its own.
|
|
84
85
|
- **REQ-SCOPE-4** — Adding a jurisdiction MUST be additive. It MUST NOT require
|
|
85
86
|
a schema change or edits to existing `intl` entries.
|
|
86
87
|
- **REQ-SCOPE-5** — Geography that gates a rule (Great Lakes, Western Rivers,
|
|
@@ -164,14 +165,14 @@ Four layers, each independently addressable.
|
|
|
164
165
|
- **REQ-MODEL-6** — Entries MUST compose. Multiple entries applying to one fact
|
|
165
166
|
record is the normal case, not an error (Rule 28 is "in addition to" Rule 23).
|
|
166
167
|
- **REQ-MODEL-7** — Five relations MUST be supported:
|
|
167
|
-
- `rel:includes` — import another entry's
|
|
168
|
-
- `rel:in_lieu_of` — legal alternatives for the same fact record;
|
|
168
|
+
- `rel:includes` — import another entry's lights, modality and scalar gates, never its axes (ADR 0019);
|
|
169
|
+
- `rel:in_lieu_of` — legal alternatives for the same fact record; overlapping targets are alternatives to each other (ADR 0019);
|
|
169
170
|
- `rel:excludes` — mutual exclusion, including across rules; symmetric
|
|
170
171
|
(A excludes B implies B excludes A), and no participating entry MAY be
|
|
171
172
|
forceful (`modality:shall`/`modality:shall-if-practicable`) — a directed "this one
|
|
172
|
-
prevails" is `rel:overrides` (ADR 0007), not `rel:excludes`;
|
|
173
|
-
- `rel:exempts` — one entry lifting another's obligation;
|
|
174
|
-
- `rel:conditional_includes` — import or alternatives, gated on a predicate.
|
|
173
|
+
prevails" is `rel:overrides` (ADR 0007), not `rel:excludes`; a constraint on one display, never a removal (ADR 0019);
|
|
174
|
+
- `rel:exempts` — one entry lifting another's obligation; reaches an entry in force, never an import (ADR 0019);
|
|
175
|
+
- `rel:conditional_includes` — import or alternatives, gated on a predicate; a `one_of` yields exactly one per display, or none under a `may` carrier (ADR 0019).
|
|
175
176
|
The five are not interchangeable; README.md holds the working semantics.
|
|
176
177
|
- **REQ-MODEL-13** — Where a condition goes is not a style choice. A
|
|
177
178
|
condition on whether a paragraph *applies to this vessel at all* MUST be in
|
|
@@ -474,24 +475,21 @@ See ADR 0003.
|
|
|
474
475
|
resolve to a known paragraph path, and on a corpus filename that disagrees
|
|
475
476
|
with the file's internal metadata. Silence MUST NOT imply coverage (the
|
|
476
477
|
language mirror of REQ-SCOPE-6).
|
|
477
|
-
- **REQ-LANG-6**
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
with per-language catalogs, separate from legal
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
package documentation MUST state that a mixed-corpus rendering is never a
|
|
493
|
-
single authoritative edition. Choice and fallback beyond that are the
|
|
494
|
-
consumer's (the language mirror of REQ-CONS-3).
|
|
478
|
+
- **REQ-LANG-6** — Display strings for the closed vocabularies the package
|
|
479
|
+
emits (light, modality, role, encounter, jurisdiction, fact values, and
|
|
480
|
+
image captions **(unimplemented — no catalog yet)**) MUST be addressable
|
|
481
|
+
via stable string keys with per-language catalogs, separate from legal
|
|
482
|
+
corpora; not being an identifier does not exclude a vocabulary. Catalog
|
|
483
|
+
entries are static strings — no interpolation, plural or gender grammar —
|
|
484
|
+
and MUST carry provenance (contributors, reviewers, review date, licence);
|
|
485
|
+
definitions stay untranslated in structural files, not catalogued.
|
|
486
|
+
- **REQ-LANG-7** — The package MUST NOT encode a language fallback policy,
|
|
487
|
+
and MUST NOT silently substitute one corpus for another. Text is only
|
|
488
|
+
addressable inside a corpus, so every textual unit a consumer retrieves
|
|
489
|
+
is attributable to its corpus; package documentation MUST state that a
|
|
490
|
+
mixed-corpus rendering is never a single authoritative edition. Choice
|
|
491
|
+
and fallback beyond that are the consumer's (the language mirror of
|
|
492
|
+
REQ-CONS-3).
|
|
495
493
|
- **REQ-LANG-8** — A `community`-tier corpus MUST record who produced and
|
|
496
494
|
who reviewed it. Machine translation without named human review MUST NOT
|
|
497
495
|
be accepted.
|
|
@@ -507,6 +505,9 @@ See ADR 0003.
|
|
|
507
505
|
reflects. The two MAY differ — a corpus transcribed from an older
|
|
508
506
|
consolidation is legitimate — but the difference MUST be machine-visible,
|
|
509
507
|
never silent.
|
|
508
|
+
- **REQ-LANG-11** — Exactly one language MUST be declared reference-complete;
|
|
509
|
+
CI MUST fail if its catalog omits an emitted value. Other catalogs MAY be
|
|
510
|
+
partial, gated only by the existing key-resolution check.
|
|
510
511
|
|
|
511
512
|
---
|
|
512
513
|
|
|
@@ -930,14 +931,13 @@ Tracked here until resolved; each becomes an ADR.
|
|
|
930
931
|
Rule 28 "[Reserved]" and has no counterpart for `23(d)(ii)`/`(iii)`
|
|
931
932
|
([verification](verification/2026-08-30-q6-q8.md), Claim 1), so a
|
|
932
933
|
`us/inland` delta that is merely silent there would inherit the `intl`
|
|
933
|
-
entries and assert international obligations on inland waters.
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
the text withheld, and left this one standing.
|
|
934
|
+
entries and assert international obligations on inland waters.
|
|
935
|
+
**Resolved 2026-09-16, ADR 0018.** Tombstones: `suppressions[]` in
|
|
936
|
+
`data/applicability.json`, one record per (jurisdiction, `intl` entry),
|
|
937
|
+
and the whole delta is an RFC 7396 merge patch by entry id, proven by
|
|
938
|
+
test; the two verified Inland absences are the first tombstones. The hold
|
|
939
|
+
on non-`intl` jurisdictions is lifted (CEVNI: structure unblocked on both
|
|
940
|
+
counts, this and ADR 0010). GATE-1, Q-10, Q-8 stay open on their triggers.
|
|
941
941
|
Also from the same verification pass, tracked on the global board rather
|
|
942
942
|
than here: four transcription defects in `data/rules.json` itself
|
|
943
943
|
(`21(a)`, `21(b)`, `23(b)`, `29(b)`) — a data fix, not a design
|
|
@@ -1153,7 +1153,7 @@ named that gap before the class existed.
|
|
|
1153
1153
|
first-cut table, but it is the one paragraph of Rule 13 that assigns a role
|
|
1154
1154
|
and a `category:classification` entry has nowhere to put one. Entry `rule:13a` is
|
|
1155
1155
|
`category:precedence` here, with 13(b)'s sector test left for the `category:classification`
|
|
1156
|
-
entry that would set the `hist:was_overtaking`
|
|
1156
|
+
entry that would set the `hist:was_overtaking` overtaking-history fact. Settled with the rest
|
|
1157
1157
|
of `Q-14`, paragraph by paragraph.
|
|
1158
1158
|
|
|
1159
1159
|
**Closed in pencil 2026-09-04 (PR #26).** 13(b) is now written, as *two*
|
|
@@ -1161,7 +1161,7 @@ named that gap before the class existed.
|
|
|
1161
1161
|
encounter type belongs to the pair and reading aspect alone classified the
|
|
1162
1162
|
overtaken vessel's side of the same encounter as a crossing. 13(a) stays `category:precedence`, and its predicate now carries both
|
|
1163
1163
|
halves of "any vessel overtaking any other": the 13(b) sector and the 13(d)
|
|
1164
|
-
|
|
1164
|
+
overtaking history, as an `any_of`, which closes the gap the entry recorded. 13(d) is a
|
|
1165
1165
|
third `category:classification` entry, reading history and no geometry at all. The
|
|
1166
1166
|
split confirms the thing ADR 0005 §1 was really defending — one category per
|
|
1167
1167
|
paragraph. **Reversed in part, 2026-09-16:** ADR 0015 as rewritten makes
|
|
@@ -1242,7 +1242,7 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
|
|
|
1242
1242
|
again: on those unordered pairs Rule 15 lays a give-way duty on a vessel
|
|
1243
1243
|
that may be unable to discharge it, which is Rule 2's region and is
|
|
1244
1244
|
recorded, not gated. `rule:13a` needs no override against `rule:15a:keep_out_of_the_way` — the
|
|
1245
|
-
entry excludes every overtaking by the `hist:was_overtaking`
|
|
1245
|
+
entry excludes every overtaking by the `hist:was_overtaking` overtaking history and by
|
|
1246
1246
|
13(b)'s sector — and a test pins that absence so it stays a reason rather
|
|
1247
1247
|
than an oversight. The hand-list of six is asserted complete by a derived
|
|
1248
1248
|
check: every Rule 18 entry that assigns a helm role and does not gate a
|
|
@@ -1284,7 +1284,7 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
|
|
|
1284
1284
|
- **Q-43** — **The partition needs history to be present, and says nothing when
|
|
1285
1285
|
it is absent.** `rule:14b` and `rule:15a:crossing` are gated on `hist:was_overtaking`
|
|
1286
1286
|
being `false` on both subjects, which is 13(d)'s requirement and the only way
|
|
1287
|
-
the three encounter types stay disjoint once the
|
|
1287
|
+
the three encounter types stay disjoint once the overtaking history is set. Because absent
|
|
1288
1288
|
is absent, a situation that omits the fact is classified as *no encounter at
|
|
1289
1289
|
all* rather than as a head-on or a crossing. That is conservative in the
|
|
1290
1290
|
right direction and completely silent, which is the wrong way to be
|
|
@@ -1344,7 +1344,7 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
|
|
|
1344
1344
|
in seamanship and is one here. Settled by deciding whether the language gains
|
|
1345
1345
|
fact-to-fact comparison — a bigger change than `not` and `any_of` were, and
|
|
1346
1346
|
not one to make for a single paragraph.
|
|
1347
|
-
- **Q-47** — **13(d)'s
|
|
1347
|
+
- **Q-47** — **13(d)'s overtaking history never clears.** The paragraph runs "until she is
|
|
1348
1348
|
finally past and clear", nothing in the fact vocabulary carries that, and so
|
|
1349
1349
|
in this model `hist:was_overtaking` is set by a consumer and cleared by a
|
|
1350
1350
|
consumer while entry `rule:13d` classifies an overtaking for as long as it stands.
|
|
@@ -1354,14 +1354,14 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
|
|
|
1354
1354
|
`category:conduct` monitors, which are the things that watch a duty end rather than
|
|
1355
1355
|
begin.
|
|
1356
1356
|
|
|
1357
|
-
**Ruled 2026-09-08:** while one vessel
|
|
1358
|
-
test is suppressed on the other, newly-gaining vessel — once
|
|
1359
|
-
always overtaking, per 13(d)'s own text, so a geometry test
|
|
1360
|
-
newly name a second give-way vessel must not fire while the
|
|
1361
|
-
holds the role by history alone. `rule:13a`'s sector branch now
|
|
1362
|
-
`other:hist:was_overtaking: false`; the closing fixture in
|
|
1357
|
+
**Ruled 2026-09-08:** while one vessel's overtaking history stands, 13(a)'s
|
|
1358
|
+
sector test is suppressed on the other, newly-gaining vessel — once
|
|
1359
|
+
overtaking, always overtaking, per 13(d)'s own text, so a geometry test
|
|
1360
|
+
that would newly name a second give-way vessel must not fire while the
|
|
1361
|
+
first still holds the role by history alone. `rule:13a`'s sector branch now
|
|
1362
|
+
also reads `other:hist:was_overtaking: false`; the closing fixture in
|
|
1363
1363
|
`fixtures/situation-fixtures.json` is the case this closes — a vessel
|
|
1364
|
-
drops back onto the
|
|
1364
|
+
drops back onto the history-holder's own stern, and only the history-holder
|
|
1365
1365
|
gives way.
|
|
1366
1366
|
- **Q-48** — **Nothing checks that a situation is geometrically possible.**
|
|
1367
1367
|
`self:geo:rel_bearing_deg`, `other:geo:rel_bearing_deg`, the two
|
|
@@ -1392,7 +1392,7 @@ written up in `docs/identifiers.md` §"Effects"; what it could not is here.
|
|
|
1392
1392
|
bearing-rate values were placeholders. They were re-derived from the headings
|
|
1393
1393
|
and speeds. Two cases could not keep their story: the R1 head-on had both
|
|
1394
1394
|
vessels with the other to starboard, which no speeds make a steady bearing,
|
|
1395
|
-
and is recast as one; the 13(d)
|
|
1395
|
+
and is recast as one; the 13(d) overtaking-history case is 400 m abeam with the CPA
|
|
1396
1396
|
already past, and lost `rule:7d_i`. The partition sweep now constructs positions
|
|
1397
1397
|
and headings for every bearing pair and is consistent at every point. The
|
|
1398
1398
|
"never both give-way" property is asserted over a sweep of steady-bearing
|
|
@@ -1430,18 +1430,18 @@ nothing is blocked while open.
|
|
|
1430
1430
|
give-way and Rule 16 binds her. *Narrow:* Section II's duties attach only with
|
|
1431
1431
|
risk of collision; 14 and 15 are emphasis. Default **wide**: `rule:13a`, `rule:13b`,
|
|
1432
1432
|
`rule:13d` and every Rule 18 entry read `in_sight` only. No recommendation.
|
|
1433
|
-
- **Q-51** — **What arms 13(d)'s
|
|
1433
|
+
- **Q-51** — **What arms 13(d)'s overtaking history?** *A:* 13(b)'s deeming, at the first
|
|
1434
1434
|
state the geometry holds. *B:* 13(a)'s duty actually attaching. They differ
|
|
1435
1435
|
where the geometry holds but a condition on 13(a) does not — `Q-50`'s
|
|
1436
|
-
surface. Pencilled **B** ✎ (Solace, 2026-09-14): the
|
|
1436
|
+
surface. Pencilled **B** ✎ (Solace, 2026-09-14): the overtaking history arms when the
|
|
1437
1437
|
duty attaches. KIVELI [2025] EWHC 1185 (Admlty) holds the analogous Rule 14
|
|
1438
|
-
|
|
1438
|
+
history arms on risk of collision and then persists; no authority was found on
|
|
1439
1439
|
overtaking geometry with no risk of collision. Free to reverse: four entries
|
|
1440
1440
|
(`rule:13a`, `rule:13b`, `rule:13d`) and their fixtures, and nothing consumes the one
|
|
1441
1441
|
state this changes the answer for. Settled by: a decision on Rule 13 itself,
|
|
1442
1442
|
or Cockcroft & Lameijer on whether overtaking status needs risk of
|
|
1443
1443
|
collision.
|
|
1444
|
-
- **Q-52** — **What does 13(d)'s
|
|
1444
|
+
- **Q-52** — **What does 13(d)'s overtaking history forbid?** *Narrow:* reclassification to
|
|
1445
1445
|
*crossing* only, as the paragraph says, leaving head-on to Rule 14 on the
|
|
1446
1446
|
geometry of the moment. *Broad:* the encounter stays an overtaking and no
|
|
1447
1447
|
other Section II classification attaches. Both preserve the duty; they differ
|
|
@@ -1459,16 +1459,16 @@ nothing is blocked while open.
|
|
|
1459
1459
|
flags any other alteration. No default (Rule 17 has no entry). Recommend
|
|
1460
1460
|
**exception**: 17(a)(ii) describes an action, not a proviso, and leaves a
|
|
1461
1461
|
monitor something to check — a drafting argument, not a source.
|
|
1462
|
-
- **Q-54** — **Are Rule 17's phases monotone?** *
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1462
|
+
- **Q-54** — **Are Rule 17's phases monotone?** *Monotone:* one three-valued
|
|
1463
|
+
marker per stand-on vessel per encounter; late compliance does not take the
|
|
1464
|
+
permission away. *Re-evaluating:* each phase is a predicate on the current
|
|
1465
|
+
state and the vessel may fall back — must she then hold her *new* course
|
|
1466
|
+
and speed? TLC distinguishes them on a four-state trace. **Ruled
|
|
1467
|
+
2026-09-16:** monotone — permission or duty, once arisen, survives belated
|
|
1468
|
+
compliance; a fall-back needs a baseline the Rules lack (`Q-57`). No holding on point (colregs#72); *Ever Smart* [2021] UKSC 6 para 61 has the obligation cease at 17(a)(ii)/17(b) with no revival — consistent with monotone, not a ruling on it.
|
|
1469
1469
|
- **Q-55** — **What does a visibility transition do to Section II state?**
|
|
1470
1470
|
Rule 11 and 19(a) switch on the current state; no paragraph says what
|
|
1471
|
-
becomes of a 13(d)
|
|
1471
|
+
becomes of a 13(d) overtaking history or a Rule 17 phase. *Persisting:* they belong to the
|
|
1472
1472
|
encounter and survive the fog. *Resetting:* Section II starts afresh on the
|
|
1473
1473
|
geometry when sight is regained — the reclassification 13(d) forbids, via
|
|
1474
1474
|
visibility. No default (nothing is temporal). Recommend **persisting**, which
|
|
@@ -923,6 +923,48 @@
|
|
|
923
923
|
"fact:length_m": 11.6
|
|
924
924
|
},
|
|
925
925
|
"expect": []
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
"name": "constrained by draft, US Inland: Rule 28 is [Reserved], the record is out of vocabulary and selects nothing",
|
|
929
|
+
"jurisdiction": "us/inland",
|
|
930
|
+
"facts": {
|
|
931
|
+
"fact:propulsion": "propulsion:power",
|
|
932
|
+
"fact:activity": "activity:cbd",
|
|
933
|
+
"fact:position": "position:underway",
|
|
934
|
+
"fact:length_m": 200
|
|
935
|
+
},
|
|
936
|
+
"expect": []
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
"name": "6 m outboard, max speed 6 kn, US Inland: 23(d)(ii) has no Inland counterpart, 23(d)(i) and 23(a) are inherited",
|
|
940
|
+
"jurisdiction": "us/inland",
|
|
941
|
+
"facts": {
|
|
942
|
+
"fact:propulsion": "propulsion:power",
|
|
943
|
+
"fact:activity": "activity:none",
|
|
944
|
+
"fact:position": "position:underway",
|
|
945
|
+
"fact:length_m": 6,
|
|
946
|
+
"fact:max_speed_kn": 6
|
|
947
|
+
},
|
|
948
|
+
"expect": [
|
|
949
|
+
"rule:23a_i",
|
|
950
|
+
"rule:23a_ii",
|
|
951
|
+
"rule:23a_iii_iv",
|
|
952
|
+
"rule:23d_i"
|
|
953
|
+
]
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
"name": "constrained by draft, at anchor, US Inland: Rule 30 is inherited untouched",
|
|
957
|
+
"jurisdiction": "us/inland",
|
|
958
|
+
"facts": {
|
|
959
|
+
"fact:propulsion": "propulsion:power",
|
|
960
|
+
"fact:activity": "activity:cbd",
|
|
961
|
+
"fact:position": "position:anchored",
|
|
962
|
+
"fact:length_m": 200
|
|
963
|
+
},
|
|
964
|
+
"expect": [
|
|
965
|
+
"rule:30a",
|
|
966
|
+
"rule:30c"
|
|
967
|
+
]
|
|
926
968
|
}
|
|
927
969
|
]
|
|
928
970
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "colregs",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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
|
+
"colregs": {
|
|
7
|
+
"referenceLanguage": "en"
|
|
8
|
+
},
|
|
6
9
|
"repository": {
|
|
7
10
|
"type": "git",
|
|
8
11
|
"url": "git+https://github.com/mark-brannan/colregs.git"
|
|
@@ -306,6 +306,21 @@
|
|
|
306
306
|
"classification_shape": { "type": "string" }
|
|
307
307
|
}
|
|
308
308
|
},
|
|
309
|
+
"suppressions": {
|
|
310
|
+
"$comment": "Tombstones (REQ-SCOPE-3, ADR 0018): a jurisdiction deliberately lacks an inherited intl entry, as distinct from not-yet-transcribed. With the jurisdiction's own entries these form its RFC 7396 merge patch over intl; suppresses is the key that patch sets to null.",
|
|
311
|
+
"type": "array",
|
|
312
|
+
"items": {
|
|
313
|
+
"type": "object",
|
|
314
|
+
"additionalProperties": false,
|
|
315
|
+
"required": ["jurisdiction", "suppresses", "cite", "why"],
|
|
316
|
+
"properties": {
|
|
317
|
+
"jurisdiction": { "type": "string", "pattern": "^[a-z]+(/[a-z]+)+$" },
|
|
318
|
+
"suppresses": { "$ref": "#/$defs/ruleId" },
|
|
319
|
+
"cite": { "type": "string", "minLength": 1 },
|
|
320
|
+
"why": { "type": "string", "minLength": 1 }
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
},
|
|
309
324
|
"known_omissions": {
|
|
310
325
|
"type": "array",
|
|
311
326
|
"items": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://github.com/mark-brannan/colregs/schema/i18n-catalog.schema.json",
|
|
4
4
|
"title": "data/i18n/*.json",
|
|
5
|
-
"description": "Display catalog for one BCP 47 language: UI strings for
|
|
5
|
+
"description": "Display catalog for one BCP 47 language: UI strings for the closed vocabularies the package emits (lights, modalities, roles, encounters, jurisdictions, closed-list fact values), not legal text -- see docs/adr/0003-language-as-a-dimension.md 'Display catalogs' and REQ-LANG-6.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": ["language", "provenance", "strings"],
|
|
@@ -25,18 +25,39 @@
|
|
|
25
25
|
},
|
|
26
26
|
"strings": {
|
|
27
27
|
"type": "object",
|
|
28
|
-
"description": "One section per vocabulary. Every section is keyed by the identifier itself, as documented in docs/identifiers.md -- light
|
|
28
|
+
"description": "One section per vocabulary (REQ-LANG-6, REQ-LANG-9's ruling docs/adr/0017). Every section is keyed by the identifier itself, as documented in docs/identifiers.md -- light, modality, role and encounter identifiers carry a type prefix; jurisdiction and closed-list fact values are keyed by their own bare or fact-prefixed form. Values are static strings: no interpolation, no plurals -- the consumer's i18n system owns composition.",
|
|
29
29
|
"additionalProperties": false,
|
|
30
30
|
"properties": {
|
|
31
|
-
"
|
|
31
|
+
"lights": {
|
|
32
32
|
"type": "object",
|
|
33
33
|
"propertyNames": { "pattern": "^light:[a-z_]+$" },
|
|
34
34
|
"additionalProperties": { "$ref": "#/$defs/label" }
|
|
35
35
|
},
|
|
36
|
-
"
|
|
36
|
+
"modalities": {
|
|
37
37
|
"type": "object",
|
|
38
38
|
"propertyNames": { "pattern": "^modality:[a-z]+(-[a-z]+)*$" },
|
|
39
39
|
"additionalProperties": { "$ref": "#/$defs/label" }
|
|
40
|
+
},
|
|
41
|
+
"roles": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"propertyNames": { "pattern": "^role:[a-z]+(-[a-z]+)*$" },
|
|
44
|
+
"additionalProperties": { "$ref": "#/$defs/label" }
|
|
45
|
+
},
|
|
46
|
+
"encounters": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"propertyNames": { "pattern": "^encounter:[a-z]+(-[a-z]+)*$" },
|
|
49
|
+
"additionalProperties": { "$ref": "#/$defs/label" }
|
|
50
|
+
},
|
|
51
|
+
"jurisdictions": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"propertyNames": { "pattern": "^[a-z]+(/[a-z]+)*$" },
|
|
54
|
+
"additionalProperties": { "$ref": "#/$defs/label" }
|
|
55
|
+
},
|
|
56
|
+
"facts": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"description": "Closed-list fact-value labels only (Tier A): propulsion, activity, position, rule18_class, dynamics, obstruction_side, wind_side. Field names (fact:length_m, geo:tcpa_s, ...) have no closed list behind them and are never catalogued here -- form-label text and unit/number formatting are the consumer's.",
|
|
59
|
+
"propertyNames": { "pattern": "^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*$" },
|
|
60
|
+
"additionalProperties": { "$ref": "#/$defs/label" }
|
|
40
61
|
}
|
|
41
62
|
}
|
|
42
63
|
}
|
|
@@ -53,10 +53,9 @@
|
|
|
53
53
|
"^light:[a-z_]+$": {
|
|
54
54
|
"type": "object",
|
|
55
55
|
"additionalProperties": false,
|
|
56
|
-
"required": ["
|
|
56
|
+
"required": ["term", "cite", "character", "rule21"],
|
|
57
57
|
"properties": {
|
|
58
|
-
"
|
|
59
|
-
"colregs_term": { "type": "string", "minLength": 1 },
|
|
58
|
+
"term": { "type": "string", "minLength": 1 },
|
|
60
59
|
"cite": { "type": "string", "minLength": 1 },
|
|
61
60
|
"color": { "type": ["string", "null"] },
|
|
62
61
|
"color_note": { "type": "string" },
|