@aicent/rttp 1.2.7 → 1.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/README.md +276 -275
- package/package.json +55 -55
- package/src/conformance.d.mts +108 -108
- package/src/conformance.mjs +358 -358
- package/src/rttp-uri.mjs +325 -325
package/README.md
CHANGED
|
@@ -1,275 +1,276 @@
|
|
|
1
|
-
# @aicent/rttp
|
|
2
|
-
|
|
3
|
-
**RTTP for JavaScript: `rttp://` intent addressing, `ROUTE_SHARD` derivation --
|
|
4
|
-
and the published conformance vectors that prove an implementation is right.**
|
|
5
|
-
|
|
6
|
-
[
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## Verify it in one command -- no trust required
|
|
11
|
-
|
|
12
|
-
```console
|
|
13
|
-
$ npx @aicent/rttp
|
|
14
|
-
[PASS] all 35 checks passed (node, independent implementation)
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
That is the point of this package. A specification is worth exactly what an
|
|
18
|
-
independent implementation can reproduce from it, so instead of asking you to
|
|
19
|
-
believe a table of numbers, the vectors ship inside the package and replay
|
|
20
|
-
locally -- offline, no account, no network. `[PASS]` or it is not. Every vector file shipped here is listed in `vectors.manifest.json` with its byte count and SHA-256, produced by a generator rather than typed by hand - recompute the hash yourself if you prefer.
|
|
21
|
-
|
|
22
|
-
The JavaScript and Python sides **share no code**. They are separate
|
|
23
|
-
implementations of the same published vectors, and they agree byte for byte.
|
|
24
|
-
|
|
25
|
-
### The other half -- Python, on PyPI
|
|
26
|
-
|
|
27
|
-
```console
|
|
28
|
-
$ pip install rttp
|
|
29
|
-
$ python -m rttp.selftest
|
|
30
|
-
[PASS] all 80 checks passed (3 skipped) # default install
|
|
31
|
-
|
|
32
|
-
$ pip install rttp[ed25519]
|
|
33
|
-
$ python -m rttp.selftest
|
|
34
|
-
[PASS] all 83 checks passed # with the optional Ed25519 backend
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Published as **`rttp`** -- the bare name, because PyPI has no scopes and the name
|
|
38
|
-
was free. That package ships a **byte-identical copy** of this same vector set and
|
|
39
|
-
replays it offline, exactly as this one does: two registries, two languages, one
|
|
40
|
-
specification.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Install
|
|
45
|
-
|
|
46
|
-
```console
|
|
47
|
-
npm install @aicent/rttp
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Node 18+. No dependencies, no build step, no transitive tree to audit: the
|
|
51
|
-
published `src/` is exactly what runs.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Quickstart
|
|
56
|
-
|
|
57
|
-
```js
|
|
58
|
-
import { parse } from '@aicent/rttp';
|
|
59
|
-
|
|
60
|
-
const r = parse('rttp://brain.epoekie.aicent/verify');
|
|
61
|
-
|
|
62
|
-
r.authority // 'brain.epoekie.aicent'
|
|
63
|
-
r.intent // 'brain'
|
|
64
|
-
r.pillar // 'epoekie'
|
|
65
|
-
r.root // 'aicent'
|
|
66
|
-
r.action // 'verify'
|
|
67
|
-
r.canonical_uri // 'rttp://brain.epoekie.aicent/verify'
|
|
68
|
-
r.route_shard_hex // '459e543b73d86005b72ba77d5756e83c' <- pure computation
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
The same three lines ship as a runnable file:
|
|
72
|
-
|
|
73
|
-
```console
|
|
74
|
-
$ node examples/quickstart.mjs
|
|
75
|
-
459e543b73d86005b72ba77d5756e83c
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Its expected output is the shard printed above -- the value the published
|
|
79
|
-
conformance vectors pin for that address, so the example can be **checked**
|
|
80
|
-
rather than believed. It is also the value the Python implementation derives,
|
|
81
|
-
from code that shares nothing with this one.
|
|
82
|
-
|
|
83
|
-
Addressing is **DNS-free** (
|
|
84
|
-
authority by SHA-256, so no registry, resolver or network is involved. A URI
|
|
85
|
-
either yields a shard or throws -- there is no fallback, and no `rttps`.
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## The one rule that surprises people
|
|
90
|
-
|
|
91
|
-
**Malformed input is rejected, not normalised.** `rttp://.../VESSEL` and
|
|
92
|
-
`rttp://F3B2A1C4..../vessel` both throw, because the canonical form is lowercase
|
|
93
|
-
US-ASCII (
|
|
94
|
-
a *different string*. Admitting both would give one address two spellings, which
|
|
95
|
-
is the ambiguity sec. 10.3 exists to remove.
|
|
96
|
-
|
|
97
|
-
Nothing here is ever accepted because a check could not be performed: this
|
|
98
|
-
package fails closed everywhere.
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## The browser's registration prefix -- accepted, never advertised
|
|
103
|
-
|
|
104
|
-
A browser lets a page register a protocol handler only for a scheme it already
|
|
105
|
-
knows, or for one carrying the browser's own **reserved registration prefix**. So
|
|
106
|
-
the string a web page hands to a handler is not the bare address.
|
|
107
|
-
|
|
108
|
-
This package **accepts** that form, because refusing a string a browser is about
|
|
109
|
-
to deliver would break the handler path -- but it is a **tolerance, and the
|
|
110
|
-
specification defines no such form**. The canonical `rttp://` address is what
|
|
111
|
-
this package reports, and the only form it ever emits: same authority, same path,
|
|
112
|
-
same **route shard** either way.
|
|
113
|
-
|
|
114
|
-
```js
|
|
115
|
-
parse('rttp://logic.zcmk.aicent/pulse').canonical_uri
|
|
116
|
-
// 'rttp://logic.zcmk.aicent/pulse' <- always the canonical form
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
The `scheme` field tells you which form arrived; everything else is identical.
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## API -- main entry
|
|
124
|
-
|
|
125
|
-
| Export | What it does |
|
|
126
|
-
|:---|:---|
|
|
127
|
-
| `parse(uri)` | Validate, canonicalise, derive. Throws `RttpUriError`. |
|
|
128
|
-
| `parseAndDerive(uri)` | Identical to `parse`; the specification's own wording. |
|
|
129
|
-
| `deriveRouteShard(authority)` | `Uint8Array(16)` = `SHA-256(ASCII(authority))[0:16]`. |
|
|
130
|
-
| `deriveRouteShardHex(authority)` | The same, as 32 lowercase hex characters. |
|
|
131
|
-
| `isValidAction(value)` | sec. 10.2 `action` form test. The verb set is **open**. |
|
|
132
|
-
| `RttpUriError` | The only error type thrown. |
|
|
133
|
-
| `SCHEME`, `BROWSER_HANDLER_SCHEME`, `ROUTE_SHARD_BYTES`, `HASH_INTENT_LEN`, `ACTION_MAX_LEN`, `SPEC_RELEASE` | Constants. |
|
|
134
|
-
|
|
135
|
-
### Returned fields
|
|
136
|
-
|
|
137
|
-
Field names are `snake_case` **on purpose**: the published conformance vectors use
|
|
138
|
-
those names, so this output and the Python package's output compare directly --
|
|
139
|
-
field for field, byte for byte.
|
|
140
|
-
|
|
141
|
-
| Field | Meaning |
|
|
142
|
-
|:---|:---|
|
|
143
|
-
| `scheme` | Which form arrived: `rttp` for the canonical address |
|
|
144
|
-
| `intent` | 8 lowercase hex digits (32-bit routing hash) or a readable organ token |
|
|
145
|
-
| `intent_is_hash` / `intent_hash32` | Whether it is a hash-intent, and its value |
|
|
146
|
-
| `pillar`, `root` | The other two authority labels |
|
|
147
|
-
| `action` | Empty string when omitted (
|
|
148
|
-
| `action_omitted` | The same fact as a boolean |
|
|
149
|
-
| `authority` | `<intent>.<pillar>.<root>`, canonical |
|
|
150
|
-
| `canonical_uri` | Always begins `rttp://` |
|
|
151
|
-
| `route_shard` / `route_shard_hex` | The 16-byte routing hash, two ways |
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
## The conformance vectors -- `@aicent/rttp/conformance`
|
|
156
|
-
|
|
157
|
-
```js
|
|
158
|
-
import { loadVectors, runConformance } from '@aicent/rttp/conformance';
|
|
159
|
-
|
|
160
|
-
const { checked, failures, passed } = runConformance(loadVectors());
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Or from the command line:
|
|
164
|
-
|
|
165
|
-
```console
|
|
166
|
-
npx @aicent/rttp # replay the shipped vectors
|
|
167
|
-
npx @aicent/rttp --vectors ./new.json # replay a different vector set
|
|
168
|
-
npx @aicent/rttp --json # machine-readable output
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
In CI:
|
|
172
|
-
|
|
173
|
-
```yaml
|
|
174
|
-
- run: npx @aicent/rttp
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
| Export | What it does |
|
|
178
|
-
|:---|:---|
|
|
179
|
-
| `runConformance(vectors)` | Replay a vector set -> `{ checked, failures, passed }` |
|
|
180
|
-
| `loadVectors(path?)` | Parse a vector set; defaults to the shipped one |
|
|
181
|
-
| `DEFAULT_VECTORS_PATH` | Absolute path of the shipped `vectors.json` |
|
|
182
|
-
| `parseUri(uri)` | The independent
|
|
183
|
-
| `buildFrame(fields)` | The independent 128-byte frame builder -> `Buffer` |
|
|
184
|
-
| `ACTION_MAX_LEN` | `16` -- the ACTION field's capacity |
|
|
185
|
-
|
|
186
|
-
`vectors.json` is plain JSON and ships inside the package, also reachable as the
|
|
187
|
-
subpath export `@aicent/rttp/vectors.json`. Any language can read it; that is
|
|
188
|
-
what it is for.
|
|
189
|
-
|
|
190
|
-
### What the 35 checks are
|
|
191
|
-
|
|
192
|
-
| Group | Count | What it proves |
|
|
193
|
-
|:---|:---|---:|
|
|
194
|
-
| `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 |
|
|
195
|
-
| `negative_uris` | 15 | **Reject** -- uppercase, userinfo, query, fragment, port, multi-segment path, wrong authority shape, trailing slash, foreign scheme, surrounding whitespace |
|
|
196
|
-
| `frame_vectors` | 3 | URI -> `ROUTE_SHARD` -> the 128-byte `PulseHeader128`, bit for bit |
|
|
197
|
-
| `compat_vector_spec_rev_0` | 1 | A frame written before the v1.2.6 extension block still verifies |
|
|
198
|
-
| `negative_frames` | 9 | **Reject** -- fail-closed frames: ACTION length/padding/embedded NUL/grammar, unknown SPEC_REV, SPEC_REV=0 with a non-zero extension, non-zero RESERVED, unknown FLAGS bit |
|
|
199
|
-
| `positive_frames` | 1 | Accept -- SPEC_REV=1 with ACTION_LEN=0 is valid and equivalent to an omitted action |
|
|
200
|
-
|
|
201
|
-
Each vector is deterministic: fixed sequence numbers, fixed timestamp, fixed AID.
|
|
202
|
-
Two implementations either agree byte for byte or they do not.
|
|
203
|
-
|
|
204
|
-
### Why the checker lives in this package, and why that is still honest
|
|
205
|
-
|
|
206
|
-
`src/conformance.mjs` is written from
|
|
207
|
-
`src/rttp-uri.mjs`**. A conformance suite that reuses the code under test only
|
|
208
|
-
proves the code equals itself. The two files share no code, so the rule is
|
|
209
|
-
checkable by reading them.
|
|
210
|
-
|
|
211
|
-
They ship together for the same reason the Python wheel ships its self-test: so
|
|
212
|
-
that a stranger can verify an installed package **offline, with no repository
|
|
213
|
-
checkout**. A verification step that requires cloning an unrelated repository is
|
|
214
|
-
not a verification step.
|
|
215
|
-
|
|
216
|
-
`buildFrame` **refuses** an action longer than the field can hold rather than
|
|
217
|
-
truncating it: a silently shortened verb would be a routing decision made by the
|
|
218
|
-
codec, and the codec does not get to make that decision. (sec. 10.2 sets no length
|
|
219
|
-
limit on `action` -- a *parser* must accept a long one; a frame builder cannot.)
|
|
220
|
-
|
|
221
|
-
The vectors are **generated, not written**. `generated_by` in the JSON names the
|
|
222
|
-
generator, and the file says so itself: change the cases in the generator, not the
|
|
223
|
-
JSON. Hand-editing a conformance vector is how a specification quietly starts
|
|
224
|
-
agreeing with its own bugs.
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
## Scope -- what this package is not
|
|
229
|
-
|
|
230
|
-
| | |
|
|
231
|
-
|:---|:---|
|
|
232
|
-
| OK **Validation** | Real, and specified (
|
|
233
|
-
| OK **Canonicalisation** | Real, and specified. |
|
|
234
|
-
| OK **ROUTE_SHARD derivation** | Real, and specified. |
|
|
235
|
-
| OK **Conformance vectors + independent replay** | Published and generated. |
|
|
236
|
-
| NO **Framing, as a library API** | `buildFrame` is exported from `./conformance`, where it exists to replay frame vectors -- it is not offered as a general codec. |
|
|
237
|
-
| NO **Transport** | Not here. There is no `send()`. |
|
|
238
|
-
| NO **Routing service** | Not here. The shard is computed locally; delivering a frame to that hash is an operator's job. |
|
|
239
|
-
| NO **Sealing (sec. 11)** | Separate artifacts. |
|
|
240
|
-
| NO **Browsers** | Node only -- it uses `node:crypto`. The derivation is trivial to reproduce elsewhere, and the vectors let you prove you did. |
|
|
241
|
-
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
## Naming
|
|
245
|
-
|
|
246
|
-
| Ecosystem | Name | Status |
|
|
247
|
-
|:---|:---|:---|
|
|
248
|
-
| crates.io | `rttp` |
|
|
249
|
-
| PyPI | `rttp` | **published** -- the Python reference implementation; `pip install rttp` |
|
|
250
|
-
| 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`. |
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
## Specification status
|
|
255
|
-
|
|
256
|
-
* **`rttp` URI scheme** --
|
|
257
|
-
Provisional, **
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this package
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
1
|
+
# @aicent/rttp
|
|
2
|
+
|
|
3
|
+
**RTTP for JavaScript: `rttp://` intent addressing, `ROUTE_SHARD` derivation --
|
|
4
|
+
and the published conformance vectors that prove an implementation is right.**
|
|
5
|
+
|
|
6
|
+
[AICENT-002 sec. 10](https://rttp.com/AICENT-002/) - spec v1.2.6 - zero dependencies - no build step
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Verify it in one command -- no trust required
|
|
11
|
+
|
|
12
|
+
```console
|
|
13
|
+
$ npx @aicent/rttp
|
|
14
|
+
[PASS] all 35 checks passed (node, independent implementation)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That is the point of this package. A specification is worth exactly what an
|
|
18
|
+
independent implementation can reproduce from it, so instead of asking you to
|
|
19
|
+
believe a table of numbers, the vectors ship inside the package and replay
|
|
20
|
+
locally -- offline, no account, no network. `[PASS]` or it is not. Every vector file shipped here is listed in `vectors.manifest.json` with its byte count and SHA-256, produced by a generator rather than typed by hand - recompute the hash yourself if you prefer.
|
|
21
|
+
|
|
22
|
+
The JavaScript and Python sides **share no code**. They are separate
|
|
23
|
+
implementations of the same published vectors, and they agree byte for byte.
|
|
24
|
+
|
|
25
|
+
### The other half -- Python, on PyPI
|
|
26
|
+
|
|
27
|
+
```console
|
|
28
|
+
$ pip install rttp
|
|
29
|
+
$ python -m rttp.selftest
|
|
30
|
+
[PASS] all 80 checks passed (3 skipped) # default install
|
|
31
|
+
|
|
32
|
+
$ pip install rttp[ed25519]
|
|
33
|
+
$ python -m rttp.selftest
|
|
34
|
+
[PASS] all 83 checks passed # with the optional Ed25519 backend
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Published as **`rttp`** -- the bare name, because PyPI has no scopes and the name
|
|
38
|
+
was free. That package ships a **byte-identical copy** of this same vector set and
|
|
39
|
+
replays it offline, exactly as this one does: two registries, two languages, one
|
|
40
|
+
specification.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```console
|
|
47
|
+
npm install @aicent/rttp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Node 18+. No dependencies, no build step, no transitive tree to audit: the
|
|
51
|
+
published `src/` is exactly what runs.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quickstart
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import { parse } from '@aicent/rttp';
|
|
59
|
+
|
|
60
|
+
const r = parse('rttp://brain.epoekie.aicent/verify');
|
|
61
|
+
|
|
62
|
+
r.authority // 'brain.epoekie.aicent'
|
|
63
|
+
r.intent // 'brain'
|
|
64
|
+
r.pillar // 'epoekie'
|
|
65
|
+
r.root // 'aicent'
|
|
66
|
+
r.action // 'verify'
|
|
67
|
+
r.canonical_uri // 'rttp://brain.epoekie.aicent/verify'
|
|
68
|
+
r.route_shard_hex // '459e543b73d86005b72ba77d5756e83c' <- pure computation
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The same three lines ship as a runnable file:
|
|
72
|
+
|
|
73
|
+
```console
|
|
74
|
+
$ node examples/quickstart.mjs
|
|
75
|
+
459e543b73d86005b72ba77d5756e83c
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Its expected output is the shard printed above -- the value the published
|
|
79
|
+
conformance vectors pin for that address, so the example can be **checked**
|
|
80
|
+
rather than believed. It is also the value the Python implementation derives,
|
|
81
|
+
from code that shares nothing with this one.
|
|
82
|
+
|
|
83
|
+
Addressing is **DNS-free** (AICENT-002 sec. 10.5): the routing hash is derived from the
|
|
84
|
+
authority by SHA-256, so no registry, resolver or network is involved. A URI
|
|
85
|
+
either yields a shard or throws -- there is no fallback, and no `rttps`.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## The one rule that surprises people
|
|
90
|
+
|
|
91
|
+
**Malformed input is rejected, not normalised.** `rttp://.../VESSEL` and
|
|
92
|
+
`rttp://F3B2A1C4..../vessel` both throw, because the canonical form is lowercase
|
|
93
|
+
US-ASCII (AICENT-002 sec. 10.3) and a case variant is not a spelling difference -- it is
|
|
94
|
+
a *different string*. Admitting both would give one address two spellings, which
|
|
95
|
+
is the ambiguity sec. 10.3 exists to remove.
|
|
96
|
+
|
|
97
|
+
Nothing here is ever accepted because a check could not be performed: this
|
|
98
|
+
package fails closed everywhere.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## The browser's registration prefix -- accepted, never advertised
|
|
103
|
+
|
|
104
|
+
A browser lets a page register a protocol handler only for a scheme it already
|
|
105
|
+
knows, or for one carrying the browser's own **reserved registration prefix**. So
|
|
106
|
+
the string a web page hands to a handler is not the bare address.
|
|
107
|
+
|
|
108
|
+
This package **accepts** that form, because refusing a string a browser is about
|
|
109
|
+
to deliver would break the handler path -- but it is a **tolerance, and the
|
|
110
|
+
specification defines no such form**. The canonical `rttp://` address is what
|
|
111
|
+
this package reports, and the only form it ever emits: same authority, same path,
|
|
112
|
+
same **route shard** either way.
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
parse('rttp://logic.zcmk.aicent/pulse').canonical_uri
|
|
116
|
+
// 'rttp://logic.zcmk.aicent/pulse' <- always the canonical form
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The `scheme` field tells you which form arrived; everything else is identical.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## API -- main entry
|
|
124
|
+
|
|
125
|
+
| Export | What it does |
|
|
126
|
+
|:---|:---|
|
|
127
|
+
| `parse(uri)` | Validate, canonicalise, derive. Throws `RttpUriError`. |
|
|
128
|
+
| `parseAndDerive(uri)` | Identical to `parse`; the specification's own wording. |
|
|
129
|
+
| `deriveRouteShard(authority)` | `Uint8Array(16)` = `SHA-256(ASCII(authority))[0:16]`. |
|
|
130
|
+
| `deriveRouteShardHex(authority)` | The same, as 32 lowercase hex characters. |
|
|
131
|
+
| `isValidAction(value)` | sec. 10.2 `action` form test. The verb set is **open**. |
|
|
132
|
+
| `RttpUriError` | The only error type thrown. |
|
|
133
|
+
| `SCHEME`, `BROWSER_HANDLER_SCHEME`, `ROUTE_SHARD_BYTES`, `HASH_INTENT_LEN`, `ACTION_MAX_LEN`, `SPEC_RELEASE` | Constants. |
|
|
134
|
+
|
|
135
|
+
### Returned fields
|
|
136
|
+
|
|
137
|
+
Field names are `snake_case` **on purpose**: the published conformance vectors use
|
|
138
|
+
those names, so this output and the Python package's output compare directly --
|
|
139
|
+
field for field, byte for byte.
|
|
140
|
+
|
|
141
|
+
| Field | Meaning |
|
|
142
|
+
|:---|:---|
|
|
143
|
+
| `scheme` | Which form arrived: `rttp` for the canonical address |
|
|
144
|
+
| `intent` | 8 lowercase hex digits (32-bit routing hash) or a readable organ token |
|
|
145
|
+
| `intent_is_hash` / `intent_hash32` | Whether it is a hash-intent, and its value |
|
|
146
|
+
| `pillar`, `root` | The other two authority labels |
|
|
147
|
+
| `action` | Empty string when omitted (AICENT-002 sec. 10.4 default operation) |
|
|
148
|
+
| `action_omitted` | The same fact as a boolean |
|
|
149
|
+
| `authority` | `<intent>.<pillar>.<root>`, canonical |
|
|
150
|
+
| `canonical_uri` | Always begins `rttp://` |
|
|
151
|
+
| `route_shard` / `route_shard_hex` | The 16-byte routing hash, two ways |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## The conformance vectors -- `@aicent/rttp/conformance`
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
import { loadVectors, runConformance } from '@aicent/rttp/conformance';
|
|
159
|
+
|
|
160
|
+
const { checked, failures, passed } = runConformance(loadVectors());
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Or from the command line:
|
|
164
|
+
|
|
165
|
+
```console
|
|
166
|
+
npx @aicent/rttp # replay the shipped vectors
|
|
167
|
+
npx @aicent/rttp --vectors ./new.json # replay a different vector set
|
|
168
|
+
npx @aicent/rttp --json # machine-readable output
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
In CI:
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
- run: npx @aicent/rttp
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
| Export | What it does |
|
|
178
|
+
|:---|:---|
|
|
179
|
+
| `runConformance(vectors)` | Replay a vector set -> `{ checked, failures, passed }` |
|
|
180
|
+
| `loadVectors(path?)` | Parse a vector set; defaults to the shipped one |
|
|
181
|
+
| `DEFAULT_VECTORS_PATH` | Absolute path of the shipped `vectors.json` |
|
|
182
|
+
| `parseUri(uri)` | The independent AICENT-002 sec. 10.2 implementation -> `{ authority, action, shardHex }` |
|
|
183
|
+
| `buildFrame(fields)` | The independent 128-byte frame builder -> `Buffer` |
|
|
184
|
+
| `ACTION_MAX_LEN` | `16` -- the ACTION field's capacity |
|
|
185
|
+
|
|
186
|
+
`vectors.json` is plain JSON and ships inside the package, also reachable as the
|
|
187
|
+
subpath export `@aicent/rttp/vectors.json`. Any language can read it; that is
|
|
188
|
+
what it is for.
|
|
189
|
+
|
|
190
|
+
### What the 35 checks are
|
|
191
|
+
|
|
192
|
+
| Group | Count | What it proves |
|
|
193
|
+
|:---|:---|---:|
|
|
194
|
+
| `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 |
|
|
195
|
+
| `negative_uris` | 15 | **Reject** -- uppercase, userinfo, query, fragment, port, multi-segment path, wrong authority shape, trailing slash, foreign scheme, surrounding whitespace |
|
|
196
|
+
| `frame_vectors` | 3 | URI -> `ROUTE_SHARD` -> the 128-byte `PulseHeader128`, bit for bit |
|
|
197
|
+
| `compat_vector_spec_rev_0` | 1 | A frame written before the v1.2.6 extension block still verifies |
|
|
198
|
+
| `negative_frames` | 9 | **Reject** -- fail-closed frames: ACTION length/padding/embedded NUL/grammar, unknown SPEC_REV, SPEC_REV=0 with a non-zero extension, non-zero RESERVED, unknown FLAGS bit |
|
|
199
|
+
| `positive_frames` | 1 | Accept -- SPEC_REV=1 with ACTION_LEN=0 is valid and equivalent to an omitted action |
|
|
200
|
+
|
|
201
|
+
Each vector is deterministic: fixed sequence numbers, fixed timestamp, fixed AID.
|
|
202
|
+
Two implementations either agree byte for byte or they do not.
|
|
203
|
+
|
|
204
|
+
### Why the checker lives in this package, and why that is still honest
|
|
205
|
+
|
|
206
|
+
`src/conformance.mjs` is written from AICENT-002 sec. 10 alone and **must never import
|
|
207
|
+
`src/rttp-uri.mjs`**. A conformance suite that reuses the code under test only
|
|
208
|
+
proves the code equals itself. The two files share no code, so the rule is
|
|
209
|
+
checkable by reading them.
|
|
210
|
+
|
|
211
|
+
They ship together for the same reason the Python wheel ships its self-test: so
|
|
212
|
+
that a stranger can verify an installed package **offline, with no repository
|
|
213
|
+
checkout**. A verification step that requires cloning an unrelated repository is
|
|
214
|
+
not a verification step.
|
|
215
|
+
|
|
216
|
+
`buildFrame` **refuses** an action longer than the field can hold rather than
|
|
217
|
+
truncating it: a silently shortened verb would be a routing decision made by the
|
|
218
|
+
codec, and the codec does not get to make that decision. (sec. 10.2 sets no length
|
|
219
|
+
limit on `action` -- a *parser* must accept a long one; a frame builder cannot.)
|
|
220
|
+
|
|
221
|
+
The vectors are **generated, not written**. `generated_by` in the JSON names the
|
|
222
|
+
generator, and the file says so itself: change the cases in the generator, not the
|
|
223
|
+
JSON. Hand-editing a conformance vector is how a specification quietly starts
|
|
224
|
+
agreeing with its own bugs.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Scope -- what this package is not
|
|
229
|
+
|
|
230
|
+
| | |
|
|
231
|
+
|:---|:---|
|
|
232
|
+
| OK **Validation** | Real, and specified (AICENT-002 sec. 10.2 / sec. 10.3). |
|
|
233
|
+
| OK **Canonicalisation** | Real, and specified. |
|
|
234
|
+
| OK **ROUTE_SHARD derivation** | Real, and specified. |
|
|
235
|
+
| OK **Conformance vectors + independent replay** | Published and generated. |
|
|
236
|
+
| NO **Framing, as a library API** | `buildFrame` is exported from `./conformance`, where it exists to replay frame vectors -- it is not offered as a general codec. |
|
|
237
|
+
| NO **Transport** | Not here. There is no `send()`. |
|
|
238
|
+
| NO **Routing service** | Not here. The shard is computed locally; delivering a frame to that hash is an operator's job. |
|
|
239
|
+
| NO **Sealing (sec. 11)** | Separate artifacts. |
|
|
240
|
+
| NO **Browsers** | Node only -- it uses `node:crypto`. The derivation is trivial to reproduce elsewhere, and the vectors let you prove you did. |
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Naming
|
|
245
|
+
|
|
246
|
+
| Ecosystem | Name | Status |
|
|
247
|
+
|:---|:---|:---|
|
|
248
|
+
| crates.io | `rttp` | **this project** -- `1.3.1`, the first formal (non-pre-release) publication; the earlier `1.2.8-alpha` remains published and is not yanked |
|
|
249
|
+
| PyPI | `rttp` | **published** -- the Python reference implementation; `pip install rttp` |
|
|
250
|
+
| 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`. |
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Specification status
|
|
255
|
+
|
|
256
|
+
* **`rttp` URI scheme** -- **registered**: IANA "URI Schemes" registry,
|
|
257
|
+
Provisional, **CRI 27** (2026-09-22), submitted under RFC 7595.
|
|
258
|
+
* **Internet-Draft (IETF)** -- under IETF review as the combined Individual
|
|
259
|
+
Submission Internet-Draft `draft-li-rttp-iqa-addressing` (revision -00,
|
|
260
|
+
16 pp., 2026-09-24, informational -- both schemes, one document):
|
|
261
|
+
https://datatracker.ietf.org/doc/draft-li-rttp-iqa-addressing/ .
|
|
262
|
+
An Internet-Draft is a working document -- it is not an IETF standard and
|
|
263
|
+
carries no IETF endorsement.
|
|
264
|
+
* **Frame layout** -- AICENT-002 sec. 4.1, extended by `SPEC/RTTP-FRAME-EXT-v1.2.6.md`.
|
|
265
|
+
* **AE-128** -- the answer-side counterpart of the pulse frame lives in the
|
|
266
|
+
sibling packages `iqa-org` 1.3.1 / `@aicent/iqa` 1.3.1 (`iqa.envelope`);
|
|
267
|
+
the two carriers do not parse each other and MUST NOT be conflated.
|
|
268
|
+
|
|
269
|
+
Where this package and the specification disagree, **the specification wins and
|
|
270
|
+
this package is wrong.** Please report it.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
Apache-2.0. See `LICENSE`.
|