@aicent/rttp 1.2.8 → 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 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
- [RFC-002 sec. 10](https://rttp.com/RFC-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** (RFC-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 (RFC-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 (RFC-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 RFC-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 RFC-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 (RFC-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` | held by this project |
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** -- submitted to IANA under RFC 7595, ticket **#1459939**,
257
- Provisional, **under review**. It is **not yet registered** -- as of 2026-09-20
258
- the IANA "URI Schemes" registry contains no `rttp` entry. Please describe it
259
- that way.
260
- * **Internet-Draft (IETF)** -- the protocol is under IETF review as the
261
- Individual Submission Internet-Draft `draft-li-rttp-intent-addressing`
262
- (revision -01, posted 2026-09-20, informational):
263
- https://datatracker.ietf.org/doc/draft-li-rttp-intent-addressing/ .
264
- An Internet-Draft is a working document -- it is not an IETF standard and
265
- carries no IETF endorsement.
266
- * **Frame layout** -- RFC-002 sec. 4.1, extended by `SPEC/RTTP-FRAME-EXT-v1.2.6.md`.
267
-
268
- Where this package and the specification disagree, **the specification wins and
269
- this package is wrong.** Please report it.
270
-
271
- ---
272
-
273
- ## License
274
-
275
- Apache-2.0. See `LICENSE`.
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`.