@aicent/iqa 1.2.8 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +298 -258
- package/bin/iqa.mjs +59 -59
- package/package.json +9 -2
- package/src/ae128-vectors-v1.json +114 -0
- package/src/conformance.d.mts +29 -29
- package/src/conformance.mjs +181 -181
- package/src/envelope.d.mts +103 -0
- package/src/envelope.mjs +309 -0
- package/src/iqa-uri.d.mts +42 -42
- package/src/iqa-uri.mjs +169 -169
- package/src/vectors.json +26 -26
package/src/iqa-uri.mjs
CHANGED
|
@@ -1,169 +1,169 @@
|
|
|
1
|
-
// iqa:// URI codec -- validate, canonicalise, derive (
|
|
2
|
-
//
|
|
3
|
-
// Authority boundaries:
|
|
4
|
-
// Grammar .........
|
|
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
|
-
//
|
|
10
|
-
// Zero dependencies. Node 18+. No build step -- the published src/ is what runs.
|
|
11
|
-
|
|
12
|
-
import { createHash } from 'node:crypto';
|
|
13
|
-
|
|
14
|
-
export const SCHEME = 'iqa';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
export const ORGANS = Object.freeze(['forge', 'tss', 'gateway']);
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
* from `rttp`, whose verb set is open: here anything outside the four verbs
|
|
22
|
-
* is rejected.
|
|
23
|
-
*/
|
|
24
|
-
export const ACTIONS = Object.freeze(['verify', 'audit', 'attest', 'revoke']);
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
export const STANDINGS = Object.freeze(['ghost', 'probation', 'radiant', 'genesis']);
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
export const HASH_LENGTHS = Object.freeze([8, 32, 64]);
|
|
31
|
-
|
|
32
|
-
const SAFE = new Set([null, 'verify']); // sec. 11.3: omitted = standing read; verify = read-only
|
|
33
|
-
const LOWHEX = new Set('0123456789abcdef');
|
|
34
|
-
const NAME_CHARS = new Set('abcdefghijklmnopqrstuvwxyz0123456789-');
|
|
35
|
-
const ALLOWED = new Set([...LOWHEX, ...NAME_CHARS, '.', '/']);
|
|
36
|
-
const FORBIDDEN = new Set(['@', ':', '?', '#', '[', ']']);
|
|
37
|
-
|
|
38
|
-
export class IqaUriError extends Error {
|
|
39
|
-
constructor(message) {
|
|
40
|
-
super(message);
|
|
41
|
-
this.name = 'IqaUriError';
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const fail = (uri, reason) => new IqaUriError(`${JSON.stringify(uri)}: ${reason}`);
|
|
46
|
-
|
|
47
|
-
/** sec. 10.2 action test -- true only for the closed four-verb set. */
|
|
48
|
-
export function isValidAction(value) {
|
|
49
|
-
return ACTIONS.includes(value);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* IQA_ROUTE = SHA-256(ASCII(canonical_authority))[0:4] (SPEC sec. 3, decision D1).
|
|
54
|
-
*
|
|
55
|
-
* Pure computation, DNS-free (
|
|
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
|
|
58
|
-
* routing hint for the *AID*. Different numbers about different things.
|
|
59
|
-
*/
|
|
60
|
-
export function deriveRoute(authority) {
|
|
61
|
-
if (!authority) throw new IqaUriError('deriveRoute: authority is empty');
|
|
62
|
-
return createHash('sha256').update(authority, 'ascii').digest().subarray(0, 4);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** The same four bytes, as 8 lowercase hex characters. */
|
|
66
|
-
export function deriveRouteHex(authority) {
|
|
67
|
-
return deriveRoute(authority).toString('hex');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Validate, canonicalise and derive. Throws IqaUriError on any violation.
|
|
72
|
-
*
|
|
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
|
|
75
|
-
* for field, byte for byte.
|
|
76
|
-
*/
|
|
77
|
-
export function parse(uri) {
|
|
78
|
-
if (typeof uri !== 'string') throw fail(uri, 'input must be a string');
|
|
79
|
-
|
|
80
|
-
const prefix = SCHEME + '://';
|
|
81
|
-
if (!uri.startsWith(prefix)) {
|
|
82
|
-
throw fail(uri, `scheme must be exactly ${JSON.stringify(prefix)} - no other `
|
|
83
|
-
+ 'scheme, no case variant, no fallback (
|
|
84
|
-
}
|
|
85
|
-
const rest = uri.slice(prefix.length);
|
|
86
|
-
|
|
87
|
-
// Charset gate: lowercase US-ASCII only (sec. 10.3). One scan rejects uppercase
|
|
88
|
-
// anywhere, userinfo '@', port ':', query '?', fragment '#', whitespace and
|
|
89
|
-
// every other foreign byte.
|
|
90
|
-
for (const ch of rest) {
|
|
91
|
-
if (!ALLOWED.has(ch)) {
|
|
92
|
-
if (FORBIDDEN.has(ch)) {
|
|
93
|
-
throw fail(uri, `${JSON.stringify(ch)} is not defined by the scheme (
|
|
94
|
-
}
|
|
95
|
-
if (/\s/.test(ch)) {
|
|
96
|
-
throw fail(uri, 'whitespace is not allowed (
|
|
97
|
-
}
|
|
98
|
-
throw fail(uri, `character ${JSON.stringify(ch)} is outside the allowed set `
|
|
99
|
-
+ "(lowercase US-ASCII, digits, '-', '.', '/')");
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Path: at most one '/', and the action behind it must be non-empty.
|
|
104
|
-
let authority = rest;
|
|
105
|
-
let action = null;
|
|
106
|
-
const slash = rest.indexOf('/');
|
|
107
|
-
if (slash !== -1) {
|
|
108
|
-
authority = rest.slice(0, slash);
|
|
109
|
-
action = rest.slice(slash + 1);
|
|
110
|
-
if (action === '') {
|
|
111
|
-
throw fail(uri, "trailing '/' with empty action - action is a closed-set "
|
|
112
|
-
+ 'verb, not omissible by empty string (sec. 10.2)');
|
|
113
|
-
}
|
|
114
|
-
if (action.includes('/')) {
|
|
115
|
-
throw fail(uri, "second path segment - path is a single '/<action>' (sec. 10.2)");
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Authority: exactly three segments.
|
|
120
|
-
const segments = authority.split('.');
|
|
121
|
-
if (segments.length !== 3) {
|
|
122
|
-
throw fail(uri, `authority has ${segments.length} segment(s), must be `
|
|
123
|
-
+ '<subject>.<organ>.<root> (
|
|
124
|
-
}
|
|
125
|
-
const [subject, organ, root] = segments;
|
|
126
|
-
if (subject === '' || root === '') {
|
|
127
|
-
throw fail(uri, 'authority segments must be non-empty (
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Subject: hash form (8/32/64 lowercase hex) or name form. Ordered choice
|
|
131
|
-
// per the ABNF: all-lowerhex input is tried as hash-subject first; a
|
|
132
|
-
// lowercase-hex string of another length is therefore a NAME subject,
|
|
133
|
-
// exactly as the grammar says. Documented, not "fixed".
|
|
134
|
-
let subjectIsHash = false;
|
|
135
|
-
let subjectHex = null;
|
|
136
|
-
if ([...subject].every((c) => LOWHEX.has(c)) && HASH_LENGTHS.includes(subject.length)) {
|
|
137
|
-
subjectIsHash = true;
|
|
138
|
-
subjectHex = subject;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Organ -- CLOSED set, case-sensitive (sec. 10.1). 'Forge', 'forgery', '' all fail.
|
|
142
|
-
if (!ORGANS.includes(organ)) {
|
|
143
|
-
throw fail(uri, `organ ${JSON.stringify(organ)} is outside the closed set `
|
|
144
|
-
+ `[${ORGANS.join(', ')}] (
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Action -- CLOSED set (sec. 10.1). The deliberate difference from rttp.
|
|
148
|
-
if (action !== null && !ACTIONS.includes(action)) {
|
|
149
|
-
throw fail(uri, `action ${JSON.stringify(action)} is outside the closed set `
|
|
150
|
-
+ `[${ACTIONS.join(', ')}] (
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const canonicalUri = SCHEME + '://' + authority + (action ? '/' + action : '');
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
scheme: SCHEME,
|
|
157
|
-
subject,
|
|
158
|
-
subject_is_hash: subjectIsHash,
|
|
159
|
-
subject_hex: subjectHex,
|
|
160
|
-
organ,
|
|
161
|
-
root,
|
|
162
|
-
authority,
|
|
163
|
-
action,
|
|
164
|
-
action_omitted: action === null,
|
|
165
|
-
action_safe: SAFE.has(action), // sec. 11.3
|
|
166
|
-
canonical_uri: canonicalUri,
|
|
167
|
-
route_hex: deriveRouteHex(authority), // SPEC sec. 3
|
|
168
|
-
};
|
|
169
|
-
}
|
|
1
|
+
// iqa:// URI codec -- validate, canonicalise, derive (AICENT-009 sec. 10/sec. 11).
|
|
2
|
+
//
|
|
3
|
+
// Authority boundaries:
|
|
4
|
+
// Grammar ......... AICENT-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
|
+
//
|
|
10
|
+
// Zero dependencies. Node 18+. No build step -- the published src/ is what runs.
|
|
11
|
+
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
export const SCHEME = 'iqa';
|
|
15
|
+
|
|
16
|
+
/** AICENT-009 sec. 10.1 -- organ is a CLOSED set (AICENT-009-A/-B/-C). */
|
|
17
|
+
export const ORGANS = Object.freeze(['forge', 'tss', 'gateway']);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* AICENT-009 sec. 10.1 -- action is a CLOSED set. This is the deliberate difference
|
|
21
|
+
* from `rttp`, whose verb set is open: here anything outside the four verbs
|
|
22
|
+
* is rejected.
|
|
23
|
+
*/
|
|
24
|
+
export const ACTIONS = Object.freeze(['verify', 'audit', 'attest', 'revoke']);
|
|
25
|
+
|
|
26
|
+
/** AICENT-009-C sec. 3 -- standings an Organ may render (closed set, lowercase). */
|
|
27
|
+
export const STANDINGS = Object.freeze(['ghost', 'probation', 'radiant', 'genesis']);
|
|
28
|
+
|
|
29
|
+
/** AICENT-009 sec. 10.2 -- hash-subject lengths: routing short form / 128-bit / 256-bit. */
|
|
30
|
+
export const HASH_LENGTHS = Object.freeze([8, 32, 64]);
|
|
31
|
+
|
|
32
|
+
const SAFE = new Set([null, 'verify']); // sec. 11.3: omitted = standing read; verify = read-only
|
|
33
|
+
const LOWHEX = new Set('0123456789abcdef');
|
|
34
|
+
const NAME_CHARS = new Set('abcdefghijklmnopqrstuvwxyz0123456789-');
|
|
35
|
+
const ALLOWED = new Set([...LOWHEX, ...NAME_CHARS, '.', '/']);
|
|
36
|
+
const FORBIDDEN = new Set(['@', ':', '?', '#', '[', ']']);
|
|
37
|
+
|
|
38
|
+
export class IqaUriError extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = 'IqaUriError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fail = (uri, reason) => new IqaUriError(`${JSON.stringify(uri)}: ${reason}`);
|
|
46
|
+
|
|
47
|
+
/** sec. 10.2 action test -- true only for the closed four-verb set. */
|
|
48
|
+
export function isValidAction(value) {
|
|
49
|
+
return ACTIONS.includes(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* IQA_ROUTE = SHA-256(ASCII(canonical_authority))[0:4] (SPEC sec. 3, decision D1).
|
|
54
|
+
*
|
|
55
|
+
* Pure computation, DNS-free (AICENT-009 sec. 12 #9). Mirrors the RTTP ROUTE_SHARD
|
|
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
|
|
58
|
+
* routing hint for the *AID*. Different numbers about different things.
|
|
59
|
+
*/
|
|
60
|
+
export function deriveRoute(authority) {
|
|
61
|
+
if (!authority) throw new IqaUriError('deriveRoute: authority is empty');
|
|
62
|
+
return createHash('sha256').update(authority, 'ascii').digest().subarray(0, 4);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The same four bytes, as 8 lowercase hex characters. */
|
|
66
|
+
export function deriveRouteHex(authority) {
|
|
67
|
+
return deriveRoute(authority).toString('hex');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validate, canonicalise and derive. Throws IqaUriError on any violation.
|
|
72
|
+
*
|
|
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
|
|
75
|
+
* for field, byte for byte.
|
|
76
|
+
*/
|
|
77
|
+
export function parse(uri) {
|
|
78
|
+
if (typeof uri !== 'string') throw fail(uri, 'input must be a string');
|
|
79
|
+
|
|
80
|
+
const prefix = SCHEME + '://';
|
|
81
|
+
if (!uri.startsWith(prefix)) {
|
|
82
|
+
throw fail(uri, `scheme must be exactly ${JSON.stringify(prefix)} - no other `
|
|
83
|
+
+ 'scheme, no case variant, no fallback (AICENT-009 sec. 10.3, sec. 12 #8)');
|
|
84
|
+
}
|
|
85
|
+
const rest = uri.slice(prefix.length);
|
|
86
|
+
|
|
87
|
+
// Charset gate: lowercase US-ASCII only (sec. 10.3). One scan rejects uppercase
|
|
88
|
+
// anywhere, userinfo '@', port ':', query '?', fragment '#', whitespace and
|
|
89
|
+
// every other foreign byte.
|
|
90
|
+
for (const ch of rest) {
|
|
91
|
+
if (!ALLOWED.has(ch)) {
|
|
92
|
+
if (FORBIDDEN.has(ch)) {
|
|
93
|
+
throw fail(uri, `${JSON.stringify(ch)} is not defined by the scheme (AICENT-009 sec. 10.3)`);
|
|
94
|
+
}
|
|
95
|
+
if (/\s/.test(ch)) {
|
|
96
|
+
throw fail(uri, 'whitespace is not allowed (AICENT-009 sec. 10.3 lowercase US-ASCII only)');
|
|
97
|
+
}
|
|
98
|
+
throw fail(uri, `character ${JSON.stringify(ch)} is outside the allowed set `
|
|
99
|
+
+ "(lowercase US-ASCII, digits, '-', '.', '/')");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Path: at most one '/', and the action behind it must be non-empty.
|
|
104
|
+
let authority = rest;
|
|
105
|
+
let action = null;
|
|
106
|
+
const slash = rest.indexOf('/');
|
|
107
|
+
if (slash !== -1) {
|
|
108
|
+
authority = rest.slice(0, slash);
|
|
109
|
+
action = rest.slice(slash + 1);
|
|
110
|
+
if (action === '') {
|
|
111
|
+
throw fail(uri, "trailing '/' with empty action - action is a closed-set "
|
|
112
|
+
+ 'verb, not omissible by empty string (sec. 10.2)');
|
|
113
|
+
}
|
|
114
|
+
if (action.includes('/')) {
|
|
115
|
+
throw fail(uri, "second path segment - path is a single '/<action>' (sec. 10.2)");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Authority: exactly three segments.
|
|
120
|
+
const segments = authority.split('.');
|
|
121
|
+
if (segments.length !== 3) {
|
|
122
|
+
throw fail(uri, `authority has ${segments.length} segment(s), must be `
|
|
123
|
+
+ '<subject>.<organ>.<root> (AICENT-009 sec. 10.2)');
|
|
124
|
+
}
|
|
125
|
+
const [subject, organ, root] = segments;
|
|
126
|
+
if (subject === '' || root === '') {
|
|
127
|
+
throw fail(uri, 'authority segments must be non-empty (AICENT-009 sec. 10.2)');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Subject: hash form (8/32/64 lowercase hex) or name form. Ordered choice
|
|
131
|
+
// per the ABNF: all-lowerhex input is tried as hash-subject first; a
|
|
132
|
+
// lowercase-hex string of another length is therefore a NAME subject,
|
|
133
|
+
// exactly as the grammar says. Documented, not "fixed".
|
|
134
|
+
let subjectIsHash = false;
|
|
135
|
+
let subjectHex = null;
|
|
136
|
+
if ([...subject].every((c) => LOWHEX.has(c)) && HASH_LENGTHS.includes(subject.length)) {
|
|
137
|
+
subjectIsHash = true;
|
|
138
|
+
subjectHex = subject;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Organ -- CLOSED set, case-sensitive (sec. 10.1). 'Forge', 'forgery', '' all fail.
|
|
142
|
+
if (!ORGANS.includes(organ)) {
|
|
143
|
+
throw fail(uri, `organ ${JSON.stringify(organ)} is outside the closed set `
|
|
144
|
+
+ `[${ORGANS.join(', ')}] (AICENT-009 sec. 10.1/sec. 10.3)`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Action -- CLOSED set (sec. 10.1). The deliberate difference from rttp.
|
|
148
|
+
if (action !== null && !ACTIONS.includes(action)) {
|
|
149
|
+
throw fail(uri, `action ${JSON.stringify(action)} is outside the closed set `
|
|
150
|
+
+ `[${ACTIONS.join(', ')}] (AICENT-009 sec. 10.1)`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const canonicalUri = SCHEME + '://' + authority + (action ? '/' + action : '');
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
scheme: SCHEME,
|
|
157
|
+
subject,
|
|
158
|
+
subject_is_hash: subjectIsHash,
|
|
159
|
+
subject_hex: subjectHex,
|
|
160
|
+
organ,
|
|
161
|
+
root,
|
|
162
|
+
authority,
|
|
163
|
+
action,
|
|
164
|
+
action_omitted: action === null,
|
|
165
|
+
action_safe: SAFE.has(action), // sec. 11.3
|
|
166
|
+
canonical_uri: canonicalUri,
|
|
167
|
+
route_hex: deriveRouteHex(authority), // SPEC sec. 3
|
|
168
|
+
};
|
|
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
|
-
"
|
|
5
|
-
"
|
|
4
|
+
"draft-li-rttp-iqa-addressing-02 §§4.2, 5",
|
|
5
|
+
"AICENT-009 sec. 10-11 (attestation semantics; beyond -02)"
|
|
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,108 +135,108 @@
|
|
|
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 (
|
|
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 (draft-02 §5.1)"
|
|
139
139
|
}
|
|
140
140
|
],
|
|
141
141
|
"negative_uris": [
|
|
142
142
|
{
|
|
143
143
|
"uri": "iqa://3f9a1b2c.gateway.iqa/AUDIT",
|
|
144
|
-
"reason": "uppercase action - lowercase only (
|
|
144
|
+
"reason": "uppercase action - lowercase only (draft-02 §4.2 + §5.1)"
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
147
|
"uri": "iqa://3F9A1B2C.gateway.iqa/audit",
|
|
148
|
-
"reason": "uppercase hex subject - lowercase only (
|
|
148
|
+
"reason": "uppercase hex subject - lowercase only (draft-02 §4.2 + §5.2)"
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
"uri": "IQA://3f9a1b2c.gateway.iqa/audit",
|
|
152
|
-
"reason": "uppercase scheme prefix - canonical form is lowercase (
|
|
152
|
+
"reason": "uppercase scheme prefix - canonical form is lowercase (draft-02 §4.2)"
|
|
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 (
|
|
156
|
+
"reason": "no iqas variant exists and no fallback is defined - fail closed (draft-02 §5.2)"
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
"uri": "iqa://subject@iqa.org",
|
|
160
|
-
"reason": "userinfo is not defined (
|
|
160
|
+
"reason": "userinfo is not defined (draft-02 §4.2)"
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"uri": "iqa://iqa.org/RFC-009/",
|
|
164
|
-
"reason": "no organ, second path segment - this is an https matter (
|
|
164
|
+
"reason": "no organ, second path segment - this is an https matter (draft-02 §5.1)"
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
"uri": "iqa://3f9a1b2c.forgery.iqa",
|
|
168
|
-
"reason": "organ is a closed set (
|
|
168
|
+
"reason": "organ is a closed set (draft-02 §5.1)"
|
|
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 (
|
|
172
|
+
"reason": "action is a closed set - 'pulse' is not one of the four verbs (draft-02 §5.1)"
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
175
|
"uri": "iqa://3f9a1b2c.Gateway.iqa/verify",
|
|
176
|
-
"reason": "organ is case-sensitive within the closed set (
|
|
176
|
+
"reason": "organ is case-sensitive within the closed set (draft-02 §5.1 + §4.2)"
|
|
177
177
|
},
|
|
178
178
|
{
|
|
179
179
|
"uri": "iqa://3f9a1b2c.gateway.iqa/audit?x=1",
|
|
180
|
-
"reason": "query is not defined (
|
|
180
|
+
"reason": "query is not defined (draft-02 §4.2)"
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
"uri": "iqa://3f9a1b2c.gateway.iqa/audit#f",
|
|
184
|
-
"reason": "fragment is not defined (
|
|
184
|
+
"reason": "fragment is not defined (draft-02 §4.2)"
|
|
185
185
|
},
|
|
186
186
|
{
|
|
187
187
|
"uri": "iqa://3f9a1b2c.gateway.iqa:443/audit",
|
|
188
|
-
"reason": "port is not defined (
|
|
188
|
+
"reason": "port is not defined (draft-02 §4.2)"
|
|
189
189
|
},
|
|
190
190
|
{
|
|
191
191
|
"uri": "iqa://3f9a1b2c.gateway.iqa/a/b",
|
|
192
|
-
"reason": "second path segment - path is a single '/<action>' (
|
|
192
|
+
"reason": "second path segment - path is a single '/<action>' (draft-02 §5.1)"
|
|
193
193
|
},
|
|
194
194
|
{
|
|
195
195
|
"uri": "iqa://3f9a1b2c.gateway.iqa/",
|
|
196
|
-
"reason": "trailing '/' with empty action - the closed set has no empty verb (
|
|
196
|
+
"reason": "trailing '/' with empty action - the closed set has no empty verb (draft-02 §5.1)"
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
"uri": "iqa://a.b",
|
|
200
|
-
"reason": "authority has two segments, must be <subject>.<organ>.<root> (
|
|
200
|
+
"reason": "authority has two segments, must be <subject>.<organ>.<root> (draft-02 §5.1)"
|
|
201
201
|
},
|
|
202
202
|
{
|
|
203
203
|
"uri": "iqa://a.b.c.d/audit",
|
|
204
|
-
"reason": "authority has four segments, must be <subject>.<organ>.<root> (
|
|
204
|
+
"reason": "authority has four segments, must be <subject>.<organ>.<root> (draft-02 §5.1)"
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
207
|
"uri": "https://3f9a1b2c.gateway.iqa/audit",
|
|
208
|
-
"reason": "not an iqa scheme - there is no fallback (
|
|
208
|
+
"reason": "not an iqa scheme - there is no fallback (draft-02 §5.2 + §4.5.2)"
|
|
209
209
|
},
|
|
210
210
|
{
|
|
211
211
|
"uri": "iqa://3f9a1b2c.gateway.iqa/audit ",
|
|
212
|
-
"reason": "trailing whitespace"
|
|
212
|
+
"reason": "trailing whitespace (draft-02 §5.1)"
|
|
213
213
|
}
|
|
214
214
|
],
|
|
215
215
|
"action_safety": [
|
|
216
216
|
{
|
|
217
217
|
"action": null,
|
|
218
218
|
"safe": true,
|
|
219
|
-
"why": "omitted - standing read (
|
|
219
|
+
"why": "omitted - standing read (draft-02 §5.3/§5.4)"
|
|
220
220
|
},
|
|
221
221
|
{
|
|
222
222
|
"action": "verify",
|
|
223
223
|
"safe": true,
|
|
224
|
-
"why": "read-only comparison, no state written (
|
|
224
|
+
"why": "read-only comparison, no state written (draft-02 §5.4)"
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
227
|
"action": "audit",
|
|
228
228
|
"safe": false,
|
|
229
|
-
"why": "a failing measurement
|
|
229
|
+
"why": "a failing measurement may change standing (draft-02 §5.4)"
|
|
230
230
|
},
|
|
231
231
|
{
|
|
232
232
|
"action": "attest",
|
|
233
233
|
"safe": false,
|
|
234
|
-
"why": "state transition - Seal issuance (
|
|
234
|
+
"why": "state transition - Seal issuance (draft-02 §5.4)"
|
|
235
235
|
},
|
|
236
236
|
{
|
|
237
237
|
"action": "revoke",
|
|
238
238
|
"safe": false,
|
|
239
|
-
"why": "state transition - standing withdrawal (
|
|
239
|
+
"why": "state transition - standing withdrawal (draft-02 §5.4)"
|
|
240
240
|
}
|
|
241
241
|
],
|
|
242
242
|
"attest_envelope_vector": {
|