@aicent/rttp 0.1.0 → 0.1.2
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 +42 -17
- package/examples/quickstart.mjs +20 -0
- package/package.json +3 -2
- package/src/conformance.mjs +3 -1
- package/src/rttp-uri.d.mts +1 -1
- package/src/rttp-uri.mjs +9 -6
- package/src/vectors.json +20 -8
package/README.md
CHANGED
|
@@ -22,6 +22,19 @@ locally — offline, no account, no network. `[PASS]` or it is not.
|
|
|
22
22
|
The JavaScript and Python sides **share no code**. They are separate
|
|
23
23
|
implementations of the same published vectors, and they agree byte for byte.
|
|
24
24
|
|
|
25
|
+
### The other half — Python, on PyPI
|
|
26
|
+
|
|
27
|
+
```console
|
|
28
|
+
$ pip install rttp
|
|
29
|
+
$ python -m rttp.selftest
|
|
30
|
+
[PASS] all 53 checks passed
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Published as **`rttp`** — the bare name, because PyPI has no scopes and the name
|
|
34
|
+
was free. That package ships a **byte-identical copy** of this same vector set and
|
|
35
|
+
replays it offline, exactly as this one does: two registries, two languages, one
|
|
36
|
+
specification.
|
|
37
|
+
|
|
25
38
|
---
|
|
26
39
|
|
|
27
40
|
## Install
|
|
@@ -51,6 +64,18 @@ r.canonical_uri // 'rttp://brain.epoekie.aicent/verify'
|
|
|
51
64
|
r.route_shard_hex // '459e543b73d86005b72ba77d5756e83c' <- pure computation
|
|
52
65
|
```
|
|
53
66
|
|
|
67
|
+
The same three lines ship as a runnable file:
|
|
68
|
+
|
|
69
|
+
```console
|
|
70
|
+
$ node examples/quickstart.mjs
|
|
71
|
+
459e543b73d86005b72ba77d5756e83c
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Its expected output is the shard printed above — the value the published
|
|
75
|
+
conformance vectors pin for that address, so the example can be **checked**
|
|
76
|
+
rather than believed. It is also the value the Python implementation derives,
|
|
77
|
+
from code that shares nothing with this one.
|
|
78
|
+
|
|
54
79
|
Addressing is **DNS-free** (RFC-002 §10.5): the routing hash is derived from the
|
|
55
80
|
authority by SHA-256, so no registry, resolver or network is involved. A URI
|
|
56
81
|
either yields a shard or throws — there is no fallback, and no `rttps`.
|
|
@@ -70,24 +95,24 @@ package fails closed everywhere.
|
|
|
70
95
|
|
|
71
96
|
---
|
|
72
97
|
|
|
73
|
-
## The
|
|
98
|
+
## The browser's registration prefix — accepted, never advertised
|
|
74
99
|
|
|
75
|
-
A
|
|
76
|
-
|
|
77
|
-
the
|
|
78
|
-
describes the **client**, never the target.
|
|
100
|
+
A browser lets a page register a protocol handler only for a scheme it already
|
|
101
|
+
knows, or for one carrying the browser's own **reserved registration prefix**. So
|
|
102
|
+
the string a web page hands to a handler is not the bare address.
|
|
79
103
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
This package **accepts** that form, because refusing a string a browser is about
|
|
105
|
+
to deliver would break the handler path — but it is a **tolerance, and the
|
|
106
|
+
specification defines no such form**. The canonical `rttp://` address is what
|
|
107
|
+
this package reports, and the only form it ever emits: same authority, same path,
|
|
108
|
+
same **route shard** either way.
|
|
83
109
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
110
|
+
```js
|
|
111
|
+
parse('rttp://logic.zcmk.aicent/pulse').canonical_uri
|
|
112
|
+
// 'rttp://logic.zcmk.aicent/pulse' <- always the canonical form
|
|
87
113
|
```
|
|
88
114
|
|
|
89
|
-
The
|
|
90
|
-
identical.
|
|
115
|
+
The `scheme` field tells you which form arrived; everything else is identical.
|
|
91
116
|
|
|
92
117
|
---
|
|
93
118
|
|
|
@@ -111,7 +136,7 @@ field for field, byte for byte.
|
|
|
111
136
|
|
|
112
137
|
| Field | Meaning |
|
|
113
138
|
|:---|:---|
|
|
114
|
-
| `scheme` |
|
|
139
|
+
| `scheme` | Which form arrived: `rttp` for the canonical address |
|
|
115
140
|
| `intent` | 8 lowercase hex digits (32-bit routing hash) or a readable organ token |
|
|
116
141
|
| `intent_is_hash` / `intent_hash32` | Whether it is a hash-intent, and its value |
|
|
117
142
|
| `pillar`, `root` | The other two authority labels |
|
|
@@ -162,7 +187,7 @@ what it is for.
|
|
|
162
187
|
|
|
163
188
|
| Group | Count | What it proves |
|
|
164
189
|
|:---|:---|---:|
|
|
165
|
-
| `positive_uris` | 6 | Accept, canonicalise and derive — including a hash-intent
|
|
190
|
+
| `positive_uris` | 6 | Accept, canonicalise and derive — including a hash-intent and an omitted action. **Five are conformance claims; one is a `tolerance`** — the browser-registration prefix — and the vector set labels it as such |
|
|
166
191
|
| `negative_uris` | 15 | **Reject** — uppercase, userinfo, query, fragment, port, multi-segment path, wrong authority shape, trailing slash, foreign scheme, surrounding whitespace |
|
|
167
192
|
| `frame_vectors` | 3 | URI → `ROUTE_SHARD` → the 128-byte `PulseHeader128`, bit for bit |
|
|
168
193
|
| `compat_vector_spec_rev_0` | 1 | A frame written before the v1.2.6 extension block still verifies |
|
|
@@ -172,7 +197,7 @@ Two implementations either agree byte for byte or they do not.
|
|
|
172
197
|
|
|
173
198
|
### Why the checker lives in this package, and why that is still honest
|
|
174
199
|
|
|
175
|
-
`src/conformance.mjs` is written from RFC-002 §10
|
|
200
|
+
`src/conformance.mjs` is written from RFC-002 §10 alone and **must never import
|
|
176
201
|
`src/rttp-uri.mjs`**. A conformance suite that reuses the code under test only
|
|
177
202
|
proves the code equals itself. The two files share no code, so the rule is
|
|
178
203
|
checkable by reading them.
|
|
@@ -215,7 +240,7 @@ agreeing with its own bugs.
|
|
|
215
240
|
| Ecosystem | Name | Status |
|
|
216
241
|
|:---|:---|:---|
|
|
217
242
|
| crates.io | `rttp` | held by this project |
|
|
218
|
-
| PyPI | `rttp` | the Python reference implementation |
|
|
243
|
+
| PyPI | `rttp` | **published** — the Python reference implementation; `pip install rttp` |
|
|
219
244
|
| npm | **`@aicent/rttp`** | the bare `rttp` name on npm is held by an unrelated, unmaintained 2017 REST helper, and the `rttp` scope cannot be created at all — a personal account already holds that name. This package is therefore published under the organization this project controls; the package name itself is still just `rttp`. |
|
|
220
245
|
|
|
221
246
|
---
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The whole quickstart, in three lines.
|
|
3
|
+
*
|
|
4
|
+
* node examples/quickstart.mjs
|
|
5
|
+
*
|
|
6
|
+
* Expected output:
|
|
7
|
+
*
|
|
8
|
+
* 459e543b73d86005b72ba77d5756e83c
|
|
9
|
+
*
|
|
10
|
+
* That is not an arbitrary number. It is the ROUTE_SHARD the published
|
|
11
|
+
* conformance vectors pin for this exact address (`positive_uris[1]` and
|
|
12
|
+
* `frame_vectors[1]` in `src/vectors.json`), which is what makes this example
|
|
13
|
+
* checkable rather than merely illustrative - and it is the same value the
|
|
14
|
+
* Python implementation derives, from code that shares nothing with this one.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { parse } from '@aicent/rttp';
|
|
18
|
+
|
|
19
|
+
const r = parse('rttp://brain.epoekie.aicent/verify');
|
|
20
|
+
console.log(r.route_shard_hex); // 459e543b73d86005b72ba77d5756e83c
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aicent/rttp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "RTTP for JavaScript: rttp:// intent validation, canonicalisation and ROUTE_SHARD derivation (RFC-002 §10), plus the published conformance vectors and an independent replay of them, including the 128-byte frame. Zero dependencies, no build step.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rttp",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"protocol"
|
|
16
16
|
],
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
|
-
"author": "RTTP
|
|
18
|
+
"author": "RTTP.COM Organization",
|
|
19
19
|
"homepage": "https://rttp.com/",
|
|
20
20
|
"type": "module",
|
|
21
21
|
"main": "./src/rttp-uri.mjs",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"src",
|
|
39
39
|
"bin",
|
|
40
|
+
"examples",
|
|
40
41
|
"README.md",
|
|
41
42
|
"LICENSE"
|
|
42
43
|
],
|
package/src/conformance.mjs
CHANGED
|
@@ -64,9 +64,11 @@ export function parseUri(uri) {
|
|
|
64
64
|
if (uri !== uri.trim()) throw new Error('leading/trailing whitespace');
|
|
65
65
|
|
|
66
66
|
let rest;
|
|
67
|
+
// The browser-registration form is accepted as a **tolerance only**: the
|
|
68
|
+
// specification defines no such form, but a browser hands it to the handler.
|
|
67
69
|
if (uri.startsWith('web+rttp://')) rest = uri.slice(11);
|
|
68
70
|
else if (uri.startsWith('rttp://')) rest = uri.slice(7);
|
|
69
|
-
else throw new Error('scheme must be rttp:// or
|
|
71
|
+
else throw new Error('scheme must be rttp:// or the browser-registration form');
|
|
70
72
|
|
|
71
73
|
for (const ch of ['@', '?', '#', '[', ']']) {
|
|
72
74
|
if (uri.includes(ch)) throw new Error(`illegal char ${ch}`);
|
package/src/rttp-uri.d.mts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
/** The canonical field set of a parsed `rttp` URI. */
|
|
9
9
|
export interface RttpUri {
|
|
10
|
-
/**
|
|
10
|
+
/** The canonical form, or the browser-registration form when that is how the input arrived. */
|
|
11
11
|
scheme: 'rttp' | 'web+rttp';
|
|
12
12
|
/** 8 lowercase hex digits (32-bit routing hash) or a readable organ token. */
|
|
13
13
|
intent: string;
|
package/src/rttp-uri.mjs
CHANGED
|
@@ -49,12 +49,15 @@ import { createHash } from 'node:crypto';
|
|
|
49
49
|
export const SCHEME = 'rttp';
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* The
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
52
|
+
* The prefix a user agent requires on a scheme it does not already know, before
|
|
53
|
+
* it will let a page register a protocol handler. It is a property of the
|
|
54
|
+
* **client**, never of the target: both spellings name the same address, and the
|
|
55
|
+
* canonical output of this module is always `rttp://`.
|
|
56
|
+
*
|
|
57
|
+
* Accepted for **tolerance only** — the specification defines no such form. A
|
|
58
|
+
* conforming third-party implementation is not required to reproduce this
|
|
59
|
+
* tolerance; refusing the string a browser is about to deliver would, however,
|
|
60
|
+
* break the handler path.
|
|
58
61
|
*/
|
|
59
62
|
export const BROWSER_HANDLER_SCHEME = 'web+rttp';
|
|
60
63
|
|
package/src/vectors.json
CHANGED
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
"root": "aicent",
|
|
21
21
|
"action": "vessel",
|
|
22
22
|
"action_omitted": false,
|
|
23
|
-
"route_shard_hex": "bf77b78ff6ceafc363226aa586562218"
|
|
23
|
+
"route_shard_hex": "bf77b78ff6ceafc363226aa586562218",
|
|
24
|
+
"tolerance": false,
|
|
25
|
+
"note": null
|
|
24
26
|
},
|
|
25
27
|
{
|
|
26
28
|
"uri": "rttp://brain.epoekie.aicent/verify",
|
|
@@ -32,7 +34,9 @@
|
|
|
32
34
|
"root": "aicent",
|
|
33
35
|
"action": "verify",
|
|
34
36
|
"action_omitted": false,
|
|
35
|
-
"route_shard_hex": "459e543b73d86005b72ba77d5756e83c"
|
|
37
|
+
"route_shard_hex": "459e543b73d86005b72ba77d5756e83c",
|
|
38
|
+
"tolerance": false,
|
|
39
|
+
"note": null
|
|
36
40
|
},
|
|
37
41
|
{
|
|
38
42
|
"uri": "rttp://f3b2a1c4.rttp.aicent",
|
|
@@ -44,7 +48,9 @@
|
|
|
44
48
|
"root": "aicent",
|
|
45
49
|
"action": "",
|
|
46
50
|
"action_omitted": true,
|
|
47
|
-
"route_shard_hex": "bf77b78ff6ceafc363226aa586562218"
|
|
51
|
+
"route_shard_hex": "bf77b78ff6ceafc363226aa586562218",
|
|
52
|
+
"tolerance": false,
|
|
53
|
+
"note": null
|
|
48
54
|
},
|
|
49
55
|
{
|
|
50
56
|
"uri": "rttp://00000000.rpki.aicent/attest",
|
|
@@ -56,7 +62,9 @@
|
|
|
56
62
|
"root": "aicent",
|
|
57
63
|
"action": "attest",
|
|
58
64
|
"action_omitted": false,
|
|
59
|
-
"route_shard_hex": "753c105fd0f8bf341811acfaaac85b37"
|
|
65
|
+
"route_shard_hex": "753c105fd0f8bf341811acfaaac85b37",
|
|
66
|
+
"tolerance": false,
|
|
67
|
+
"note": null
|
|
60
68
|
},
|
|
61
69
|
{
|
|
62
70
|
"uri": "web+rttp://logic.zcmk.aicent/pulse",
|
|
@@ -68,7 +76,9 @@
|
|
|
68
76
|
"root": "aicent",
|
|
69
77
|
"action": "pulse",
|
|
70
78
|
"action_omitted": false,
|
|
71
|
-
"route_shard_hex": "cdc32d19641c84aca3292de6276de056"
|
|
79
|
+
"route_shard_hex": "cdc32d19641c84aca3292de6276de056",
|
|
80
|
+
"tolerance": true,
|
|
81
|
+
"note": "tolerance - the specification defines no such form; accepted because it is the scheme name under which a handler is registered (RFC-002 §10.6)"
|
|
72
82
|
},
|
|
73
83
|
{
|
|
74
84
|
"uri": "rttp://a-b-c.d-e.f-g/audit-v2",
|
|
@@ -80,7 +90,9 @@
|
|
|
80
90
|
"root": "f-g",
|
|
81
91
|
"action": "audit-v2",
|
|
82
92
|
"action_omitted": false,
|
|
83
|
-
"route_shard_hex": "905bf68b86aadf883cda2db9640912de"
|
|
93
|
+
"route_shard_hex": "905bf68b86aadf883cda2db9640912de",
|
|
94
|
+
"tolerance": false,
|
|
95
|
+
"note": null
|
|
84
96
|
}
|
|
85
97
|
],
|
|
86
98
|
"negative_uris": [
|
|
@@ -94,11 +106,11 @@
|
|
|
94
106
|
},
|
|
95
107
|
{
|
|
96
108
|
"uri": "RTTP://f3b2a1c4.rttp.aicent/vessel",
|
|
97
|
-
"reason": "uppercase scheme prefix - canonical form is lowercase (RFC-002 §10.
|
|
109
|
+
"reason": "uppercase scheme prefix - canonical form is lowercase (RFC-002 §10.3)"
|
|
98
110
|
},
|
|
99
111
|
{
|
|
100
112
|
"uri": "WEB+RTTP://f3b2a1c4.rttp.aicent/vessel",
|
|
101
|
-
"reason": "uppercase scheme prefix on the handler form (RFC-002 §10.
|
|
113
|
+
"reason": "uppercase scheme prefix on the handler form (RFC-002 §10.3)"
|
|
102
114
|
},
|
|
103
115
|
{
|
|
104
116
|
"uri": "rttp://subject@rttp.aicent/vessel",
|