colregs 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROVENANCE.md +3 -3
- package/README.md +19 -10
- package/data/applicability.json +219 -244
- package/data/corpora.json +41 -0
- package/data/editions.json +27 -0
- package/data/facts.json +4 -4
- package/data/geometry.json +8 -8
- package/data/i18n/en.json +27 -0
- package/data/i18n/fi.json +25 -0
- package/data/images.json +32 -32
- package/data/operations.json +79 -0
- package/data/rules.json +176 -599
- package/data/text/intl/2016/en-US.uscg.json +877 -0
- package/data/text/intl/2016/es.boe.json +23 -0
- package/data/text/intl/2016/fi.finlex.json +22 -0
- package/data/text/us/inland/2014/en-US.ecfr.json +22 -0
- package/data/version.json +1 -1
- package/docs/adr/0001-name-and-jurisdiction-model.md +42 -2
- package/docs/adr/0003-language-as-a-dimension.md +8 -7
- package/docs/adr/0006-json-schema-and-identifier-diff.md +2 -0
- package/docs/adr/0011-api-shape.md +1 -1
- package/docs/adr/0012-trace-and-rule2-departure-api.md +3 -4
- package/docs/adr/0013-corpus-files-with-editions.md +70 -0
- package/docs/adr/0014-engine-interface-owned-by-colregs.md +103 -0
- package/docs/adr/0015-rule-ids-are-paragraph-keys.md +191 -0
- package/docs/budgets.json +17 -17
- package/docs/gates.json +3 -3
- package/docs/identifiers.md +49 -51
- package/docs/maritime-sources.md +58 -0
- package/docs/normative-language.md +103 -0
- package/docs/part-b-invariants.md +31 -29
- package/docs/requirements.md +118 -97
- package/docs/timeline.md +102 -0
- package/docs/verification/2026-09-09-text-slug-straw-man.md +3 -2
- package/fixtures/applicability-fixtures.json +190 -190
- package/fixtures/situation-fixtures.json +287 -287
- package/package.json +1 -1
- package/schema/applicability-fixtures.schema.json +4 -13
- package/schema/applicability.schema.json +27 -27
- package/schema/conduct-evaluation.schema.json +135 -0
- package/schema/corpora.schema.json +68 -0
- package/schema/corpus.schema.json +329 -0
- package/schema/display-evaluation.schema.json +146 -0
- package/schema/editions.schema.json +47 -0
- package/schema/encounter-evaluation.schema.json +109 -0
- package/schema/evaluation.schema.json +149 -0
- package/schema/fact-record.schema.json +30 -0
- package/schema/i18n-catalog.schema.json +47 -0
- package/schema/operations.schema.json +124 -0
- package/schema/rule2-departure-finding.schema.json +72 -0
- package/schema/rule2-departure-model.schema.json +159 -0
- package/schema/rules.schema.json +20 -90
- package/schema/situation-fixtures.schema.json +10 -146
- package/schema/situation.schema.json +72 -0
- package/schema/trace.schema.json +33 -0
- package/data/deprecated-identifiers.json +0 -7
- package/schema/deprecated-identifiers.schema.json +0 -29
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "intl@2016.es.boe",
|
|
3
|
+
"edition": "intl@2016",
|
|
4
|
+
"edition_status": "claimed",
|
|
5
|
+
"language": "es",
|
|
6
|
+
"source_id": "boe",
|
|
7
|
+
"tier": "national",
|
|
8
|
+
"normalization": "NFC",
|
|
9
|
+
"source": {
|
|
10
|
+
"publisher": "Agencia Estatal Boletín Oficial del Estado",
|
|
11
|
+
"title": "Instrumento de Adhesión de España al Convenio sobre el Reglamento internacional para prevenir los abordajes, 1972",
|
|
12
|
+
"url": "https://www.boe.es/",
|
|
13
|
+
"retrieved": null
|
|
14
|
+
},
|
|
15
|
+
"rights": {
|
|
16
|
+
"source_text": "BOE aviso legal: reuse permitted, commercial and non-commercial; consolidated texts must not be represented as official",
|
|
17
|
+
"redistribution_basis": "BOE aviso legal, with attribution",
|
|
18
|
+
"attribution": "Fuente de los datos: Agencia Estatal Boletín Oficial del Estado",
|
|
19
|
+
"package_licence": "Apache-2.0"
|
|
20
|
+
},
|
|
21
|
+
"note": "Stub: the BOE instrument id is not yet located; resolve it and the retrieval date before any text lands -- Q-7.",
|
|
22
|
+
"paragraphs": {}
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "intl@2016.fi.finlex",
|
|
3
|
+
"edition": "intl@2016",
|
|
4
|
+
"edition_status": "claimed",
|
|
5
|
+
"language": "fi",
|
|
6
|
+
"source_id": "finlex",
|
|
7
|
+
"tier": "national",
|
|
8
|
+
"normalization": "NFC",
|
|
9
|
+
"source": {
|
|
10
|
+
"publisher": "Finlex (Oikeusministeriö), Suomen säädöskokoelman sopimussarja",
|
|
11
|
+
"title": "Asetus kansainvälisistä säännöistä yhteentörmäämisen ehkäisemiseksi merellä vuonna 1972 tehdyn yleissopimuksen voimaansaattamisesta, SopS 30/1977",
|
|
12
|
+
"url": "https://www.finlex.fi/fi/sopimukset/sopsteksti/1977/19770030",
|
|
13
|
+
"retrieved": "2026-09-12"
|
|
14
|
+
},
|
|
15
|
+
"rights": {
|
|
16
|
+
"source_text": "Tekijänoikeuslaki 404/1961 §9: no copyright in treaties and their official translations",
|
|
17
|
+
"redistribution_basis": "Finlex FAQ: published materials carry no usage restriction; open data CC BY 4.0",
|
|
18
|
+
"package_licence": "Apache-2.0"
|
|
19
|
+
},
|
|
20
|
+
"note": "Stub: identity and provenance only; the Finnish text is not transcribed yet. GATE-2 is adopted in docs/gates.json, settled by ADR 0013, so nothing structural blocks it.",
|
|
21
|
+
"paragraphs": {}
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "us/inland@2014.en-US.ecfr",
|
|
3
|
+
"edition": "us/inland@2014",
|
|
4
|
+
"edition_status": "claimed",
|
|
5
|
+
"language": "en-US",
|
|
6
|
+
"source_id": "ecfr",
|
|
7
|
+
"tier": "national",
|
|
8
|
+
"normalization": "NFC",
|
|
9
|
+
"source": {
|
|
10
|
+
"publisher": "Office of the Federal Register, eCFR",
|
|
11
|
+
"title": "33 CFR Part 83, Navigation Rules (Inland)",
|
|
12
|
+
"url": "https://www.ecfr.gov/current/title-33/chapter-I/subchapter-E/part-83",
|
|
13
|
+
"retrieved": null
|
|
14
|
+
},
|
|
15
|
+
"rights": {
|
|
16
|
+
"source_text": "Work of the United States Government, not subject to copyright in the United States (17 U.S.C. 105)",
|
|
17
|
+
"redistribution_basis": "17 U.S.C. 105",
|
|
18
|
+
"package_licence": "Apache-2.0"
|
|
19
|
+
},
|
|
20
|
+
"note": "Stub: a second jurisdiction, so the key shape is exercised on both axes; the delta itself waits on Q-11 -- REQ-SCOPE-3.",
|
|
21
|
+
"paragraphs": {}
|
|
22
|
+
}
|
package/data/version.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# ADR 0001 — Package name, and jurisdiction as a dimension
|
|
2
2
|
|
|
3
3
|
Date: 2026-08-29
|
|
4
|
-
Status: accepted; amended 2026-09-05 and 2026-09-
|
|
5
|
-
see Amendments). CEVNI's text stays blocked; ADR 0010
|
|
4
|
+
Status: accepted; amended 2026-09-05, 2026-09-09 and 2026-09-12 (licence
|
|
5
|
+
terms verified, see Amendments). CEVNI's text stays blocked; ADR 0010
|
|
6
|
+
unblocks modelling it.
|
|
6
7
|
|
|
7
8
|
## Context
|
|
8
9
|
|
|
@@ -225,3 +226,42 @@ corpus candidates, previously unchecked. Q-6 (authenticity) verified
|
|
|
225
226
|
`es` clears REQ-PROV-2; `fi` and `en`/`fr` via UNTS stay open. Updating
|
|
226
227
|
`docs/requirements.md` Q-7 for this is left as a follow-up (concurrent
|
|
227
228
|
edits to that file are in flight elsewhere).
|
|
229
|
+
|
|
230
|
+
### 2026-09-12 — UNTS and Finlex resolved (issue #81, Q-7)
|
|
231
|
+
|
|
232
|
+
Re-ran both 2026-09-09 checks with a fetch that renders what looked like a
|
|
233
|
+
JS-only shell. Finlex turned out not to need a browser: its Next.js page
|
|
234
|
+
ships the article text inline in a React Server Components payload that a
|
|
235
|
+
plain fetch already receives, just not as visible HTML.
|
|
236
|
+
|
|
237
|
+
**Finlex (`fi`) — resolved clean.** §9, Tekijänoikeuslaki 404/1961: no
|
|
238
|
+
copyright in laws and decrees; in other documents enacted via the Statutes
|
|
239
|
+
Collection and in treaties and similar instruments containing international
|
|
240
|
+
obligations; in the decisions and statements of an authority or other public
|
|
241
|
+
body; or in official translations of the above. Finlex's own FAQ
|
|
242
|
+
(`finlex.fi/fi/ukk`, Ministry of Justice) states published materials carry
|
|
243
|
+
no usage restrictions, and machine-readable open data is licensed CC BY 4.0.
|
|
244
|
+
EFFI's contested claim concerns the compiled database, not the statute text
|
|
245
|
+
§9 already excludes; Finlex's current primary statement outranks that
|
|
246
|
+
secondary source.
|
|
247
|
+
|
|
248
|
+
**UNTS (`en`/`fr`) — resolved, and it does not clear.** Checked the UNTS
|
|
249
|
+
intro and FAQ pages on treaties.un.org and the copyright and terms-of-use
|
|
250
|
+
pages on un.org directly; none needed a browser. No UNTS-specific rights
|
|
251
|
+
page exists anywhere in that set — every path leads to the same general UN
|
|
252
|
+
terms already found blocking CEVNI: personal, non-commercial use, with no
|
|
253
|
+
right to resell, redistribute or create derivative works. Confirmed
|
|
254
|
+
blocked, not merely ambiguous.
|
|
255
|
+
|
|
256
|
+
Per Q-7's per-language routing, `es` (BOE, cleared 2026-09-09) is the
|
|
257
|
+
corpus that unblocks the first non-`intl` text; `fi` is now open alongside
|
|
258
|
+
it. UNTS stays closed pending written UN permission or a national
|
|
259
|
+
republication of the authentic `en`/`fr` text.
|
|
260
|
+
|
|
261
|
+
Sources: `fi` —
|
|
262
|
+
<https://www.finlex.fi/fi/laki/ajantasa/1961/19610404> (§9);
|
|
263
|
+
<https://www.finlex.fi/fi/ukk> (usage terms, CC BY 4.0). UNTS —
|
|
264
|
+
<https://treaties.un.org/Pages/Content.aspx?path=DB/UNTS/pageIntro_en.xml>;
|
|
265
|
+
<https://treaties.un.org/Pages/Overview.aspx?path=overview/faq/page1_en.xml>;
|
|
266
|
+
<https://www.un.org/en/about-us/copyright>;
|
|
267
|
+
<https://www.un.org/en/about-us/terms-of-use>.
|
|
@@ -79,8 +79,10 @@ with BCP 47 codes, structured as three layers:
|
|
|
79
79
|
consolidated state*: the skeleton declares, as data, the amendment state
|
|
80
80
|
it consolidates, and every corpus declares the amendment state its
|
|
81
81
|
source reflects (REQ-LANG-10). A mismatch is legitimate but
|
|
82
|
-
machine-visible — declared staleness, never silence.
|
|
83
|
-
|
|
82
|
+
machine-visible — declared staleness, never silence. (GATE-2 adopted in
|
|
83
|
+
ADR 0013: `data/editions.json` now registers editions in force
|
|
84
|
+
*concurrently*, so "historical states are prior package versions" holds
|
|
85
|
+
only for a fully superseded edition, not one still in transition.) A
|
|
84
86
|
renumbering amendment is a major version under REQ-PKG-4 — but it is
|
|
85
87
|
resolved by *issuing new paragraph paths and deprecating the old ones*
|
|
86
88
|
(REQ-MODEL-10/11), never by repointing an existing path at different
|
|
@@ -204,11 +206,10 @@ check. What was declined, and why — recorded so it isn't re-argued:
|
|
|
204
206
|
renumbering lands — or when Q-8's check of the first national
|
|
205
207
|
amalgamation shows paragraph paths do not survive it.
|
|
206
208
|
- **A full temporal/legal-version model** (instrument → edition → corpus as
|
|
207
|
-
first-class layers; GATE-2).
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
that is a new ADR.
|
|
209
|
+
first-class layers; GATE-2). Declined *here*; the cheap 80% (declared
|
|
210
|
+
amendment state, machine-visible mismatch) was adopted instead. **Re-taken
|
|
211
|
+
and adopted in ADR 0013**, on the trigger this bullet named: a
|
|
212
|
+
jurisdiction requiring multiple concurrent editions.
|
|
212
213
|
- **`dir: ltr|rtl` metadata per language.** Derivable from the language
|
|
213
214
|
tag via CLDR by any consumer that needs it; storing it invites drift.
|
|
214
215
|
|
|
@@ -167,7 +167,7 @@ resolution, and validation of the situation record.
|
|
|
167
167
|
| `own` required, `other`/`Subject.fact` per colregs 0.2.0's fixture schema | ✎ | revised 2026-09-07 from "own/other both required"; Mark to confirm before ink |
|
|
168
168
|
| `appliedEncounterEntries` as the fixture-replay companion | ✎ | the situation-fixture replay being written |
|
|
169
169
|
| Field names snake_case with unit suffixes across both ADRs; `EntryId`/`ParagraphCite` alias `string` for ids and cites | ✎ | the rename's alias window closing; a consumer arguing the compiler should enforce the two apart |
|
|
170
|
-
| `EncounterEvaluation` field set (§4) | ✎ | building it; Q-35, Q-36, Q-43 in colregs |
|
|
170
|
+
| `EncounterEvaluation` field set (§4); `categories` and `provenance` added 2026-09-16 beyond the block above, as `DisplayEvaluation` carries them — colregs-engine 0.1.5 built them and ADR 0014's `encounter-evaluation.schema.json` is now the shape | ✎ | building it; Q-35, Q-36, Q-43 in colregs |
|
|
171
171
|
| `encounter` absent vs the ADR 0005 §5 status alphabet | ✎ | Q-43 |
|
|
172
172
|
| `conduct` is a separate package, not a third verb | ✎ | superseded by ADR 0012: a third and fourth verb, in this package |
|
|
173
173
|
| Geometry-consistency validation (REQ-VERIFY-8) in the engine's validator | ? | deciding whether it is data-suite-only |
|
|
@@ -183,9 +183,8 @@ trace becomes a fixture; a certified grid, a `Rule2DepartureModel`.
|
|
|
183
183
|
2. `Trace`, `appliedConductEntries` against those fixtures — phase 3
|
|
184
184
|
starts here.
|
|
185
185
|
3. `evaluateConduct` with verdicts and phases; the constants above.
|
|
186
|
-
4. `evaluateRule2Departure` once a grid exists. Until then the name is
|
|
187
|
-
|
|
188
|
-
is a stub wearing a status.
|
|
186
|
+
4. `evaluateRule2Departure` once a grid exists. ~~Until then the name is reserved and nothing exported: a stub answering `inconclusive-in-model` is a stub wearing a status.~~
|
|
187
|
+
**Struck. Reserving the name was never the policy here: a name gets an export the day it is written down, throwing or answering `inconclusive-in-model`. Withholding an export to look careful is the failure mode, not the stub. — Solace, 2026-09-13**
|
|
189
188
|
|
|
190
189
|
## Register
|
|
191
190
|
|
|
@@ -200,4 +199,4 @@ trace becomes a fixture; a certified grid, a `Rule2DepartureModel`.
|
|
|
200
199
|
| Vague-quantity constants live in colregs; `SolverParameters` on the model and echoed on the finding, `colregs_version` naming the release solved against; nothing else on the model is API | ✎ | the first constant a conduct entry reads; Q-19's sensitivity matrix |
|
|
201
200
|
| `Rule2DepartureFinding` field set — `rules`, `Rule2DepartureAdvisory[]`, no banner cite (it is a function of `status`); the status alphabet is colregs' (ADR 0005 §5), not this package's to rename | ✎ | proposal v4 §4's sensitivity matrix; Q-19, Q-20 |
|
|
202
201
|
| No *situation* input names a departure; the grid does, and is named in every finding | ✎ | — |
|
|
203
|
-
|
|
|
202
|
+
| A name is exported the day it is written down, throwing or answering `inconclusive-in-model`; exports carry TSDoc's `@beta` release tag until a fixture backs them | ✎ | — |
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# ADR 0013 — Rule text as corpus files under an edition registry (GATE-2 adopted)
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-12
|
|
4
|
+
Status: accepted (2026-09-13)
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
ADR 0003 made language a dimension and sketched its layout, but nothing
|
|
9
|
+
landed, and its GATE-2 (an edition layer between instrument and corpus) is
|
|
10
|
+
due for re-take *before* the first non-English corpus. Finlex (`fi`) and BOE
|
|
11
|
+
(`es`) are both licence-clear (ADR 0001, 2026-09-12), so the next text to
|
|
12
|
+
land forces the key shape. This ADR fixes the keys and leaves the words for
|
|
13
|
+
issue #98. A sibling proposal (branch `multi-jurisdiction-schema`, option A)
|
|
14
|
+
confirms GATE-2 declined instead; the two are meant to be read side by side.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
1. **`data/rules.json` is the skeleton.** Per paragraph: `path`, `rule`,
|
|
19
|
+
`jurisdiction`, `images`. No `text`, no `rule_title`, no source, and no
|
|
20
|
+
amendment state of its own. It gains `24(g)(i)`: the Convention has the
|
|
21
|
+
path; the USCG page lacks the words, which is the corpus's gap.
|
|
22
|
+
2. **`data/editions.json` is the registry: jurisdiction → instrument →
|
|
23
|
+
editions.** An edition id is `<jurisdiction>@<tag>` (`intl@2016`,
|
|
24
|
+
`us/inland@2014`), carrying `amended_through`, `in_force` and optionally
|
|
25
|
+
`superseded_by`. **The tag is the `in_force` year, nothing else** — the
|
|
26
|
+
amending instrument (an IMO resolution, a Federal Register cite) belongs
|
|
27
|
+
in `amended_through`, never in the tag. Each jurisdiction names the
|
|
28
|
+
edition the skeleton consolidates. Two editions of
|
|
29
|
+
one jurisdiction may be registered at once, which is GATE-2's trigger
|
|
30
|
+
case expressed as data rather than as a diff between strings.
|
|
31
|
+
3. **One file per corpus**,
|
|
32
|
+
`data/text/<jurisdiction>/<tag>/<language>.<source_id>.json`, identity
|
|
33
|
+
`<edition>.<language>.<source_id>`. A corpus names its `edition` and
|
|
34
|
+
nothing about jurisdiction or amendment state: both are the edition's.
|
|
35
|
+
CI checks the edition is registered and the filename agrees.
|
|
36
|
+
4. **A corpus paragraph is `rule_title` + `text`** (or ADR 0010's withheld
|
|
37
|
+
fields). Corpus metadata otherwise as in option A: `tier`,
|
|
38
|
+
`normalization`, `source` (REQ-PROV-6), `rights` (three statements),
|
|
39
|
+
`gaps`, optional `translation_of`.
|
|
40
|
+
5. **`data/corpora.json` indexes the files**, derived and drift-checked.
|
|
41
|
+
6. **GATE-2 is re-taken and adopted.** The cost is one registry file and
|
|
42
|
+
one extra path segment; the gain is that "which consolidated state" is an
|
|
43
|
+
identifier compared by equality, not free text compared by eye, and a
|
|
44
|
+
stub must name an edition before it names anything else.
|
|
45
|
+
|
|
46
|
+
## What the stubs show
|
|
47
|
+
|
|
48
|
+
Four corpora ship: `intl@2016.en-US.uscg` (today's text, moved verbatim),
|
|
49
|
+
`intl@2016.fi.finlex`, `intl@2016.es.boe` and `us/inland@2014.en-US.ecfr`,
|
|
50
|
+
the last three empty. The edition layer forces a claim option A lets a stub
|
|
51
|
+
defer: the Finnish and Spanish stubs assert they reflect `intl@2016` before
|
|
52
|
+
anyone has checked, and the `us/inland@2014` edition is recalled, not
|
|
53
|
+
verified, and says so. That is the honest cost of this option and the reason
|
|
54
|
+
to look at both.
|
|
55
|
+
|
|
56
|
+
## Consequences
|
|
57
|
+
|
|
58
|
+
- Breaking file layout (REQ-PKG-4): `data/rules.json` no longer carries
|
|
59
|
+
text. colregs-engine's conformance research reads only `paragraphs` keys
|
|
60
|
+
and is unaffected; anything reading `.text` moves to the en-US corpus.
|
|
61
|
+
- ADR 0010's withheld mechanics move to the corpus paragraph, where the
|
|
62
|
+
text is; the skeleton has nothing to withhold.
|
|
63
|
+
- A new IMO amendment is a new registered edition, a new skeleton pointer,
|
|
64
|
+
and new corpus files under a new directory; the old ones may stay until
|
|
65
|
+
the last consumer moves, marked `superseded_by`. Under option A the same
|
|
66
|
+
event is an in-place edit of every corpus's `amendment_state`.
|
|
67
|
+
- REQ-LANG-1, -3, -5, -9, -10 and REQ-PROV-6 move from unimplemented to
|
|
68
|
+
implemented for the data on file; catalogs (REQ-LANG-6) are untouched.
|
|
69
|
+
- Landing Finnish or Spanish words is an edit to one file and one number in
|
|
70
|
+
the index, and still waits on this ADR being accepted (REQ-GATE-2).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# ADR 0014 — The engine interface is colregs' to own: an operations manifest and result schemas
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-16
|
|
4
|
+
Status: proposed. Solace ordered options 2 and 3 built on 2026-09-16; the
|
|
5
|
+
register marks what settles each row.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
ADR 0011 and ADR 0012 fix the engine's verbs and result envelopes, in prose
|
|
10
|
+
and TypeScript blocks, in this repository. The only machine-readable copy
|
|
11
|
+
lives in colregs-engine's `src/types.ts`, so the interface is de facto
|
|
12
|
+
colregs-owned and de jure the engine's: a second engine, in any language,
|
|
13
|
+
would transcribe the ADRs by hand and drift the way the engine's own
|
|
14
|
+
generator was written to stop.
|
|
15
|
+
|
|
16
|
+
colregs already owns *shapes* in production: the engine compiles every
|
|
17
|
+
`schema/*.schema.json` into generated types and derives `FactRecord` and
|
|
18
|
+
`Situation` from `data/facts.json`. What it does not own is the *operations*
|
|
19
|
+
— which verb reads which input and answers which envelope — and the fixture
|
|
20
|
+
files bind to verbs only by convention.
|
|
21
|
+
|
|
22
|
+
Ten routes were surveyed on 2026-09-16 for making the interface a colregs
|
|
23
|
+
artefact, the Java sense of interface against implementation:
|
|
24
|
+
|
|
25
|
+
| # | route | what it gives, what it costs |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| 1 | result schemas only, verbs stay prose | shapes checkable, operations still hand-read |
|
|
28
|
+
| 2 | **operations manifest**: verb → inputs → result → companion, in JSON | language-neutral; any engine derives its own binding |
|
|
29
|
+
| 3 | **fixtures bound to verbs** in the manifest | the conformance replay becomes the behavioural contract |
|
|
30
|
+
| 4 | hand-written `.d.ts` in colregs | TypeScript-only; the transcription the engine's generator exists to avoid |
|
|
31
|
+
| 5 | `x-operation` keywords inside the existing schemas | option 2 spread over eleven files |
|
|
32
|
+
| 6 | OpenAPI / AsyncAPI | a projection generable *from* 2, not a source |
|
|
33
|
+
| 7 | Protobuf / Smithy | model operations natively; a toolchain a data-only package does not have |
|
|
34
|
+
| 8 | TypeSpec | one source projecting JSON Schema and OpenAPI; same toolchain cost |
|
|
35
|
+
| 9 | a separate `colregs-api` package | a third release train for two files |
|
|
36
|
+
| 10 | status quo | the drift above |
|
|
37
|
+
|
|
38
|
+
## Decision
|
|
39
|
+
|
|
40
|
+
1. **`data/operations.json` is the interface.** One operation per verb ADR
|
|
41
|
+
0011 and ADR 0012 name, each with positional `inputs` (name and schema),
|
|
42
|
+
an `output` schema, the entry-id `companion` verb where one exists, and
|
|
43
|
+
the `fixtures` that exercise it. `schema/operations.schema.json` checks
|
|
44
|
+
its shape; `test/data.test.mjs` checks that every reference resolves.
|
|
45
|
+
2. **Result envelopes are schemas under `schema/`:** `display-evaluation`,
|
|
46
|
+
`encounter-evaluation`, `conduct-evaluation`, `rule2-departure-finding`,
|
|
47
|
+
transcribed from the engine's `src/types.ts` at colregs-engine 0.1.5.
|
|
48
|
+
JSON-Schema-expressible only: `Record<EntryId, Modality>` becomes
|
|
49
|
+
`patternProperties`, the `EntryId` and `ParagraphCite` aliases become
|
|
50
|
+
`$defs`, the two deprecated camelCase aliases are marked `deprecated` and
|
|
51
|
+
optional. From here the schema is the normative statement of each
|
|
52
|
+
envelope; the TypeScript blocks in ADR 0011 §4 and ADR 0012 §2–4 are
|
|
53
|
+
illustrations of record, and an envelope changes here first, the engine
|
|
54
|
+
second.
|
|
55
|
+
3. **Inputs get schemas too:** `fact-record`, `situation`, `trace`,
|
|
56
|
+
`rule2-departure-model`. The two fixture schemas `$ref` the first two
|
|
57
|
+
for a case's `facts` and `situation` instead of carrying a copy; a fixture
|
|
58
|
+
case is the verb's input, and the tests validate every bound case against
|
|
59
|
+
it.
|
|
60
|
+
4. **Schema files compose by `$ref`,** relative to their `$id`
|
|
61
|
+
(`applicability.schema.json#/$defs/ruleId`). ADR 0006's "no cross-file
|
|
62
|
+
references" is about data references — cite to `rules.json` — which stay in
|
|
63
|
+
the tests; a `$ref` between two schema files is one shape reused, not a
|
|
64
|
+
data reference. The suite registers every schema by `$id` before compiling.
|
|
65
|
+
What every envelope shares — the `colregs` stamp, `provenance`, the
|
|
66
|
+
`ruleId` and `paragraphCite` vocabularies — is `schema/evaluation.schema.json`,
|
|
67
|
+
`$defs` only, so moving one later is never a two-repository change.
|
|
68
|
+
5. **A fixture file is bound to a verb** by `fixtures[].file` and
|
|
69
|
+
`case_inputs`, one case key per positional input, with the answer under
|
|
70
|
+
`expect`: the companion's entry ids unless the binding names another
|
|
71
|
+
schema. A case's `status` and `jurisdiction` are the fixture file's own.
|
|
72
|
+
Every file under `fixtures/` must be bound; `evaluateConduct` and
|
|
73
|
+
`evaluateRule2Departure` bind nothing yet, and say so with an empty list.
|
|
74
|
+
6. **Not in the manifest:** trailing options a binding accepts (`opts.data`),
|
|
75
|
+
which verbs are built, and what a verb throws. Build status is ADR 0011's
|
|
76
|
+
and ADR 0012's tables; errors are the binding's.
|
|
77
|
+
|
|
78
|
+
## Consequences
|
|
79
|
+
|
|
80
|
+
- Ten new files under `schema/`. The engine's `generate-schema-types.ts`
|
|
81
|
+
throws on any schema its `ROOT_NAMES` does not list, so the next colregs
|
|
82
|
+
bump fails its build until colregs-engine#86 lands: emit
|
|
83
|
+
`interface ColregsEngine` from `operations.json`, `satisfies ColregsEngine`
|
|
84
|
+
at the engine's root, settle the `fact-record.ts` / `situation.ts`
|
|
85
|
+
collision with the types it derives from `facts.json` today, and validate
|
|
86
|
+
real output against these schemas for every fixture case.
|
|
87
|
+
- A YAML rendering of the manifest is lossless; XML is a generated view (JSON
|
|
88
|
+
Schema to XSD). Authoring the interface in TypeScript first is the one route
|
|
89
|
+
that forecloses both.
|
|
90
|
+
- The 2026-09-16 hand-off named the finding's schema `rule2-departure`; it
|
|
91
|
+
lands as `rule2-departure-finding` beside `rule2-departure-model`, since
|
|
92
|
+
the verb has two shapes to name.
|
|
93
|
+
|
|
94
|
+
## Register
|
|
95
|
+
|
|
96
|
+
| item | level | what would settle it |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| Options 2 and 3, not 1 or 4–10 | ✎ | this ADR accepted |
|
|
99
|
+
| Manifest shape: positional `inputs`, `output`, `companion`, `fixtures[].case_inputs` and `expect` | ✎ | the engine generator consuming it; the first conduct or Rule 2 fixture |
|
|
100
|
+
| Envelope schemas transcribed from `types.ts`, envelope changes land here first | ✎ | the first envelope change after the follow-up |
|
|
101
|
+
| Cross-file `$ref` between schema files | ✎ | a consumer whose validator cannot register a schema set |
|
|
102
|
+
| Deprecated aliases optional in the schema, required in the engine | ✎ | the engine dropping them |
|
|
103
|
+
| `opts` and thrown errors stay out of the manifest | ✎ | a second binding needing either |
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# ADR 0015 — Rule ids are paragraph keys in the `rule:` namespace
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-16
|
|
4
|
+
Status: accepted — Solace's ruling, 2026-09-16
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Issue #121 asked what an applicability entry should be called, and ruling C
|
|
9
|
+
was read as *names, not citations*. The first draft of this ADR wrote that
|
|
10
|
+
reading out: every id became a coined name in an `entry:` namespace —
|
|
11
|
+
`entry:anchored`, `entry:towing_mastheads_long_tow`, `entry:being_overtaken`.
|
|
12
|
+
It never landed and never shipped. Solace superseded that reading on
|
|
13
|
+
2026-09-16 and this file is the replacement.
|
|
14
|
+
|
|
15
|
+
What the rename was buying was insulation: `cite` is a data field, revised
|
|
16
|
+
whenever the package reads the Rules better, and `14a` had already become
|
|
17
|
+
`14b` when the reading of *which paragraph deems a head-on* moved. An
|
|
18
|
+
identifier containing a data field gets renamed every time the field is.
|
|
19
|
+
|
|
20
|
+
What it cost is the vocabulary. An entry is a norm out of the Convention,
|
|
21
|
+
and the people who read these ids — and the consumers that store them
|
|
22
|
+
alongside a `cite` anyway — already have a name for every norm in the
|
|
23
|
+
package: the Rule. `rule:13b` is *Rule 13(b)* in the mariner's mouth. A
|
|
24
|
+
coined name is a second vocabulary beside that one, invented here,
|
|
25
|
+
unguessable from the Rules, and needing a lookup in this file to be read
|
|
26
|
+
back as the paragraph it came from. The insulation is worth one rename in
|
|
27
|
+
a pre-1.0 package; the second vocabulary is permanent. Ruled by Solace,
|
|
28
|
+
2026-09-16.
|
|
29
|
+
|
|
30
|
+
## Decision
|
|
31
|
+
|
|
32
|
+
1. **The namespace noun is `rule:`.** It replaces `entry:` everywhere. The
|
|
33
|
+
overload with the Convention's Rules is the point, not an accident to
|
|
34
|
+
be designed around.
|
|
35
|
+
2. **An id is `rule:<paragraph-slug>`.** The slug is the cite with its
|
|
36
|
+
punctuation dropped: rule number, paragraph letter attached, each roman
|
|
37
|
+
subparagraph joined by `_`. `27(a)(i)` → `27a_i`; `13(b)` → `13b`;
|
|
38
|
+
`11` → `11`; `23(a)(iii)-(iv)` → `23a_iii_iv`.
|
|
39
|
+
3. **A bare id is the paragraph's principal norm.** Where the text yields
|
|
40
|
+
a further norm from the same paragraph, it takes a third segment after
|
|
41
|
+
a colon, named in the text's own words: `rule:24a_i` is 24(a)(i)'s two
|
|
42
|
+
masthead lights and `rule:24a_i:exceeds_200m` its three, the threshold
|
|
43
|
+
being the Convention's own, which is when a number may appear in a name.
|
|
44
|
+
4. **Where one sentence fuses a deeming test and a duty, both norms are
|
|
45
|
+
named.** 15(a) gives `rule:15a:crossing` — the classification, effect
|
|
46
|
+
`encounter: crossing` — and `rule:15a:keep_out_of_the_way` — the
|
|
47
|
+
precedence, own give-way. `avoid_crossing_ahead` is reserved for 15(a)'s
|
|
48
|
+
second duty when the `conduct` shape exists; it is not in use.
|
|
49
|
+
5. **A Convention subparagraph keeps its own cite.** 30(d)'s chapeau is
|
|
50
|
+
`rule:30d`, citing `30(d)`, importing the 30(a)/(b) anchor lights,
|
|
51
|
+
modality `shall`; the two all-round red lights are `rule:30d_i`, citing
|
|
52
|
+
`30(d)(i)`, modality `shall-if-practicable` because the paragraph says
|
|
53
|
+
"if practicable". `30(d)(i)` joins `rules.json` as a skeleton path with
|
|
54
|
+
this change. `rule:30d_ii` is reserved for the three balls, and lands
|
|
55
|
+
when day shapes do.
|
|
56
|
+
6. **A jurisdiction override is named by its difference**, and jurisdiction
|
|
57
|
+
stays a field: `rule:30a:mooring_buoy` and `rule:30b:mooring_buoy`, both
|
|
58
|
+
`us/inland`. The name says what 33 CFR 90.5 adds, not who adds it.
|
|
59
|
+
7. **13(b) is one symmetric entry.** The two entries that preceded it were
|
|
60
|
+
identical but for which subject's `geo:rel_bearing_deg` fell in
|
|
61
|
+
(112.5, 247.5), and both produced `encounter: overtaking`. `rule:13b`
|
|
62
|
+
reads the same sector object on either subject under `any_of` — the
|
|
63
|
+
pattern 13(d) already used over `hist:was_overtaking` — because the
|
|
64
|
+
encounter type belongs to the pair. One paragraph, one norm, one id.
|
|
65
|
+
8. **Every other entry with a unique cite takes the bare slug of it.**
|
|
66
|
+
9. **`represented_paragraphs` ids take the prefix too** — `2a` becomes
|
|
67
|
+
`rule:2a` — and keep their own list. They no longer need a schema
|
|
68
|
+
definition of their own: the id pattern is the same one.
|
|
69
|
+
10. **The schema pattern is**
|
|
70
|
+
`^rule:[0-9]+[a-z]?(_[ivx]+)*(:[a-z][a-z0-9_]*)?$`.
|
|
71
|
+
**An id is opaque to a consumer.** It looks like a citation and is not
|
|
72
|
+
one: a consumer that wants the paragraph reads `cite`, and never splits
|
|
73
|
+
the id to find it. The resemblance is for the human reading a trace;
|
|
74
|
+
the `cite` field is the machine-readable link to the Convention, and it
|
|
75
|
+
is the field that moves when the package's reading of a paragraph moves.
|
|
76
|
+
11. **The identifier-diff mechanism is removed, not left dormant.**
|
|
77
|
+
`retired_entry_ids`, `data/deprecated-identifiers.json`, its schema, the
|
|
78
|
+
dormant diff test and the retired-id half of the REQ-MODEL-10 collision
|
|
79
|
+
test all go. Nothing pre-1.0 is immutable, so there was nothing for the
|
|
80
|
+
registry to hold and no diff for the test to run; a mechanism that
|
|
81
|
+
cannot fire is a mechanism nobody maintains. It returns, if at all, with
|
|
82
|
+
the 1.0.0 tag that REQ-MODEL-10's baseline names. Ruled by Solace,
|
|
83
|
+
2026-09-16.
|
|
84
|
+
|
|
85
|
+
## The names
|
|
86
|
+
|
|
87
|
+
`was` is the `entry:` name from the superseded draft of this ADR, which
|
|
88
|
+
shipped in no release. The citation-derived ids that preceded *it* —
|
|
89
|
+
`24a-m2`, `23a1`, `30d-red` — were never names either and are not carried
|
|
90
|
+
forward.
|
|
91
|
+
|
|
92
|
+
| was | is | cite |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `entry:power_forward_masthead` | `rule:23a_i` | 23(a)(i) |
|
|
95
|
+
| `entry:power_second_masthead` | `rule:23a_ii` | 23(a)(ii) |
|
|
96
|
+
| `entry:power_sidelights_sternlight` | `rule:23a_iii_iv` | 23(a)(iii)-(iv) |
|
|
97
|
+
| `entry:air_cushion_non_displacement` | `rule:23b` | 23(b) |
|
|
98
|
+
| `entry:wig_near_surface` | `rule:23c` | 23(c) |
|
|
99
|
+
| `entry:power_under_12m` | `rule:23d_i` | 23(d)(i) |
|
|
100
|
+
| `entry:power_under_7m_slow` | `rule:23d_ii` | 23(d)(ii) |
|
|
101
|
+
| `entry:towing_mastheads` | `rule:24a_i` | 24(a)(i) |
|
|
102
|
+
| `entry:towing_mastheads_long_tow` | `rule:24a_i:exceeds_200m` | 24(a)(i) |
|
|
103
|
+
| `entry:towing_light` | `rule:24a_ii_iv` | 24(a)(ii)-(iv) |
|
|
104
|
+
| `entry:pushing_composite_unit` | `rule:24b` | 24(b) |
|
|
105
|
+
| `entry:pushing_ahead_or_alongside` | `rule:24c` | 24(c) |
|
|
106
|
+
| `entry:being_towed` | `rule:24e` | 24(e) |
|
|
107
|
+
| `entry:sail_sidelights_sternlight` | `rule:25a` | 25(a) |
|
|
108
|
+
| `entry:sail_combined_lantern` | `rule:25b` | 25(b) |
|
|
109
|
+
| `entry:sail_red_over_green` | `rule:25c` | 25(c) |
|
|
110
|
+
| `entry:sail_under_7m` | `rule:25d_i` | 25(d)(i) |
|
|
111
|
+
| `entry:under_oars` | `rule:25d_ii` | 25(d)(ii) |
|
|
112
|
+
| `entry:trawling_green_over_white` | `rule:26b_i` | 26(b)(i) |
|
|
113
|
+
| `entry:trawling_masthead` | `rule:26b_ii` | 26(b)(ii) |
|
|
114
|
+
| `entry:trawling_making_way` | `rule:26b_iii` | 26(b)(iii) |
|
|
115
|
+
| `entry:fishing_red_over_white` | `rule:26c_i` | 26(c)(i) |
|
|
116
|
+
| `entry:fishing_outlying_gear` | `rule:26c_ii` | 26(c)(ii) |
|
|
117
|
+
| `entry:fishing_making_way` | `rule:26c_iii` | 26(c)(iii) |
|
|
118
|
+
| `entry:nuc_red_over_red` | `rule:27a_i` | 27(a)(i) |
|
|
119
|
+
| `entry:nuc_making_way` | `rule:27a_iii` | 27(a)(iii) |
|
|
120
|
+
| `entry:ram_red_white_red` | `rule:27b_i` | 27(b)(i) |
|
|
121
|
+
| `entry:ram_making_way` | `rule:27b_iii` | 27(b)(iii) |
|
|
122
|
+
| `entry:ram_anchored` | `rule:27b_iv` | 27(b)(iv) |
|
|
123
|
+
| `entry:ram_underwater_obstruction` | `rule:27d` | 27(d) |
|
|
124
|
+
| `entry:diving` | `rule:27e_i` | 27(e)(i) |
|
|
125
|
+
| `entry:mine_clearance` | `rule:27f` | 27(f) |
|
|
126
|
+
| `entry:cbd` | `rule:28` | 28 |
|
|
127
|
+
| `entry:pilot` | `rule:29a` | 29(a) |
|
|
128
|
+
| `entry:anchored` | `rule:30a` | 30(a) |
|
|
129
|
+
| `entry:anchored_under_50m` | `rule:30b` | 30(b) |
|
|
130
|
+
| `entry:anchored_deck_lights` | `rule:30c` | 30(c) |
|
|
131
|
+
| `entry:aground_anchor_lights` | `rule:30d` | 30(d) |
|
|
132
|
+
| `entry:aground_red_over_red` | `rule:30d_i` | 30(d)(i) |
|
|
133
|
+
| `entry:anchored_under_7m_not_near_channel` | `rule:30e` | 30(e) |
|
|
134
|
+
| `entry:any_visibility` | `rule:4` | 4 |
|
|
135
|
+
| `entry:in_sight` | `rule:11` | 11 |
|
|
136
|
+
| `entry:restricted_visibility` | `rule:19a` | 19(a) |
|
|
137
|
+
| `entry:power_gives_way_to_nuc` | `rule:18a_i` | 18(a)(i) |
|
|
138
|
+
| `entry:power_gives_way_to_ram` | `rule:18a_ii` | 18(a)(ii) |
|
|
139
|
+
| `entry:power_gives_way_to_fishing` | `rule:18a_iii` | 18(a)(iii) |
|
|
140
|
+
| `entry:power_gives_way_to_sail` | `rule:18a_iv` | 18(a)(iv) |
|
|
141
|
+
| `entry:sail_gives_way_to_nuc` | `rule:18b_i` | 18(b)(i) |
|
|
142
|
+
| `entry:sail_gives_way_to_ram` | `rule:18b_ii` | 18(b)(ii) |
|
|
143
|
+
| `entry:sail_gives_way_to_fishing` | `rule:18b_iii` | 18(b)(iii) |
|
|
144
|
+
| `entry:fishing_gives_way_to_nuc` | `rule:18c_i` | 18(c)(i) |
|
|
145
|
+
| `entry:fishing_gives_way_to_ram` | `rule:18c_ii` | 18(c)(ii) |
|
|
146
|
+
| `entry:avoid_impeding_cbd` | `rule:18d_i` | 18(d)(i) |
|
|
147
|
+
| `entry:wig_keeps_well_clear` | `rule:18f_i` | 18(f)(i) |
|
|
148
|
+
| `entry:not_impeded_remains_obliged` | `rule:8f_iii` | 8(f)(iii) |
|
|
149
|
+
| `entry:overtaking_gives_way` | `rule:13a` | 13(a) |
|
|
150
|
+
| `entry:narrow_channel_small_or_sail` | `rule:9b` | 9(b) |
|
|
151
|
+
| `entry:narrow_channel_fishing` | `rule:9c` | 9(c) |
|
|
152
|
+
| `entry:traffic_lane_fishing` | `rule:10i` | 10(i) |
|
|
153
|
+
| `entry:traffic_lane_small_or_sail` | `rule:10j` | 10(j) |
|
|
154
|
+
| `entry:steady_bearing` | `rule:7d_i` | 7(d)(i) |
|
|
155
|
+
| `entry:sail_port_tack_gives_way` | `rule:12a_i` | 12(a)(i) |
|
|
156
|
+
| `entry:sail_windward_gives_way` | `rule:12a_ii` | 12(a)(ii) |
|
|
157
|
+
| `entry:sail_port_tack_uncertain_gives_way` | `rule:12a_iii` | 12(a)(iii) |
|
|
158
|
+
| `entry:overtaking` | `rule:13b` | 13(b) |
|
|
159
|
+
| `entry:being_overtaken` | `rule:13b` | 13(b) |
|
|
160
|
+
| `entry:overtaking_until_past_and_clear` | `rule:13d` | 13(d) |
|
|
161
|
+
| `entry:head_on` | `rule:14b` | 14(b) |
|
|
162
|
+
| `entry:crossing` | `rule:15a:crossing` | 15(a) |
|
|
163
|
+
| `entry:crossing_gives_way` | `rule:15a:keep_out_of_the_way` | 15(a) |
|
|
164
|
+
| `entry:mooring_buoy` | `rule:30a:mooring_buoy` | 30(a) |
|
|
165
|
+
| `entry:mooring_buoy_under_50m` | `rule:30b:mooring_buoy` | 30(b) |
|
|
166
|
+
|
|
167
|
+
## Consequences
|
|
168
|
+
|
|
169
|
+
- One pass, one commit: `data/applicability.json`, both fixture files,
|
|
170
|
+
`data/geometry.json`, `data/images.json`, `data/facts.json`,
|
|
171
|
+
`data/rules.json`, the schemas, the suite and the living docs. The
|
|
172
|
+
schema `$defs` that named the type follow the noun: `entryId` is
|
|
173
|
+
`ruleId`, `entryIdList` is `ruleIdList`.
|
|
174
|
+
- Two entries may still share a cite — `rule:24a_i` and
|
|
175
|
+
`rule:24a_i:exceeds_200m`, the two halves of 15(a), the two mooring-buoy
|
|
176
|
+
deltas. What they never share is an id, and the third segment says which
|
|
177
|
+
norm out of the paragraph the entry carries.
|
|
178
|
+
- Earlier ADRs and closed questions cite entries by the superseded names;
|
|
179
|
+
the table above is the map and stays in this file permanently. ADR 0006
|
|
180
|
+
carries a dated note that its identifier-diff half is gone.
|
|
181
|
+
- `represented_paragraphs` is now a second list of records that look like
|
|
182
|
+
entries, differing only in carrying no `when`. Folding it into `entries`
|
|
183
|
+
with a `care`/`meta` category is the obvious follow-up and is not done
|
|
184
|
+
here.
|
|
185
|
+
- Open, and not ruled: whether `fact:rule18_class` should be renamed. It
|
|
186
|
+
carries a rule number inside a fact name, which is the shape this ADR
|
|
187
|
+
moved *entry* ids towards and says nothing about for facts. Left visibly
|
|
188
|
+
open.
|
|
189
|
+
- An encounter with more than two vessels is issue #135; nothing in this
|
|
190
|
+
scheme is aimed at it, and a third subject would need a segment none of
|
|
191
|
+
these ids have.
|