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.
- package/README.md +22 -14
- package/data/applicability.json +295 -276
- package/data/facts.json +19 -19
- package/data/i18n/en.json +73 -7
- package/data/i18n/fi.json +5 -5
- 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/0011-api-shape.md +7 -7
- package/docs/adr/0012-trace-and-rule2-departure-api.md +2 -2
- package/docs/adr/0016-encounter-roles-are-pooled-across-frames.md +82 -0
- package/docs/adr/0017-closed-vocabularies-are-prefixed-identifiers.md +105 -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 +6 -1
- package/docs/decisions.md +8 -0
- package/docs/identifiers.md +77 -60
- package/docs/part-b-invariants.md +41 -38
- package/docs/requirements.md +137 -137
- package/fixtures/applicability-fixtures.json +42 -0
- package/fixtures/situation-fixtures.json +600 -329
- package/package.json +4 -1
- package/schema/applicability.schema.json +74 -59
- package/schema/conduct-evaluation.schema.json +1 -1
- package/schema/encounter-evaluation.schema.json +6 -6
- package/schema/evaluation.schema.json +2 -2
- package/schema/facts.schema.json +4 -4
- package/schema/i18n-catalog.schema.json +26 -5
- package/schema/lights.schema.json +2 -3
- package/schema/situation-fixtures.schema.json +39 -0
- package/schema/situation.schema.json +3 -3
|
@@ -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`.
|
package/docs/identifiers.md
CHANGED
|
@@ -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
|
|
76
|
-
entry reads two, and needs to say *whose*
|
|
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 | `
|
|
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
|
-
`
|
|
90
|
-
`
|
|
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 `
|
|
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:
|
|
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: `
|
|
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
|
-
`
|
|
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
|
|
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` | `
|
|
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
|
-
`
|
|
136
|
-
off
|
|
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) —
|
|
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 `
|
|
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
|
-
`
|
|
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
|
|
184
|
+
hands the duty to the wrong vessel. So overtaking history is a fact:
|
|
166
185
|
|
|
167
|
-
- `
|
|
186
|
+
- `self:hist:was_overtaking` — this subject was, earlier in this encounter,
|
|
168
187
|
an overtaking vessel with respect to the other.
|
|
169
|
-
- `
|
|
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 *
|
|
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` | `{"
|
|
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`,
|
|
204
|
-
`keep-clear`, `none`. They are declared
|
|
205
|
-
`effects`, and
|
|
206
|
-
|
|
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),
|
|
212
|
-
(Rule 15), `overtaking` (Rule 13) and
|
|
213
|
-
`data/applicability.json` under
|
|
214
|
-
are
|
|
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
|
|
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
|
|
285
|
-
other vessel stands on. Writing only
|
|
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
|
|
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 `
|
|
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
|