colregs 0.3.1 → 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.
@@ -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
@@ -21,8 +21,13 @@
21
21
  "docs/adr/0013-corpus-files-with-editions.md": 95,
22
22
  "docs/adr/0014-engine-interface-owned-by-colregs.md": 110,
23
23
  "docs/adr/0015-rule-ids-are-paragraph-keys.md": 210,
24
+ "docs/adr/0016-encounter-roles-are-pooled-across-frames.md": 90,
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,
24
28
  "docs/timeline.md": 130,
25
- "docs/maritime-sources.md": 130
29
+ "docs/maritime-sources.md": 130,
30
+ "docs/decisions.md": 40
26
31
  },
27
32
  "json_prose": {
28
33
  "targets": [
@@ -0,0 +1,8 @@
1
+ # Decisions
2
+
3
+ Rulings from `/sequence` sessions and other closed judgment calls. One line
4
+ each: date, short name, the answer, a link to the argument.
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.
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`.
@@ -24,6 +24,17 @@ but it is a name in a namespace and a consumer reads the paragraph out of
24
24
  Paragraph-keying is argued in ADR 0001 and required by REQ-MODEL-4; nothing
25
25
  here reopens either.
26
26
 
27
+ **Jurisdiction values sit beside paragraph paths in this class, and for the
28
+ same reason.** `intl` and `us/inland` are not names this package coined:
29
+ jurisdiction is a coordinate with REQ-SCOPE-2's own `<body>/<waters>`
30
+ grammar, its left segment borrowed from ISO 3166, the whole value doubling
31
+ as a corpus key and a `data/text/` filesystem path — its sibling axis,
32
+ `language`, is a bare BCP 47 tag for the same reason. A jurisdiction value
33
+ is immutable under REQ-MODEL-10 like any identifier here — renaming
34
+ `us/inland` would break every stored provenance and corpus path — it just
35
+ carries no prefix, because the grammar that owns it already keeps it stable
36
+ and collision-free (ADR 0017).
37
+
27
38
  **Vocabulary identifiers carry a type prefix.** These names are this
28
39
  package's own — nothing in COLREGS calls anything `masthead` or `nuc`. They
29
40
  share one flat string space across five files, and before the prefix they
@@ -31,7 +42,11 @@ collided in it: `towing` was simultaneously a light id (Rule 21(d)) and an
31
42
  `activity` value (Rule 24(a)), so a consumer holding the string `towing`
32
43
  could not say what it was a name *for* without knowing which field it came
33
44
  out of. The prefix makes the namespace part of the identifier, which
34
- resolves that collision by construction rather than by convention.
45
+ resolves that collision by construction rather than by convention. The same
46
+ shape recurs inside the closed vocabularies themselves: `shall-not-impede`
47
+ names both a modality and a role, and `none` names both a role and an
48
+ encounter — resolved the identical way, `modality:shall-not-impede` and
49
+ `role:shall-not-impede` being two names rather than one (ADR 0017).
35
50
 
36
51
  ## The scheme
37
52
 
@@ -42,6 +57,10 @@ resolves that collision by construction rather than by convention.
42
57
  | `fact:<key>` | fact keys — the input vocabulary (`data/facts.json`) | `fact:activity`, `fact:length_m`, `fact:making_way`, `fact:on_mooring_buoy` |
43
58
  | `<fact>:<value>` | values of an enumerated fact | `activity:nuc`, `position:anchored`, `propulsion:sail`, `obstruction_side:port` |
44
59
  | `rel:<name>` | the five relation verbs (`data/applicability.json`) | `rel:includes`, `rel:in_lieu_of`, `rel:exempts` |
60
+ | `modality:<value>` | modality values (`data/applicability.json` `modalities`) | `modality:shall`, `modality:may` |
61
+ | `role:<value>` | effect role values (`data/applicability.json` `effects.roles`) | `role:give-way`, `role:none` |
62
+ | `encounter:<value>` | effect encounter values (`data/applicability.json` `effects.encounters`) | `encounter:head-on`, `encounter:none` |
63
+ | `category:<value>` | entry category values (`data/applicability.json` `categories`) | `category:precedence`, `category:display` |
45
64
 
46
65
  The prefix names the namespace the identifier lives in. For a fact *value*
47
66
  that namespace is the fact itself, written bare: `activity:nuc`, not
@@ -72,9 +91,9 @@ for a better idea, logging the change. What would settle it: the first
72
91
  two-subject entry — Rule 18 — actually being written against it. This
73
92
  section answers `Q-28`.
74
93
 
75
- A `display` entry reads one vessel. A `classification` or `precedence`
76
- entry reads two, and needs to say *whose* `fact:activity` it means. The form
77
- is three segments:
94
+ A `category:display` entry reads one vessel. A `category:classification` or
95
+ `category:precedence` entry reads two, and needs to say *whose*
96
+ `fact:activity` it means. The form is three segments:
78
97
 
79
98
  ```
80
99
  <subject>:<class>:<key>
@@ -82,14 +101,14 @@ is three segments:
82
101
 
83
102
  | segment | values |
84
103
  |---|---|
85
- | subject | `own`, `other`, `pair` |
104
+ | subject | `self`, `other`, `pair` |
86
105
  | class | `fact`, `kin`, `geo`, `hist` |
87
106
  | key | the identifier as it already exists, or a new one in a new class |
88
107
 
89
- `own:fact:activity`, `other:kin:heading_deg`, `pair:geo:in_sight`,
90
- `own:hist:was_overtaking`.
108
+ `self:fact:activity`, `other:kin:heading_deg`, `pair:geo:in_sight`,
109
+ `self:hist:was_overtaking`.
91
110
 
92
- **A key with no subject segment means `own:`.** This is the whole of the
111
+ **A key with no subject segment means `self:`.** This is the whole of the
93
112
  backward-compatibility story and it is why the subject is a *prefix* rather
94
113
  than a change to the fact keys. `fact:activity` still spells `fact:activity`
95
114
  and still denotes what it always denoted, so every predicate in
@@ -97,14 +116,14 @@ and still denotes what it always denoted, so every predicate in
97
116
  `fixtures/applicability-fixtures.json` and every stored citation a consumer
98
117
  holds stays correct unedited — `REQ-MODEL-10` is satisfied by construction
99
118
  rather than by a migration. The alternative shapes were a suffix
100
- (`fact:activity:own`), which buries the thing you are scanning for at the
119
+ (`fact:activity:self`), which buries the thing you are scanning for at the
101
120
  end of a variable-length name, and per-subject fact keys
102
121
  (`fact:own_activity`), which would double the fact vocabulary and repoint
103
122
  nothing but would leave two names for one concept forever. Prefixing is the
104
123
  only one of the three where the existing vocabulary is a strict subset of
105
124
  the new one.
106
125
 
107
- The cost, stated so nobody rediscovers it: `own`, `other` and `pair` are now
126
+ The cost, stated so nobody rediscovers it: `self`, `other` and `pair` are now
108
127
  reserved at the head of the identifier space, and no fact, light or relation
109
128
  may ever be named one of them. That is the price of a subject segment that
110
129
  is not itself prefixed, and it is cheap — the three words are not candidate
@@ -112,9 +131,9 @@ names for anything this package models.
112
131
 
113
132
  ### The three subjects
114
133
 
115
- `own` is the vessel the rule addresses; `other` is the vessel it is in an
134
+ `self` is the vessel the rule addresses; `other` is the vessel it is in an
116
135
  encounter with. **`pair` is the encounter itself**, and it exists because
117
- some facts belong to neither vessel: range is one number, not own's number
136
+ some facts belong to neither vessel: range is one number, not self's number
118
137
  and the other's. Putting `geo:range_m` under both subjects would create two
119
138
  identifiers for one quantity and a class of bug — the two disagreeing —
120
139
  that has no meaning.
@@ -125,21 +144,21 @@ Geometry splits on whether the quantity is symmetric between the vessels:
125
144
 
126
145
  | fact | subject | |
127
146
  |---|---|---|
128
- | `geo:rel_bearing_deg` | `own` / `other` | bearing of the *other* subject, clockwise from this subject's heading |
147
+ | `geo:rel_bearing_deg` | `self` / `other` | bearing of the *other* subject, clockwise from this subject's heading |
129
148
  | `geo:range_m` | `pair` | |
130
149
  | `geo:bearing_change_deg_min` | `pair` | Rule 7(d)(i)'s steady bearing |
131
150
  | `geo:cpa_m`, `geo:tcpa_s` | `pair` | |
132
151
  | `geo:in_sight` | `pair` | Rule 3(k), symmetric because the rule defines it that way |
133
152
 
134
153
  The directional row is where the namespace earns its keep.
135
- `own:geo:rel_bearing_deg` is relative bearing — where the other vessel is
136
- off own's bow. `other:geo:rel_bearing_deg` is the same fact read from the
154
+ `self:geo:rel_bearing_deg` is relative bearing — where the other vessel is
155
+ off self's bow. `other:geo:rel_bearing_deg` is the same fact read from the
137
156
  other side, which is **aspect**. So aspect gets no identifier of its own: it
138
157
  is a subject swap, not a second fact. Rule 13(b)'s overtaking sector is then
139
- `other:geo:rel_bearing_deg` in (112.5, 247.5) — own more than 22.5° abaft
158
+ `other:geo:rel_bearing_deg` in (112.5, 247.5) — self more than 22.5° abaft
140
159
  the other vessel's beam — written once, in the units the rule itself uses.
141
- Swapping `own` and `other` throughout a predicate reverses the encounter,
142
- which is exactly the operation a `precedence` rule needs and the reason to
160
+ Swapping `self` and `other` throughout a predicate reverses the encounter,
161
+ which is exactly the operation a `category:precedence` rule needs and the reason to
143
162
  prefer a subject namespace over two parallel vocabularies.
144
163
 
145
164
  The directional and the pair geometry are redundant with `kin:` wherever both
@@ -153,7 +172,7 @@ and a record that omits the kinematics is unchecked rather than wrong.
153
172
 
154
173
  `kin:` is the kinematic class ADR 0005 introduces — `kin:position`,
155
174
  `kin:heading_deg`, `kin:sog_kn`, `kin:rot_deg_min`, `kin:dynamics`. It takes
156
- `own`/`other` only; there is no kinematic state of a pair. `kin:dynamics`
175
+ `self`/`other` only; there is no kinematic state of a pair. `kin:dynamics`
157
176
  is an enumerated fact, so its values follow the bare-fact-name rule above:
158
177
  `dynamics:tanker`, not `kin:dynamics:tanker`.
159
178
 
@@ -162,14 +181,14 @@ is an enumerated fact, so its values follow the bare-fact-name rule above:
162
181
  Rule 13(d) is the reason history is a class and not a note. Once a vessel is
163
182
  overtaking, a subsequent alteration of the bearing does not make her a
164
183
  crossing vessel; the instantaneous geometry, read alone, says otherwise and
165
- hands the duty to the wrong vessel. So the latch is a fact:
184
+ hands the duty to the wrong vessel. So overtaking history is a fact:
166
185
 
167
- - `own:hist:was_overtaking` — this subject was, earlier in this encounter,
186
+ - `self:hist:was_overtaking` — this subject was, earlier in this encounter,
168
187
  an overtaking vessel with respect to the other.
169
- - `own:hist:latched_at_s` — how long ago that attached, for a `conduct`
188
+ - `self:hist:latched_at_s` — how long ago that attached, for a `category:conduct`
170
189
  monitor. A predicate at a point does not read it.
171
190
 
172
- History is directional — it is *own* that was overtaking — so it takes a
191
+ History is directional — it is *self* that was overtaking — so it takes a
173
192
  subject segment like the fact record does, and never `pair`.
174
193
 
175
194
  ### What this does not do
@@ -183,41 +202,41 @@ keys would have broken.
183
202
  ## Effects `✎`
184
203
 
185
204
  **Pencil** (`docs/conventions.md`): ADR 0005 puts the whole two-subject shape
186
- there. What would settle it: a second family of `precedence` paragraphs —
205
+ there. What would settle it: a second family of `category:precedence` paragraphs —
187
206
  Rules 12, 14 and 15 — written against it. **Written, and it held with one
188
- addition**: Rules 7(d) and 13–15 are the first `classification` entries, and a
207
+ addition**: Rules 7(d) and 13–15 are the first `category:classification` entries, and a
189
208
  classification produces neither a role nor a section, so the table below grows
190
- a third row. Rule 12 turned out to be `precedence` and not `classification`
209
+ a third row. Rule 12 turned out to be `category:precedence` and not `category:classification`
191
210
  (below). This section answers the data half of `Q-27` and is required by
192
211
  `REQ-CAT-8`.
193
212
 
194
- A `display` entry produces `lights`. A `scope` or `precedence` entry produces
213
+ A `category:display` entry produces `lights`. A `category:scope` or `category:precedence` entry produces
195
214
  an **effect**, and the shape of the effect is fixed by the category:
196
215
 
197
216
  | category | effect |
198
217
  |---|---|
199
- | `scope` | `{"part", "section", "applies_rules"}` — which section of which Part governs, and the rules it contains |
200
- | `precedence` | `{"own": <role>, "other": <role>}` — one role per subject |
201
- | `classification` | `{"encounter": <encounter>}` **or** `{"risk_of_collision": true}` — exactly one key |
218
+ | `category:scope` | `{"part", "section", "applies_rules"}` — which section of which Part governs, and the rules it contains |
219
+ | `category:precedence` | `{"self": <role>, "other": <role>}` — one role per subject |
220
+ | `category:classification` | `{"encounter": <encounter>}` **or** `{"risk_of_collision": true}` — exactly one key |
202
221
 
203
- Five roles, a closed set: `give-way`, `stand-on`, `shall-not-impede`,
204
- `keep-clear`, `none`. They are declared in `data/applicability.json` under
205
- `effects`, and they are **not identifiers** — like modality and jurisdiction
206
- values they are a closed vocabulary of their own, outside what `REQ-MODEL-10`
207
- binds.
222
+ Five roles, a closed set: `role:give-way`, `role:stand-on`,
223
+ `role:shall-not-impede`, `role:keep-clear`, `role:none`. They are declared
224
+ in `data/applicability.json` under `effects`, and, like modality and
225
+ category, they are identifiers: prefixed closed vocabularies `REQ-MODEL-10`
226
+ binds (ADR 0017).
208
227
 
209
228
  ### Encounters, and why a classification effect has two shapes
210
229
 
211
- Four encounters, a closed set like the roles: `head-on` (Rule 14), `crossing`
212
- (Rule 15), `overtaking` (Rule 13) and `none`. They are declared in
213
- `data/applicability.json` under `effects.encounters` and, like the roles, they
214
- are not identifiers.
230
+ Four encounters, a closed set like the roles: `encounter:head-on` (Rule 14),
231
+ `encounter:crossing` (Rule 15), `encounter:overtaking` (Rule 13) and
232
+ `encounter:none`. They are declared in `data/applicability.json` under
233
+ `effects.encounters` and, like the roles, they are identifiers.
215
234
 
216
- A `classification` effect carries **exactly one key**, and which key it is
235
+ A `category:classification` effect carries **exactly one key**, and which key it is
217
236
  depends on which question the paragraph answers. Rule 7(d)(i) answers *does
218
237
  risk of collision exist* and produces `{"risk_of_collision": true}`; Rules 13,
219
238
  14 and 15 answer *what kind of encounter is this* and produce an `encounter`.
220
- ADR 0005 gives both questions to `classification` — "relative geometry,
239
+ ADR 0005 gives both questions to `category:classification` — "relative geometry,
221
240
  history → encounter type, risk of collision" — and the two do not merge. A
222
241
  single shape would have made every encounter entry state a risk it does not
223
242
  decide, and 15(a)'s crossing test reads `pair:geo:risk_of_collision` as an
@@ -226,7 +245,7 @@ input rather than producing it.
226
245
  There is no `{"risk_of_collision": false}` and there never will be. 7(a) makes
227
246
  risk a judgement on all available means and deems it to exist in any doubt, so
228
247
  an entry can add a ground for risk and nothing in this package can deny one.
229
- `none` is declared as an encounter for the completeness of the vocabulary and
248
+ `encounter:none` is declared for the completeness of the vocabulary and
230
249
  no entry produces it: an encounter type is asserted by a paragraph, and the
231
250
  absence of one is the absence of an entry rather than an entry with a null
232
251
  value.
@@ -242,17 +261,17 @@ subjects' bearings in half-degree steps and asserts exactly one encounter at
242
261
  each of the 518 400 points; the Alloy version of the same property lives in
243
262
  `colregs-engine`.
244
263
 
245
- ### Rule 12 is `precedence`, not `classification`
264
+ ### Rule 12 is `category:precedence`, not `category:classification`
246
265
 
247
266
  ADR 0005 §1 and the proposal's first-cut table file Rule 12 under
248
- `classification`. It is `precedence` here, for the reason `Q-37` gives for
267
+ `category:classification`. It is `category:precedence` here, for the reason `Q-37` gives for
249
268
  13(a): **12(a) produces a role, and a classification effect has nowhere to put
250
269
  one.** "One of them shall keep out of the way of the other" is give-way and
251
270
  stand-on in the effect vocabulary that already exists, and it is not an
252
271
  encounter type — two sailing vessels meeting are still in a head-on, a
253
272
  crossing or an overtaking, and Rule 12 says which of them gives way rather
254
273
  than which kind of meeting it is. Rule 12 has no deeming paragraph at all:
255
- 12(b) defines the windward side and is a `definition`, so it is the cite on the
274
+ 12(b) defines the windward side and is a `category:definition`, so it is the cite on the
256
275
  `kin:wind_side` fact rather than an entry.
257
276
 
258
277
  The category is `Q-14`'s to settle paragraph by paragraph and this is two more
@@ -280,18 +299,18 @@ that checkable: a Rule 18 entry meets Rule 15 when it assigns a helm role and
280
299
  neither subject is gated to a sailing vessel, and every such entry must carry
281
300
  the override, so a Rule 18 paragraph added later cannot join Rule 15 silently.
282
301
 
283
- **The effect names both subjects, and that is the point.** A `precedence`
284
- entry is evaluated from own's side, so 18(a)(i) says own gives way *and* the
285
- other vessel stands on. Writing only own's half would lose Rule 17, which
302
+ **The effect names both subjects, and that is the point.** A `category:precedence`
303
+ entry is evaluated from self's side, so 18(a)(i) says self gives way *and* the
304
+ other vessel stands on. Writing only self's half would lose Rule 17, which
286
305
  attaches to the counterpart of a give-way duty and to nothing else. So
287
- `stand-on` appears only opposite `give-way`, and the counterpart of
288
- `shall-not-impede` is always `none` — that is 8(f)(iii) in the data: a vessel
289
- whose passage is not to be impeded acquires no privilege by it. `none` is
306
+ `role:stand-on` appears only opposite `role:give-way`, and the counterpart of
307
+ `role:shall-not-impede` is always `role:none` — that is 8(f)(iii) in the data: a vessel
308
+ whose passage is not to be impeded acquires no privilege by it. `role:none` is
290
309
  written rather than omitted, because a norm that confers nothing on a subject
291
- is a finding and not an absence: NUC against RAM is `none` on both sides, and
310
+ is a finding and not an absence: NUC against RAM is `role:none` on both sides, and
292
311
  that is Rule 18's partial order rather than a gap in the table.
293
312
 
294
- `keep-clear` is one role for the two duties 18(e) and 18(f)(i) impose together
313
+ `role:keep-clear` is one role for the two duties 18(e) and 18(f)(i) impose together
295
314
  — keep well clear, and avoid impeding navigation. The vocabulary cannot
296
315
  separate them and does not pretend to.
297
316
 
@@ -299,7 +318,7 @@ separate them and does not pretend to.
299
318
 
300
319
  A two-subject entry is keyed on its paragraph like any other (below):
301
320
  `rule:18a_i` is 18(a)(i), `rule:9c` is 9(c), `rule:8f_iii` is 8(f)(iii). No
302
- subject segment appears in an id: every entry is evaluated from own's side,
321
+ subject segment appears in an id: every entry is evaluated from self's side,
303
322
  and where the paragraph classifies the pair rather than one vessel the entry
304
323
  reads both subjects inside one predicate — `rule:13b` is 13(b) whichever
305
324
  vessel is coming up. Where a paragraph's subject is disjunctive — 9(b) is
@@ -391,11 +410,6 @@ implemented in the reference evaluator and asserted by the fixtures.
391
410
 
392
411
  ## What is not an identifier
393
412
 
394
- - **Modality values** (`shall`, `may`, `shall-if-practicable`,
395
- `conditional`, `exempt`) and **jurisdiction values** (`intl`,
396
- `us/inland`) are their own closed vocabularies, defined in §2 of the
397
- requirements and not part of the identifier space REQ-MODEL-10 binds. So are
398
- the **role** and **encounter** values of an effect.
399
413
  - **Constants** — `situation.constants` in `data/facts.json`: the numbers a
400
414
  Part B predicate needs and the Rules do not always give
401
415
  (`appreciable_bearing_change_deg_min`, `head_on_half_angle_deg`, the two
@@ -410,7 +424,7 @@ implemented in the reference evaluator and asserted by the fixtures.
410
424
  data is addressed by. Only the values inside them can be identifiers, and
411
425
  where they are (`also_activity` holds an activity value) they are prefixed.
412
426
  `not` and `any_of` are the second pair of words reserved at the head of an
413
- identifier space, after `own`/`other`/`pair`: they appear where a fact key
427
+ identifier space, after `self`/`other`/`pair`: they appear where a fact key
414
428
  appears, so no fact may ever be named either. The cost is the same and as
415
429
  cheap — every fact key carries a class prefix (`fact:`, `geo:`, `kin:`,
416
430
  `hist:`, `env:`) and neither word could be one.
@@ -419,6 +433,9 @@ implemented in the reference evaluator and asserted by the fixtures.
419
433
  words. It is deliberately not a light reference and does not resolve to
420
434
  one.
421
435
 
436
+ Not being an identifier says nothing about whether a vocabulary gets a
437
+ catalog label; see REQ-LANG-6.
438
+
422
439
  ## This does not reverse GATE-5
423
440
 
424
441
  GATE-5 declined a CI-enforced terminology glossary, permanently, for the