@aicent/iqa 1.2.6 → 1.2.8

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,14 +1,14 @@
1
1
  # @aicent/iqa
2
2
 
3
3
  **IQA for JavaScript: `iqa://` subject-attestation addressing, `IQA_ROUTE`
4
- derivation, action safety classes — and the published conformance vectors that
4
+ derivation, action safety classes -- and the published conformance vectors that
5
5
  prove an implementation is right.**
6
6
 
7
- [RFC-009 §10](https://iqa.org/RFC-009/) · §11 · v1.2.6 · zero dependencies · no build step
7
+ [RFC-009 sec. 10](https://iqa.org/RFC-009/) - sec. 11 - stack v1.2.6 - zero dependencies - no build step
8
8
 
9
9
  ---
10
10
 
11
- ## Verify it in one command — no trust required
11
+ ## Verify it in one command -- no trust required
12
12
 
13
13
  ```console
14
14
  $ npx @aicent/iqa
@@ -18,22 +18,27 @@ $ npx @aicent/iqa
18
18
  That is the point of this package. A specification is worth exactly what an
19
19
  independent implementation can reproduce from it, so instead of asking you to
20
20
  believe a table of numbers, the vectors ship inside the package and replay
21
- locally — offline, no account, no network. `[PASS]` or it is not.
21
+ 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.
22
22
 
23
- The vectors cover the `iqa://` grammar (RFC-009 §10.2), the **closed sets**
24
- (`organ` and `action` — the deliberate difference from `rttp`, whose verb set
25
- is open), the §11.3 action safety classes, the `IQA_ROUTE` derivation, **and**
26
- the deterministic Ed25519 attestation envelope — replayed by both
23
+ The vectors cover the `iqa://` grammar (RFC-009 sec. 10.2), the **closed sets**
24
+ (`organ` and `action` -- the deliberate difference from `rttp`, whose verb set
25
+ is open), the sec. 11.3 action safety classes, the `IQA_ROUTE` derivation, **and**
26
+ the deterministic Ed25519 attestation envelope -- replayed by both
27
27
  implementations.
28
28
 
29
- ### The other half — Python, on PyPI
29
+ ### The other half -- Python, on PyPI
30
30
 
31
31
  ```console
32
32
  $ pip install iqa-org
33
33
  $ python -m iqa.selftest
34
+ [PASS] all 75 checks passed (3 skipped) # default install
35
+
36
+ $ pip install iqa-org[ed25519]
37
+ $ python -m iqa.selftest
38
+ [PASS] all 78 checks passed # with the optional Ed25519 backend
34
39
  ```
35
40
 
36
- Published as **`@aicent/iqa`** — the bare `iqa` name is unregistered on npm but
41
+ Published as **`@aicent/iqa`** -- the bare `iqa` name is unregistered on npm but
37
42
  blocked by its typosquatting protection, so the JavaScript side lives under
38
43
  this project's organization, as `@aicent/rttp` already does. PyPI applies the
39
44
  same class of guard: the Python package is therefore **`iqa-org`** (same name
@@ -62,10 +67,10 @@ import { parse } from '@aicent/iqa';
62
67
 
63
68
  const r = parse('iqa://3f9a1b2c.gateway.iqa');
64
69
 
65
- r.subject // '3f9a1b2c' — 8-hex routing short form of the AID
66
- r.organ // 'gateway' — closed set: forge | tss | gateway
67
- r.action // null — omitted = standing read (§11.1)
68
- r.action_safe // true — §11.3: omitted and verify are SAFE
70
+ r.subject // '3f9a1b2c' -- 8-hex routing short form of the AID
71
+ r.organ // 'gateway' -- closed set: forge | tss | gateway
72
+ r.action // null -- omitted = standing read (sec. 11.1)
73
+ r.action_safe // true -- sec. 11.3: omitted and verify are SAFE
69
74
  r.canonical_uri // 'iqa://3f9a1b2c.gateway.iqa'
70
75
  r.route_hex // '5c378581' <- pure computation, pinned by the vectors
71
76
  ```
@@ -80,28 +85,28 @@ $ node examples/quickstart.mjs
80
85
  Its expected output is the route fingerprint the published conformance vectors
81
86
  pin for that address, so the example can be **checked** rather than believed.
82
87
 
83
- Addressing is **DNS-free** (RFC-009 §12 #9): the route fingerprint is derived
88
+ Addressing is **DNS-free** (RFC-009 sec. 12 #9): the route fingerprint is derived
84
89
  from the authority by SHA-256, so no registry, resolver or network is
85
- involved. A URI either yields its fields or throws — there is no fallback, no
90
+ involved. A URI either yields its fields or throws -- there is no fallback, no
86
91
  `iqas`, and no rewriting to `https`.
87
92
 
88
93
  ---
89
94
 
90
- ## The two closed sets — the deliberate difference from `rttp`
95
+ ## The two closed sets -- the deliberate difference from `rttp`
91
96
 
92
- `rttp` accepts any well-formed action verb. **`iqa` does not.** RFC-009 §10.1
97
+ `rttp` accepts any well-formed action verb. **`iqa` does not.** RFC-009 sec. 10.1
93
98
  marks *both* segments "Closed set", and this package enforces exactly that:
94
99
 
95
100
  ```js
96
- parse('iqa://3f9a1b2c.gateway.iqa/pulse') // throws — 'pulse' is an rttp verb
97
- parse('iqa://3f9a1b2c.forgery.iqa') // throws — organ is forge|tss|gateway
101
+ parse('iqa://3f9a1b2c.gateway.iqa/pulse') // throws -- 'pulse' is an rttp verb
102
+ parse('iqa://3f9a1b2c.forgery.iqa') // throws -- organ is forge|tss|gateway
98
103
  ```
99
104
 
100
105
  ### The one rule that surprises people
101
106
 
102
- **Malformed input is rejected, not normalised.** `iqa://…/AUDIT`,
103
- `iqa://3F9A1B2C.…` and `IQA://…` all throw: the canonical form is lowercase
104
- US-ASCII (RFC-009 §10.3), a case variant is not a spelling difference, and
107
+ **Malformed input is rejected, not normalised.** `iqa://.../AUDIT`,
108
+ `iqa://3F9A1B2C....` and `IQA://...` all throw: the canonical form is lowercase
109
+ US-ASCII (RFC-009 sec. 10.3), a case variant is not a spelling difference, and
105
110
  nothing is ever accepted because a check could not be performed.
106
111
 
107
112
  ### One honest quirk of the grammar
@@ -113,36 +118,36 @@ implementation agrees on it.
113
118
 
114
119
  ---
115
120
 
116
- ## Action safety classes (RFC-009 §11.3)
121
+ ## Action safety classes (RFC-009 sec. 11.3)
117
122
 
118
123
  | `action` | Class | `action_safe` |
119
124
  |:---|:---|:---|
120
- | *(omitted)* | SAFE — standing read | `true` |
121
- | `verify` | SAFE — read-only comparison | `true` |
122
- | `audit` | NOT SAFE — can trigger Authority Ischemia | `false` |
123
- | `attest` | NOT SAFE — state transition | `false` |
124
- | `revoke` | NOT SAFE — state transition | `false` |
125
+ | *(omitted)* | SAFE -- standing read | `true` |
126
+ | `verify` | SAFE -- read-only comparison | `true` |
127
+ | `audit` | NOT SAFE -- can trigger Authority Ischemia | `false` |
128
+ | `attest` | NOT SAFE -- state transition | `false` |
129
+ | `revoke` | NOT SAFE -- state transition | `false` |
125
130
 
126
- Per §11.2, dereferencing an `iqa` URI MUST NOT transition any subject's
131
+ Per sec. 11.2, dereferencing an `iqa` URI MUST NOT transition any subject's
127
132
  standing. Tooling built on this package MUST gate the three NOT-SAFE verbs
128
- behind an explicit user action — parsing one is not requesting one.
133
+ behind an explicit user action -- parsing one is not requesting one.
129
134
 
130
135
  ---
131
136
 
132
- ## API — main entry
137
+ ## API -- main entry
133
138
 
134
139
  | Export | What it does |
135
140
  |:---|:---|
136
141
  | `parse(uri)` | Validate, canonicalise, classify, derive. Throws `IqaUriError`. |
137
142
  | `deriveRoute(authority)` | `Uint8Array(4)` = `SHA-256(ASCII(authority))[0:4]`. |
138
143
  | `deriveRouteHex(authority)` | The same, as 8 lowercase hex characters. |
139
- | `isValidAction(value)` | §10.1 closed-set test. |
144
+ | `isValidAction(value)` | sec. 10.1 closed-set test. |
140
145
  | `IqaUriError` | The only error type thrown. |
141
146
  | `SCHEME`, `ORGANS`, `ACTIONS`, `STANDINGS`, `HASH_LENGTHS` | Constants. |
142
147
 
143
- ### The attestation envelope — `@aicent/iqa/conformance`
148
+ ### The attestation envelope -- `@aicent/iqa/conformance`
144
149
 
145
- The deterministic Ed25519 envelope (SPEC/IQA-URI-ATTEST-v1.2.6 §5: domain
150
+ The deterministic Ed25519 envelope (SPEC/IQA-URI-ATTEST-v1.2.6 sec. 5: domain
146
151
  prefix `iqa-attest-v1`, `AID = SHA-256(pub)`, `ts`/`nonce` inside the
147
152
  signature, 120-second freshness) is part of the shared vector set and is
148
153
  replayed by this package's conformance run. `buildSigningInput` is exported
@@ -174,9 +179,9 @@ In CI:
174
179
 
175
180
  | Group | Count | What it proves |
176
181
  |:---|---:|:---|
177
- | `positive_uris` | 8 | Accept, classify and derive — §10.3's own examples, the 256-bit form, the all-zero boundary, the name-subject fall-through |
178
- | `negative_uris` | 18 | Reject — uppercase anywhere, `iqas://`, userinfo, port, query, fragment, wrong authority shape, closed-set violations on both segments |
179
- | `action_safety` | 5 | §11.3 classes as data |
182
+ | `positive_uris` | 8 | Accept, classify and derive -- sec. 10.3's own examples, the 256-bit form, the all-zero boundary, the name-subject fall-through |
183
+ | `negative_uris` | 18 | Reject -- uppercase anywhere, `iqas://`, userinfo, port, query, fragment, wrong authority shape, closed-set violations on both segments |
184
+ | `action_safety` | 5 | sec. 11.3 classes as data |
180
185
  | `attest_envelope_vector` | 1 | Deterministic Ed25519 envelope, verified through `node:crypto` |
181
186
 
182
187
  `vectors.json` is plain JSON and ships inside the package, also reachable as
@@ -185,10 +190,10 @@ is what it is for.
185
190
 
186
191
  ### Why the checker lives in this package, and why that is still honest
187
192
 
188
- `src/conformance.mjs` is written from RFC-009 §10 alone and **must never
193
+ `src/conformance.mjs` is written from RFC-009 sec. 10 alone and **must never
189
194
  import `src/iqa-uri.mjs`**. A conformance suite that reuses the code under
190
- test only proves the code equals itself. The two files share no code — one is
191
- a streaming charset scanner, the other a single full-match grammar check — so
195
+ test only proves the code equals itself. The two files share no code -- one is
196
+ a streaming charset scanner, the other a single full-match grammar check -- so
192
197
  the rule is checkable by reading them.
193
198
 
194
199
  The vectors are **generated, not written**. `generated_by` in the JSON names
@@ -198,20 +203,20 @@ starts agreeing with its own bugs.
198
203
 
199
204
  ---
200
205
 
201
- ## Scope — what this package is not
206
+ ## Scope -- what this package is not
202
207
 
203
208
  | | |
204
209
  |:---|:---|
205
- | ✅ **Validation / canonicalisation** | Real, and specified (RFC-009 §10.2 / §10.3). |
206
- | ✅ **Closed-set enforcement** | Real, and specified (§10.1). |
207
- | ✅ **IQA_ROUTE derivation** | Real (SPEC §3, decision D1). |
208
- | ✅ **Action safety classes** | Real, and specified (§11.3). |
209
- | ✅ **Conformance vectors + independent replay** | Published and generated. |
210
- | ❌ **Dereferencing / resolver service** | Not here. The codec computes; *answering* a standing read is an Organ's job (RFC-009-C §3). |
211
- | ❌ **Staking, tiers, vitality** | Operator economics and telemetry — RFC-009 narrative, no cryptographic object in this package. |
212
- | ❌ **Post-quantum Lattice Guard** | RFC-009 §12 #11 schedules it for v1.4.0; no implementation exists. |
213
- | ❌ **Revocation transport** | §11.3 defines the verb; executing it is not the codec's job. |
214
- | ❌ **Confidentiality** | Signing is not encryption. |
210
+ | OK **Validation / canonicalisation** | Real, and specified (RFC-009 sec. 10.2 / sec. 10.3). |
211
+ | OK **Closed-set enforcement** | Real, and specified (sec. 10.1). |
212
+ | OK **IQA_ROUTE derivation** | Real (SPEC sec. 3, decision D1). |
213
+ | OK **Action safety classes** | Real, and specified (sec. 11.3). |
214
+ | OK **Conformance vectors + independent replay** | Published and generated. |
215
+ | NO **Dereferencing / resolver service** | Not here. The codec computes; *answering* a standing read is an Organ's job (RFC-009-C sec. 3). |
216
+ | NO **Staking, tiers, vitality** | Operator economics and telemetry -- RFC-009 narrative, no cryptographic object in this package. |
217
+ | NO **Post-quantum Lattice Guard** | RFC-009 sec. 12 #11 schedules it for v1.4.0; no implementation exists. |
218
+ | NO **Revocation transport** | sec. 11.3 defines the verb; executing it is not the codec's job. |
219
+ | NO **Confidentiality** | Signing is not encryption. |
215
220
 
216
221
  ---
217
222
 
@@ -219,21 +224,27 @@ starts agreeing with its own bugs.
219
224
 
220
225
  | Ecosystem | Name | Status |
221
226
  |:---|:---|:---|
222
- | crates.io | `iqa-org` | held by this project (Shipyard Rust crate) |
223
- | PyPI | **`iqa-org`** | **published** — the Python reference implementation; `pip install iqa-org` (bare `iqa` blocked by PyPI's typosquatting protection, same class of guard as npm) |
224
- | npm | **`@aicent/iqa`** | the bare `iqa` name is unregistered but **blocked by npm's typosquatting protection** (too similar to existing short names) — so this package is published under the organization this project controls, as `@aicent/rttp` already is. The package name itself is still just `iqa`. |
227
+ | crates.io | **`iqa-org`** | **published** -- `1.2.8-alpha`. Every published version of this crate is a pre-release, so Cargo will not select it from a plain `iqa-org` requirement: write `cargo add iqa-org@1.2.8-alpha`. |
228
+ | PyPI | **`iqa-org`** | **published** -- the Python reference implementation; `pip install iqa-org` (bare `iqa` blocked by PyPI's typosquatting protection, same class of guard as npm) |
229
+ | npm | **`@aicent/iqa`** | the bare `iqa` name is unregistered but **blocked by npm's typosquatting protection** (too similar to existing short names) -- so this package is published under the organization this project controls, as `@aicent/rttp` already is. The package name itself is still just `iqa`. |
225
230
 
226
231
  ---
227
232
 
228
233
  ## Specification status
229
234
 
230
- * **`iqa` URI scheme** — submitted to IANA under RFC 7595, ticket **#1459963**,
231
- Provisional, **under review**. It is **not yet registered** — as of
232
- 2026-09-18 the IANA "URI Schemes" registry contains no `iqa` entry. Please
235
+ * **`iqa` URI scheme** -- submitted to IANA under RFC 7595, ticket **#1459963**,
236
+ Provisional, **under review**. It is **not yet registered** -- as of
237
+ 2026-09-20 the IANA "URI Schemes" registry contains no `iqa` entry. Please
233
238
  describe it that way.
234
- * **URI grammar** — RFC-009 §10.2 (frozen; this package adds no syntax).
235
- * **Dereference safety** — RFC-009 §11 (closed sets and safety classes).
236
- * **Attestation envelope** — `SPEC/IQA-URI-ATTEST-v1.2.6.md` §5, a **draft**:
239
+ * **Internet-Draft (IETF)** -- under IETF review as the Individual Submission
240
+ Internet-Draft `draft-li-iqa-subject-attestation` (revision -00, posted
241
+ 2026-09-20, informational):
242
+ https://datatracker.ietf.org/doc/draft-li-iqa-subject-attestation/ .
243
+ An Internet-Draft is a working document -- it is not an IETF standard and
244
+ carries no IETF endorsement.
245
+ * **URI grammar** -- RFC-009 sec. 10.2 (frozen; this package adds no syntax).
246
+ * **Dereference safety** -- RFC-009 sec. 11 (closed sets and safety classes).
247
+ * **Attestation envelope** -- `SPEC/IQA-URI-ATTEST-v1.2.6.md` sec. 5, a **draft**:
237
248
  the format is implemented and vector-tested, but it is not yet a numbered
238
249
  RFC-009 section.
239
250
 
package/bin/iqa.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // iqa — replay the published conformance vectors. [PASS] or it is not.
2
+ // iqa -- replay the published conformance vectors. [PASS] or it is not.
3
3
  //
4
4
  // npx @aicent/iqa # replay the shipped vectors
5
5
  // npx @aicent/iqa --vectors ./new.json # replay a different vector set
@@ -22,7 +22,7 @@ for (let i = 0; i < args.length; i++) {
22
22
  } else if (args[i] === '--json') {
23
23
  json = true;
24
24
  } else if (args[i] === '--help' || args[i] === '-h') {
25
- console.log('@aicent/iqa — replay the published conformance vectors (RFC-009 §10/§11)');
25
+ console.log('@aicent/iqa -- replay the published conformance vectors (RFC-009 sec. 10/sec. 11)');
26
26
  console.log(' npx @aicent/iqa replay the shipped vectors');
27
27
  console.log(' npx @aicent/iqa --vectors ./new.json replay a different vector set');
28
28
  console.log(' npx @aicent/iqa --json machine-readable output');
@@ -51,7 +51,7 @@ if (json) {
51
51
  vectors: vectorsPath,
52
52
  }, null, 2));
53
53
  } else {
54
- console.log(`iqa conformance replay — spec ${vectors.spec ?? '(unlabeled)'}`);
54
+ console.log(`iqa conformance replay -- spec ${vectors.spec ?? '(unlabeled)'}`);
55
55
  for (const f of result.failures) console.log(`[FAIL] ${f}`);
56
56
  console.log(`[PASS] all ${result.checked} checks passed`);
57
57
  }
@@ -1,4 +1,4 @@
1
- // The same three lines as the README quickstart — runnable, so the example can
1
+ // The same three lines as the README quickstart -- runnable, so the example can
2
2
  // be CHECKED rather than believed. Its expected output is the route fingerprint
3
3
  // the published conformance vectors pin for that address.
4
4
  //
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@aicent/iqa",
3
- "version": "1.2.6",
4
- "description": "IQA for JavaScript: iqa:// subject-attestation addressing, IQA_ROUTE derivation, action safety classes — and the published conformance vectors that prove an implementation is right.",
3
+ "version": "1.2.8",
4
+ "description": "IQA for JavaScript: iqa:// subject-attestation addressing, IQA_ROUTE derivation, action safety classes -- and the published conformance vectors that prove an implementation is right.",
5
5
  "author": "IQA.ORG Organization",
6
6
  "license": "Apache-2.0",
7
+ "homepage": "https://iqa.org/",
7
8
  "type": "module",
8
9
  "main": "./src/iqa-uri.mjs",
9
10
  "types": "./src/iqa-uri.d.mts",
@@ -42,7 +43,7 @@
42
43
  ],
43
44
  "repository": {
44
45
  "type": "git",
45
- "url": "git+https://github.com/Aicent-Stack/aicent-stack.git"
46
+ "url": "git+https://github.com/Aicent-Stack/iqa-org.git"
46
47
  },
47
48
  "publishConfig": {
48
49
  "access": "public"
@@ -13,9 +13,9 @@ export interface ParsedForConformance {
13
13
  }
14
14
 
15
15
  /**
16
- * The independent RFC-009 §10 implementation used by the checker.
17
- * `buildFrame`-style helpers do not exist here: IQA v1.2.6 has no frame —
18
- * the derivation chain is URI → IQA_ROUTE (4 bytes).
16
+ * The independent RFC-009 sec. 10 implementation used by the checker.
17
+ * `buildFrame`-style helpers do not exist here: IQA v1.2.6 has no frame --
18
+ * the derivation chain is URI -> IQA_ROUTE (4 bytes).
19
19
  */
20
20
  export declare function parseUri(uri: string): ParsedForConformance;
21
21
 
@@ -1,13 +1,13 @@
1
- // IQA conformance replay — an INDEPENDENT implementation of RFC-009 §10.
1
+ // IQA conformance replay -- an INDEPENDENT implementation of RFC-009 sec. 10.
2
2
  //
3
- // This file is written from RFC-009 §10/§11 alone and MUST NEVER import
3
+ // This file is written from RFC-009 sec. 10/sec. 11 alone and MUST NEVER import
4
4
  // ./iqa-uri.mjs. A conformance suite that reuses the code under test only
5
5
  // proves the code equals itself. The two files share no code, so the rule is
6
6
  // checkable by reading them.
7
7
  //
8
8
  // They ship together for the same reason the Python wheel ships its self-test:
9
9
  // so a stranger can verify an installed package offline, with no repository
10
- // checkout. Zero dependencies — node:crypto and node:fs only.
10
+ // checkout. Zero dependencies -- node:crypto and node:fs only.
11
11
 
12
12
  import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
13
13
  import { readFileSync } from 'node:fs';
@@ -17,7 +17,7 @@ import { dirname, join } from 'node:path';
17
17
  const DOMAIN = Buffer.from('iqa-attest-v1\n', 'utf8');
18
18
 
19
19
  // ---------------------------------------------------------------------------
20
- // Independent parse — single full-match grammar check, deliberately different
20
+ // Independent parse -- single full-match grammar check, deliberately different
21
21
  // in structure from the streaming charset scanner in iqa-uri.mjs.
22
22
  // ---------------------------------------------------------------------------
23
23
 
@@ -27,9 +27,9 @@ const DOMAIN = Buffer.from('iqa-attest-v1\n', 'utf8');
27
27
  // so uppercase, '@', ':', '?', '#', whitespace and all foreign bytes fail.
28
28
  const GRAMMAR = /^iqa:\/\/([a-z0-9-]+)\.([a-z0-9-]+)\.([a-z0-9-]+)(?:\/([a-z0-9-]+))?$/;
29
29
 
30
- const ORGANS = ['forge', 'tss', 'gateway']; // §10.1 closed set
31
- const ACTIONS = ['verify', 'audit', 'attest', 'revoke']; // §10.1 closed set
32
- const HASH_LENGTHS = [8, 32, 64]; // §10.2 hash-subject
30
+ const ORGANS = ['forge', 'tss', 'gateway']; // sec. 10.1 closed set
31
+ const ACTIONS = ['verify', 'audit', 'attest', 'revoke']; // sec. 10.1 closed set
32
+ const HASH_LENGTHS = [8, 32, 64]; // sec. 10.2 hash-subject
33
33
 
34
34
  export function parseUri(uri) {
35
35
  const m = GRAMMAR.exec(uri);
@@ -58,7 +58,7 @@ export function parseUri(uri) {
58
58
  authority: `${subject}.${organ}.${root}`,
59
59
  action,
60
60
  action_omitted: action === null,
61
- action_safe: action === null || action === 'verify', // §11.3
61
+ action_safe: action === null || action === 'verify', // sec. 11.3
62
62
  canonical_uri: 'iqa://' + `${subject}.${organ}.${root}` + (action ? '/' + action : ''),
63
63
  route_hex: createHash('sha256').update(`${subject}.${organ}.${root}`, 'ascii')
64
64
  .digest().subarray(0, 4).toString('hex'),
@@ -66,7 +66,7 @@ export function parseUri(uri) {
66
66
  }
67
67
 
68
68
  // ---------------------------------------------------------------------------
69
- // Envelope — canonical JSON + Ed25519 over the domain-separated input.
69
+ // Envelope -- canonical JSON + Ed25519 over the domain-separated input.
70
70
  // ---------------------------------------------------------------------------
71
71
 
72
72
  function canonicalize(value) {
@@ -120,7 +120,7 @@ export function loadVectors(path = DEFAULT_VECTORS_PATH) {
120
120
  }
121
121
 
122
122
  /**
123
- * Replay a vector set → { checked, failures, passed }.
123
+ * Replay a vector set -> { checked, failures, passed }.
124
124
  * The checker accepts any object with the documented groups, so a different
125
125
  * vector set (e.g. a candidate next revision) can be replayed the same way.
126
126
  */
package/src/iqa-uri.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * iqa — RFC-009 §10/§11 codec for JavaScript.
2
+ * iqa -- RFC-009 sec. 10/sec. 11 codec for JavaScript.
3
3
  * Zero dependencies. Field names are snake_case on purpose: they match the
4
4
  * published conformance vectors (and the Python implementation) field for field.
5
5
  */
@@ -12,12 +12,12 @@ export declare const HASH_LENGTHS: readonly [8, 32, 64];
12
12
 
13
13
  export declare class IqaUriError extends Error {}
14
14
 
15
- /** True only for the closed four-verb set (RFC-009 §10.1). */
15
+ /** True only for the closed four-verb set (RFC-009 sec. 10.1). */
16
16
  export declare function isValidAction(value: string): boolean;
17
17
 
18
18
  /**
19
- * IQA_ROUTE = SHA-256(ASCII(canonical_authority))[0:4] (SPEC §3).
20
- * The address fingerprint — not the subject hash (which hints the AID).
19
+ * IQA_ROUTE = SHA-256(ASCII(canonical_authority))[0:4] (SPEC sec. 3).
20
+ * The address fingerprint -- not the subject hash (which hints the AID).
21
21
  */
22
22
  export declare function deriveRoute(authority: string): Uint8Array;
23
23
  export declare function deriveRouteHex(authority: string): string;
@@ -32,7 +32,7 @@ export interface ParsedUri {
32
32
  authority: string;
33
33
  action: string | null;
34
34
  action_omitted: boolean;
35
- /** RFC-009 §11.3 — true for omitted and `verify`, false for audit/attest/revoke. */
35
+ /** RFC-009 sec. 11.3 -- true for omitted and `verify`, false for audit/attest/revoke. */
36
36
  action_safe: boolean;
37
37
  canonical_uri: string;
38
38
  route_hex: string;
package/src/iqa-uri.mjs CHANGED
@@ -1,35 +1,35 @@
1
- // iqa:// URI codec — validate, canonicalise, derive (RFC-009 §10/§11).
1
+ // iqa:// URI codec -- validate, canonicalise, derive (RFC-009 sec. 10/sec. 11).
2
2
  //
3
3
  // Authority boundaries:
4
- // Grammar ......... RFC-009 §10.2 ABNF (sole authority)
5
- // Closed sets ..... §10.1 (organ and action are CLOSED sets)
6
- // Case discipline . §10.3 (lowercase US-ASCII only) + §12 #8 (fail closed)
7
- // Deref safety .... §11.3 (action safety classes)
8
- // Route derivation SPEC/IQA-URI-ATTEST-v1.2.6.md §3 (IQA_ROUTE)
4
+ // Grammar ......... RFC-009 sec. 10.2 ABNF (sole authority)
5
+ // Closed sets ..... sec. 10.1 (organ and action are CLOSED sets)
6
+ // Case discipline . sec. 10.3 (lowercase US-ASCII only) + sec. 12 #8 (fail closed)
7
+ // Deref safety .... sec. 11.3 (action safety classes)
8
+ // Route derivation SPEC/IQA-URI-ATTEST-v1.2.6.md sec. 3 (IQA_ROUTE)
9
9
  //
10
- // Zero dependencies. Node 18+. No build step — the published src/ is what runs.
10
+ // Zero dependencies. Node 18+. No build step -- the published src/ is what runs.
11
11
 
12
12
  import { createHash } from 'node:crypto';
13
13
 
14
14
  export const SCHEME = 'iqa';
15
15
 
16
- /** RFC-009 §10.1 — organ is a CLOSED set (RFC-009-A/-B/-C). */
16
+ /** RFC-009 sec. 10.1 -- organ is a CLOSED set (RFC-009-A/-B/-C). */
17
17
  export const ORGANS = Object.freeze(['forge', 'tss', 'gateway']);
18
18
 
19
19
  /**
20
- * RFC-009 §10.1 — action is a CLOSED set. This is the deliberate difference
20
+ * RFC-009 sec. 10.1 -- action is a CLOSED set. This is the deliberate difference
21
21
  * from `rttp`, whose verb set is open: here anything outside the four verbs
22
22
  * is rejected.
23
23
  */
24
24
  export const ACTIONS = Object.freeze(['verify', 'audit', 'attest', 'revoke']);
25
25
 
26
- /** RFC-009-C §3 — standings an Organ may render (closed set, lowercase). */
26
+ /** RFC-009-C sec. 3 -- standings an Organ may render (closed set, lowercase). */
27
27
  export const STANDINGS = Object.freeze(['ghost', 'probation', 'radiant', 'genesis']);
28
28
 
29
- /** RFC-009 §10.2 — hash-subject lengths: routing short form / 128-bit / 256-bit. */
29
+ /** RFC-009 sec. 10.2 -- hash-subject lengths: routing short form / 128-bit / 256-bit. */
30
30
  export const HASH_LENGTHS = Object.freeze([8, 32, 64]);
31
31
 
32
- const SAFE = new Set([null, 'verify']); // §11.3: omitted = standing read; verify = read-only
32
+ const SAFE = new Set([null, 'verify']); // sec. 11.3: omitted = standing read; verify = read-only
33
33
  const LOWHEX = new Set('0123456789abcdef');
34
34
  const NAME_CHARS = new Set('abcdefghijklmnopqrstuvwxyz0123456789-');
35
35
  const ALLOWED = new Set([...LOWHEX, ...NAME_CHARS, '.', '/']);
@@ -44,17 +44,17 @@ export class IqaUriError extends Error {
44
44
 
45
45
  const fail = (uri, reason) => new IqaUriError(`${JSON.stringify(uri)}: ${reason}`);
46
46
 
47
- /** §10.2 action test — true only for the closed four-verb set. */
47
+ /** sec. 10.2 action test -- true only for the closed four-verb set. */
48
48
  export function isValidAction(value) {
49
49
  return ACTIONS.includes(value);
50
50
  }
51
51
 
52
52
  /**
53
- * IQA_ROUTE = SHA-256(ASCII(canonical_authority))[0:4] (SPEC §3, decision D1).
53
+ * IQA_ROUTE = SHA-256(ASCII(canonical_authority))[0:4] (SPEC sec. 3, decision D1).
54
54
  *
55
- * Pure computation, DNS-free (RFC-009 §12 #9). Mirrors the RTTP ROUTE_SHARD
55
+ * Pure computation, DNS-free (RFC-009 sec. 12 #9). Mirrors the RTTP ROUTE_SHARD
56
56
  * rule so both schemes of the same addressing family derive the same way.
57
- * NOTE: this is the *address* fingerprint — NOT the subject hash, which is a
57
+ * NOTE: this is the *address* fingerprint -- NOT the subject hash, which is a
58
58
  * routing hint for the *AID*. Different numbers about different things.
59
59
  */
60
60
  export function deriveRoute(authority) {
@@ -71,7 +71,7 @@ export function deriveRouteHex(authority) {
71
71
  * Validate, canonicalise and derive. Throws IqaUriError on any violation.
72
72
  *
73
73
  * Returned fields are snake_case ON PURPOSE so this output and the Python
74
- * package's output compare directly against the published vectors — field
74
+ * package's output compare directly against the published vectors -- field
75
75
  * for field, byte for byte.
76
76
  */
77
77
  export function parse(uri) {
@@ -80,20 +80,20 @@ export function parse(uri) {
80
80
  const prefix = SCHEME + '://';
81
81
  if (!uri.startsWith(prefix)) {
82
82
  throw fail(uri, `scheme must be exactly ${JSON.stringify(prefix)} - no other `
83
- + 'scheme, no case variant, no fallback (RFC-009 §10.3, §12 #8)');
83
+ + 'scheme, no case variant, no fallback (RFC-009 sec. 10.3, sec. 12 #8)');
84
84
  }
85
85
  const rest = uri.slice(prefix.length);
86
86
 
87
- // Charset gate: lowercase US-ASCII only (§10.3). One scan rejects uppercase
87
+ // Charset gate: lowercase US-ASCII only (sec. 10.3). One scan rejects uppercase
88
88
  // anywhere, userinfo '@', port ':', query '?', fragment '#', whitespace and
89
89
  // every other foreign byte.
90
90
  for (const ch of rest) {
91
91
  if (!ALLOWED.has(ch)) {
92
92
  if (FORBIDDEN.has(ch)) {
93
- throw fail(uri, `${JSON.stringify(ch)} is not defined by the scheme (RFC-009 §10.3)`);
93
+ throw fail(uri, `${JSON.stringify(ch)} is not defined by the scheme (RFC-009 sec. 10.3)`);
94
94
  }
95
95
  if (/\s/.test(ch)) {
96
- throw fail(uri, 'whitespace is not allowed (RFC-009 §10.3 lowercase US-ASCII only)');
96
+ throw fail(uri, 'whitespace is not allowed (RFC-009 sec. 10.3 lowercase US-ASCII only)');
97
97
  }
98
98
  throw fail(uri, `character ${JSON.stringify(ch)} is outside the allowed set `
99
99
  + "(lowercase US-ASCII, digits, '-', '.', '/')");
@@ -109,10 +109,10 @@ export function parse(uri) {
109
109
  action = rest.slice(slash + 1);
110
110
  if (action === '') {
111
111
  throw fail(uri, "trailing '/' with empty action - action is a closed-set "
112
- + 'verb, not omissible by empty string (§10.2)');
112
+ + 'verb, not omissible by empty string (sec. 10.2)');
113
113
  }
114
114
  if (action.includes('/')) {
115
- throw fail(uri, "second path segment - path is a single '/<action>' (§10.2)");
115
+ throw fail(uri, "second path segment - path is a single '/<action>' (sec. 10.2)");
116
116
  }
117
117
  }
118
118
 
@@ -120,11 +120,11 @@ export function parse(uri) {
120
120
  const segments = authority.split('.');
121
121
  if (segments.length !== 3) {
122
122
  throw fail(uri, `authority has ${segments.length} segment(s), must be `
123
- + '<subject>.<organ>.<root> (RFC-009 §10.2)');
123
+ + '<subject>.<organ>.<root> (RFC-009 sec. 10.2)');
124
124
  }
125
125
  const [subject, organ, root] = segments;
126
126
  if (subject === '' || root === '') {
127
- throw fail(uri, 'authority segments must be non-empty (RFC-009 §10.2)');
127
+ throw fail(uri, 'authority segments must be non-empty (RFC-009 sec. 10.2)');
128
128
  }
129
129
 
130
130
  // Subject: hash form (8/32/64 lowercase hex) or name form. Ordered choice
@@ -138,16 +138,16 @@ export function parse(uri) {
138
138
  subjectHex = subject;
139
139
  }
140
140
 
141
- // Organ — CLOSED set, case-sensitive (§10.1). 'Forge', 'forgery', '' all fail.
141
+ // Organ -- CLOSED set, case-sensitive (sec. 10.1). 'Forge', 'forgery', '' all fail.
142
142
  if (!ORGANS.includes(organ)) {
143
143
  throw fail(uri, `organ ${JSON.stringify(organ)} is outside the closed set `
144
- + `[${ORGANS.join(', ')}] (RFC-009 §10.1/§10.3)`);
144
+ + `[${ORGANS.join(', ')}] (RFC-009 sec. 10.1/sec. 10.3)`);
145
145
  }
146
146
 
147
- // Action — CLOSED set (§10.1). The deliberate difference from rttp.
147
+ // Action -- CLOSED set (sec. 10.1). The deliberate difference from rttp.
148
148
  if (action !== null && !ACTIONS.includes(action)) {
149
149
  throw fail(uri, `action ${JSON.stringify(action)} is outside the closed set `
150
- + `[${ACTIONS.join(', ')}] (RFC-009 §10.1)`);
150
+ + `[${ACTIONS.join(', ')}] (RFC-009 sec. 10.1)`);
151
151
  }
152
152
 
153
153
  const canonicalUri = SCHEME + '://' + authority + (action ? '/' + action : '');
@@ -162,8 +162,8 @@ export function parse(uri) {
162
162
  authority,
163
163
  action,
164
164
  action_omitted: action === null,
165
- action_safe: SAFE.has(action), // §11.3
165
+ action_safe: SAFE.has(action), // sec. 11.3
166
166
  canonical_uri: canonicalUri,
167
- route_hex: deriveRouteHex(authority), // SPEC §3
167
+ route_hex: deriveRouteHex(authority), // SPEC sec. 3
168
168
  };
169
169
  }
package/src/vectors.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "spec": "IQA-URI-ATTEST-v1.2.6",
3
3
  "applies_to": [
4
- "RFC-009 §10",
5
- "RFC-009 §11"
4
+ "RFC-009 sec. 10",
5
+ "RFC-009 sec. 11"
6
6
  ],
7
7
  "generated_by": "SPEC/tools/iqa_conformance.py",
8
8
  "note": "Generated file - do not edit by hand. Change the cases in iqa_conformance.py instead.",
@@ -135,77 +135,77 @@
135
135
  "action_omitted": false,
136
136
  "action_safe": true,
137
137
  "route_hex": "acee0323",
138
- "note": "note - 9 hex digits is not a hash length (8/32/64), so the subject parses as a name-subject per the ABNF ordered choice (RFC-009 §10.2)"
138
+ "note": "note - 9 hex digits is not a hash length (8/32/64), so the subject parses as a name-subject per the ABNF ordered choice (RFC-009 sec. 10.2)"
139
139
  }
140
140
  ],
141
141
  "negative_uris": [
142
142
  {
143
143
  "uri": "iqa://3f9a1b2c.gateway.iqa/AUDIT",
144
- "reason": "uppercase action - lowercase only (RFC-009 §10.3)"
144
+ "reason": "uppercase action - lowercase only (RFC-009 sec. 10.3)"
145
145
  },
146
146
  {
147
147
  "uri": "iqa://3F9A1B2C.gateway.iqa/audit",
148
- "reason": "uppercase hex subject - lowercase only (RFC-009 §10.3)"
148
+ "reason": "uppercase hex subject - lowercase only (RFC-009 sec. 10.3)"
149
149
  },
150
150
  {
151
151
  "uri": "IQA://3f9a1b2c.gateway.iqa/audit",
152
- "reason": "uppercase scheme prefix - canonical form is lowercase (RFC-009 §10.3)"
152
+ "reason": "uppercase scheme prefix - canonical form is lowercase (RFC-009 sec. 10.3)"
153
153
  },
154
154
  {
155
155
  "uri": "iqas://3f9a1b2c.gateway.iqa/audit",
156
- "reason": "no iqas variant exists and no fallback is defined - fail closed (RFC-009 §12 #8)"
156
+ "reason": "no iqas variant exists and no fallback is defined - fail closed (RFC-009 sec. 12 #8)"
157
157
  },
158
158
  {
159
159
  "uri": "iqa://subject@iqa.org",
160
- "reason": "userinfo is not defined (RFC-009 §10.3)"
160
+ "reason": "userinfo is not defined (RFC-009 sec. 10.3)"
161
161
  },
162
162
  {
163
163
  "uri": "iqa://iqa.org/RFC-009/",
164
- "reason": "no organ, second path segment - this is an https matter (RFC-009 §10.3)"
164
+ "reason": "no organ, second path segment - this is an https matter (RFC-009 sec. 10.3)"
165
165
  },
166
166
  {
167
167
  "uri": "iqa://3f9a1b2c.forgery.iqa",
168
- "reason": "organ is a closed set (RFC-009 §10.1/§10.3)"
168
+ "reason": "organ is a closed set (RFC-009 sec. 10.1/sec. 10.3)"
169
169
  },
170
170
  {
171
171
  "uri": "iqa://3f9a1b2c.gateway.iqa/pulse",
172
- "reason": "action is a closed set - 'pulse' is not one of the four verbs (RFC-009 §10.1)"
172
+ "reason": "action is a closed set - 'pulse' is not one of the four verbs (RFC-009 sec. 10.1)"
173
173
  },
174
174
  {
175
175
  "uri": "iqa://3f9a1b2c.Gateway.iqa/verify",
176
- "reason": "organ is case-sensitive within the closed set (RFC-009 §10.3)"
176
+ "reason": "organ is case-sensitive within the closed set (RFC-009 sec. 10.3)"
177
177
  },
178
178
  {
179
179
  "uri": "iqa://3f9a1b2c.gateway.iqa/audit?x=1",
180
- "reason": "query is not defined (RFC-009 §10.3)"
180
+ "reason": "query is not defined (RFC-009 sec. 10.3)"
181
181
  },
182
182
  {
183
183
  "uri": "iqa://3f9a1b2c.gateway.iqa/audit#f",
184
- "reason": "fragment is not defined (RFC-009 §10.3)"
184
+ "reason": "fragment is not defined (RFC-009 sec. 10.3)"
185
185
  },
186
186
  {
187
187
  "uri": "iqa://3f9a1b2c.gateway.iqa:443/audit",
188
- "reason": "port is not defined (RFC-009 §10.3)"
188
+ "reason": "port is not defined (RFC-009 sec. 10.3)"
189
189
  },
190
190
  {
191
191
  "uri": "iqa://3f9a1b2c.gateway.iqa/a/b",
192
- "reason": "second path segment - path is a single '/<action>' (RFC-009 §10.2)"
192
+ "reason": "second path segment - path is a single '/<action>' (RFC-009 sec. 10.2)"
193
193
  },
194
194
  {
195
195
  "uri": "iqa://3f9a1b2c.gateway.iqa/",
196
- "reason": "trailing '/' with empty action - the closed set has no empty verb (RFC-009 §10.2)"
196
+ "reason": "trailing '/' with empty action - the closed set has no empty verb (RFC-009 sec. 10.2)"
197
197
  },
198
198
  {
199
199
  "uri": "iqa://a.b",
200
- "reason": "authority has two segments, must be <subject>.<organ>.<root> (RFC-009 §10.2)"
200
+ "reason": "authority has two segments, must be <subject>.<organ>.<root> (RFC-009 sec. 10.2)"
201
201
  },
202
202
  {
203
203
  "uri": "iqa://a.b.c.d/audit",
204
- "reason": "authority has four segments, must be <subject>.<organ>.<root> (RFC-009 §10.2)"
204
+ "reason": "authority has four segments, must be <subject>.<organ>.<root> (RFC-009 sec. 10.2)"
205
205
  },
206
206
  {
207
207
  "uri": "https://3f9a1b2c.gateway.iqa/audit",
208
- "reason": "not an iqa scheme - there is no fallback (RFC-009 §10.3, §12 #8)"
208
+ "reason": "not an iqa scheme - there is no fallback (RFC-009 sec. 10.3, sec. 12 #8)"
209
209
  },
210
210
  {
211
211
  "uri": "iqa://3f9a1b2c.gateway.iqa/audit ",
@@ -216,27 +216,27 @@
216
216
  {
217
217
  "action": null,
218
218
  "safe": true,
219
- "why": "omitted - standing read (RFC-009 §11.1/§11.3)"
219
+ "why": "omitted - standing read (RFC-009 sec. 11.1/sec. 11.3)"
220
220
  },
221
221
  {
222
222
  "action": "verify",
223
223
  "safe": true,
224
- "why": "read-only comparison, no state written (RFC-009 §11.3)"
224
+ "why": "read-only comparison, no state written (RFC-009 sec. 11.3)"
225
225
  },
226
226
  {
227
227
  "action": "audit",
228
228
  "safe": false,
229
- "why": "a failing measurement triggers Authority Ischemia (RFC-009 §11.3/§11.4)"
229
+ "why": "a failing measurement triggers Authority Ischemia (RFC-009 sec. 11.3/sec. 11.4)"
230
230
  },
231
231
  {
232
232
  "action": "attest",
233
233
  "safe": false,
234
- "why": "state transition - Seal issuance (RFC-009 §11.3)"
234
+ "why": "state transition - Seal issuance (RFC-009 sec. 11.3)"
235
235
  },
236
236
  {
237
237
  "action": "revoke",
238
238
  "safe": false,
239
- "why": "state transition - standing withdrawal (RFC-009 §11.3)"
239
+ "why": "state transition - standing withdrawal (RFC-009 sec. 11.3)"
240
240
  }
241
241
  ],
242
242
  "attest_envelope_vector": {