@kashscript/lexicons 0.2.0-alpha.0 → 0.4.0-alpha.0
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/LICENSE +1 -1
- package/README.md +74 -74
- package/dist/extensions/door.d.ts +158 -47
- package/dist/extensions/door.d.ts.map +1 -1
- package/dist/extensions/door.js +124 -45
- package/dist/extensions/door.js.map +1 -1
- package/package.json +1 -1
- package/src/extensions/door.ts +560 -384
package/src/extensions/door.ts
CHANGED
|
@@ -1,384 +1,560 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Door Extension — the rule a door publishes, and the decision receipt it
|
|
3
|
-
* gives back (ECOSYSTEM d84(a): rule · receipt · decision · recourse · renewal).
|
|
4
|
-
*
|
|
5
|
-
* kash.rule@1.0.0
|
|
6
|
-
* A door's published rule: what it decides, why, who decides (a role),
|
|
7
|
-
* the reasons it may give, its contest window, its renewal and end dates,
|
|
8
|
-
* and its criteria. Each criterion is an OpenID4VP 1.0 DCQL query for the
|
|
9
|
-
* receipts that answer it (or the person's own word) plus a FEEL condition
|
|
10
|
-
* over what the query returns — DMN's literal expression. Public and
|
|
11
|
-
* organisation-authored. Signed by its author as a DSSE envelope
|
|
12
|
-
* (`application/vnd.kash.rule+json`).
|
|
13
|
-
*
|
|
14
|
-
* kash.
|
|
15
|
-
* The
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
readonly
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
readonly
|
|
96
|
-
readonly
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
readonly
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
},
|
|
136
|
-
["id", "
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
readonly
|
|
186
|
-
readonly
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
readonly
|
|
210
|
-
/**
|
|
211
|
-
readonly
|
|
212
|
-
readonly
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
readonly
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
readonly
|
|
227
|
-
|
|
228
|
-
readonly
|
|
229
|
-
|
|
230
|
-
readonly
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
[
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
),
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
export
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Door Extension — the rule a door publishes, and the decision receipt it
|
|
3
|
+
* gives back (ECOSYSTEM d84(a): rule · receipt · decision · recourse · renewal).
|
|
4
|
+
*
|
|
5
|
+
* kash.rule@1.0.0
|
|
6
|
+
* A door's published rule: what it decides, why, who decides (a role),
|
|
7
|
+
* the reasons it may give, its contest window, its renewal and end dates,
|
|
8
|
+
* and its criteria. Each criterion is an OpenID4VP 1.0 DCQL query for the
|
|
9
|
+
* receipts that answer it (or the person's own word) plus a FEEL condition
|
|
10
|
+
* over what the query returns — DMN's literal expression. Public and
|
|
11
|
+
* organisation-authored. Signed by its author as a DSSE envelope
|
|
12
|
+
* (`application/vnd.kash.rule+json`).
|
|
13
|
+
*
|
|
14
|
+
* kash.rule@1.1.0 (KS-Q1b; ECOSYSTEM d85(b)–(c))
|
|
15
|
+
* The same rule with its **address** — sector (ISIC Revision 5), anchor
|
|
16
|
+
* family, authority, journey and the seven givens — a **subject** on each
|
|
17
|
+
* criterion, **bounds** on the parameters an adopter may set, and `draft`.
|
|
18
|
+
* Every new field is optional, so every 1.0.0 body is a 1.1.0 body.
|
|
19
|
+
*
|
|
20
|
+
* kash.rule@1.2.0 (KS-Q11)
|
|
21
|
+
* The same rule, with `meta.holder: "countersigner"` on a `kash-artifact`
|
|
22
|
+
* query: the presentation is bound to the one key whose acceptance in the
|
|
23
|
+
* countersign bundle verifies (a guest who accepted a host's receipt).
|
|
24
|
+
* Optional, so every 1.1.0 body is a 1.2.0 body; a 1.1.0 reader refuses a
|
|
25
|
+
* rule that names it, rather than binding it to the requester.
|
|
26
|
+
*
|
|
27
|
+
* kash.adoption@1.0.0
|
|
28
|
+
* An anchor — an organisation — adopts one version of a rule with its own
|
|
29
|
+
* values for the parameters the rule bounds. Public, signed by the anchor
|
|
30
|
+
* as DSSE (`application/vnd.kash.adoption+json`). An adoption is the
|
|
31
|
+
* anchor's pact (d85(d)): rulebooks written once, executed locally.
|
|
32
|
+
*
|
|
33
|
+
* Every shape is closed: a field this schema does not name is refused, so none
|
|
34
|
+
* can carry a score or a person's identity. Meaning — the floor's refusals, the
|
|
35
|
+
* FEEL check, bounds, completeness — is checked by `@kashscript/predicate`;
|
|
36
|
+
* this extension is the shape.
|
|
37
|
+
*
|
|
38
|
+
* **The decision receipt is not here** (since 0.3.0): it is the person's own
|
|
39
|
+
* record, a credential they hold, so its schema lives with the package that
|
|
40
|
+
* issues and checks it — `@kashscript/predicate/decision`. d59 makes lexicons
|
|
41
|
+
* the home of public, organisation-authored records only.
|
|
42
|
+
*
|
|
43
|
+
* Import path: `@kashscript/lexicons/extensions/door`.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import type { LexiconDef } from "../registry";
|
|
47
|
+
import { S, type ObjectSchema, type Schema } from "../schema";
|
|
48
|
+
|
|
49
|
+
/* ── shared pieces ─────────────────────────────────────────────────── */
|
|
50
|
+
|
|
51
|
+
const DID = S.string({ format: "did-ks" });
|
|
52
|
+
const ISO = S.string({ format: "iso8601" });
|
|
53
|
+
const HEX64 = S.string({ pattern: /^[0-9a-f]{64}$/ });
|
|
54
|
+
const SLUG = S.string({ pattern: /^[a-z0-9][a-z0-9-]{0,62}$/ });
|
|
55
|
+
/** DCQL ids: alphanumerics, `_` and `-` (OpenID4VP 1.0 §6.1). */
|
|
56
|
+
const DCQL_ID = S.string({ pattern: /^[A-Za-z0-9_-]{1,64}$/ });
|
|
57
|
+
const TEXT = (max: number): Schema => S.string({ minLength: 1, maxLength: max });
|
|
58
|
+
|
|
59
|
+
/** The DMN types a rule may give a value it reads — DMN's itemDefinition, in the FEEL subset. */
|
|
60
|
+
export const DOOR_INPUT_TYPES = [
|
|
61
|
+
"string",
|
|
62
|
+
"number",
|
|
63
|
+
"boolean",
|
|
64
|
+
"date",
|
|
65
|
+
"date and time",
|
|
66
|
+
"days and time duration",
|
|
67
|
+
"years and months duration",
|
|
68
|
+
"list of string",
|
|
69
|
+
"list of number",
|
|
70
|
+
"list of boolean",
|
|
71
|
+
"list of date",
|
|
72
|
+
"list of date and time",
|
|
73
|
+
"list of days and time duration",
|
|
74
|
+
"list of years and months duration",
|
|
75
|
+
] as const;
|
|
76
|
+
export type DoorInputType = (typeof DOOR_INPUT_TYPES)[number];
|
|
77
|
+
const INPUT_TYPE = S.enum(...DOOR_INPUT_TYPES);
|
|
78
|
+
|
|
79
|
+
export const DECISION_OUTCOMES = ["yes", "not-yet", "no", "partial"] as const;
|
|
80
|
+
export type DecisionOutcome = (typeof DECISION_OUTCOMES)[number];
|
|
81
|
+
|
|
82
|
+
/* ── DCQL (OpenID4VP 1.0 §6), as a rule carries it ─────────────────── */
|
|
83
|
+
|
|
84
|
+
export type ClaimPathElement = string | number | null;
|
|
85
|
+
|
|
86
|
+
export interface DcqlClaimsQuery {
|
|
87
|
+
/** Required here: a claim's id is the name its criterion's condition reads. */
|
|
88
|
+
readonly id: string;
|
|
89
|
+
readonly path: readonly ClaimPathElement[];
|
|
90
|
+
readonly values?: readonly (string | number | boolean)[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface DcqlTrustedAuthoritiesQuery {
|
|
94
|
+
/** `kash_issuer` (issuer DIDs) or `kash_trust_list` (`<publisher DID>#<list id>`), in the spec's extension point. */
|
|
95
|
+
readonly type: string;
|
|
96
|
+
readonly values: readonly string[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface DcqlMeta {
|
|
100
|
+
/** `dc+sd-jwt`: the accepted `vct` values. */
|
|
101
|
+
readonly vct_values?: readonly string[];
|
|
102
|
+
/** `kash-artifact`: the accepted registered DSTs (attest's registry). */
|
|
103
|
+
readonly dst_values?: readonly string[];
|
|
104
|
+
/** `kash-artifact`: `countersigned` asks for two independent signatures over the same bytes. */
|
|
105
|
+
readonly attestation?: "countersigned" | "issuer-signed";
|
|
106
|
+
/** `kash-artifact`: where in the signed payload the holder's key is named, for the presentation's binding. */
|
|
107
|
+
readonly holder_path?: readonly ClaimPathElement[];
|
|
108
|
+
/**
|
|
109
|
+
* `kash-artifact`, **kash.rule@1.2.0** (KS-Q11): `countersigner` names the
|
|
110
|
+
* holder as the one key whose acceptance in the countersign bundle verifies —
|
|
111
|
+
* the party who countersigned these bytes, not the one who asked. Never with
|
|
112
|
+
* `holder_path`; only with `attestation: countersigned`.
|
|
113
|
+
*/
|
|
114
|
+
readonly holder?: "countersigner";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface DcqlCredentialQuery {
|
|
118
|
+
readonly id: string;
|
|
119
|
+
readonly format: string;
|
|
120
|
+
readonly multiple?: boolean;
|
|
121
|
+
readonly meta?: DcqlMeta;
|
|
122
|
+
readonly trusted_authorities?: readonly DcqlTrustedAuthoritiesQuery[];
|
|
123
|
+
readonly require_cryptographic_holder_binding?: boolean;
|
|
124
|
+
readonly claims?: readonly DcqlClaimsQuery[];
|
|
125
|
+
readonly claim_sets?: readonly (readonly string[])[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const PATH = S.array(S.union(S.string({ minLength: 1, maxLength: 128 }), S.number({ integer: true, min: 0 }), S.literal(null)), { minItems: 1, maxItems: 16 });
|
|
129
|
+
|
|
130
|
+
export const DCQL_CLAIMS_QUERY_SCHEMA: ObjectSchema = S.object(
|
|
131
|
+
{
|
|
132
|
+
id: DCQL_ID,
|
|
133
|
+
path: PATH,
|
|
134
|
+
values: S.optional(S.array(S.union(S.string({ maxLength: 512 }), S.number({ integer: true }), S.boolean()), { minItems: 1, maxItems: 64 })),
|
|
135
|
+
},
|
|
136
|
+
["id", "path"],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const DCQL_META_PROPERTIES = {
|
|
140
|
+
vct_values: S.optional(S.array(S.string({ minLength: 1, maxLength: 256 }), { minItems: 1, maxItems: 16 })),
|
|
141
|
+
dst_values: S.optional(S.array(S.string({ minLength: 1, maxLength: 256 }), { minItems: 1, maxItems: 16 })),
|
|
142
|
+
attestation: S.optional(S.enum("countersigned", "issuer-signed")),
|
|
143
|
+
holder_path: S.optional(PATH),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const DCQL_CREDENTIAL_QUERY_SCHEMA: ObjectSchema = S.object(
|
|
147
|
+
{
|
|
148
|
+
id: DCQL_ID,
|
|
149
|
+
format: S.string({ minLength: 1, maxLength: 64 }),
|
|
150
|
+
multiple: S.optional(S.boolean()),
|
|
151
|
+
meta: S.optional(S.object(DCQL_META_PROPERTIES)),
|
|
152
|
+
trusted_authorities: S.optional(
|
|
153
|
+
S.array(S.object({ type: S.string({ minLength: 1, maxLength: 64 }), values: S.array(S.string({ minLength: 1, maxLength: 256 }), { minItems: 1, maxItems: 64 }) }, ["type", "values"]), { minItems: 1, maxItems: 8 }),
|
|
154
|
+
),
|
|
155
|
+
require_cryptographic_holder_binding: S.optional(S.boolean()),
|
|
156
|
+
claims: S.optional(S.array(DCQL_CLAIMS_QUERY_SCHEMA, { minItems: 1, maxItems: 32 })),
|
|
157
|
+
claim_sets: S.optional(S.array(S.array(DCQL_ID, { minItems: 1, maxItems: 32 }), { minItems: 1, maxItems: 16 })),
|
|
158
|
+
},
|
|
159
|
+
["id", "format"],
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
/* ── kash.rule@1.0.0 ───────────────────────────────────────────────── */
|
|
163
|
+
|
|
164
|
+
export const KASH_RULE_NSID = "kash.rule" as const;
|
|
165
|
+
export const KASH_RULE_ID = "kash.rule@1.0.0" as const;
|
|
166
|
+
|
|
167
|
+
export interface RuleParameter {
|
|
168
|
+
readonly type: DoorInputType;
|
|
169
|
+
/** Public rules: the threshold in clear. */
|
|
170
|
+
readonly value?: string | number | boolean;
|
|
171
|
+
/** Private rules: SHA-256 hex of JCS({name, value, salt}); the desk holds the opening. */
|
|
172
|
+
readonly sealed?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface RuleReason {
|
|
176
|
+
readonly code: string;
|
|
177
|
+
readonly outcome: DecisionOutcome;
|
|
178
|
+
readonly text: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface CriterionBasis {
|
|
182
|
+
readonly title: string;
|
|
183
|
+
readonly url?: string;
|
|
184
|
+
readonly clause?: string;
|
|
185
|
+
readonly page?: number;
|
|
186
|
+
readonly quote?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export type CriterionAnswer =
|
|
190
|
+
| {
|
|
191
|
+
readonly by: "receipts";
|
|
192
|
+
/** DCQL credential queries, verbatim. Their ids are `<criterion>` or `<criterion>_<suffix>`. */
|
|
193
|
+
readonly query: readonly DcqlCredentialQuery[];
|
|
194
|
+
/** Which of them together answer the criterion (DCQL `credential_sets.options`). Default: each alone. */
|
|
195
|
+
readonly options?: readonly (readonly string[])[];
|
|
196
|
+
/** Display names for issuer DIDs and trust lists the queries name. */
|
|
197
|
+
readonly names?: Readonly<Record<string, string>>;
|
|
198
|
+
}
|
|
199
|
+
| {
|
|
200
|
+
readonly by: "own-word";
|
|
201
|
+
/** What the person states, in plain words. */
|
|
202
|
+
readonly statement: string;
|
|
203
|
+
/** The values the person gives, by name and DMN type. */
|
|
204
|
+
readonly fields: Readonly<Record<string, DoorInputType>>;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export interface Criterion {
|
|
208
|
+
readonly id: string;
|
|
209
|
+
readonly label: string;
|
|
210
|
+
/** Why the door needs it. Required: P4's second rule — a rule asks only for what its purpose needs. */
|
|
211
|
+
readonly purpose: string;
|
|
212
|
+
readonly answer: CriterionAnswer;
|
|
213
|
+
/** An issuer's predicate (attest's predicate credential, `kind` = this criterion, `rule` = this rule) answers the row instead. */
|
|
214
|
+
readonly predicate?: { readonly issuers: readonly { readonly id: string; readonly name: string }[]; readonly freshDays: number };
|
|
215
|
+
/** A FEEL expression (the DMN subset) over what the query returns, with a DMN type for every claim it reads. */
|
|
216
|
+
readonly condition?: { readonly feel: string; readonly types: Readonly<Record<string, DoorInputType>> };
|
|
217
|
+
/** The clause it comes from, when a rule is drawn from a written policy. */
|
|
218
|
+
readonly basis?: CriterionBasis;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface RuleBody {
|
|
222
|
+
/** `did:kash:<author>/rule/<slug>` — the publisher's DID plus a path, as d60 identifies places. */
|
|
223
|
+
readonly id: string;
|
|
224
|
+
readonly version: number;
|
|
225
|
+
/** The organisation that publishes it and answers for it. Never a person. */
|
|
226
|
+
readonly author: string;
|
|
227
|
+
readonly title: string;
|
|
228
|
+
readonly purpose: string;
|
|
229
|
+
readonly visibility: "public" | "private";
|
|
230
|
+
readonly parameters?: Readonly<Record<string, RuleParameter>>;
|
|
231
|
+
/** d43: every rule renews and ends. */
|
|
232
|
+
readonly dates: { readonly published: string; readonly renewBy: string; readonly endsAt: string };
|
|
233
|
+
/** Who decides: a role, never a named person. */
|
|
234
|
+
readonly decider: { readonly role: string };
|
|
235
|
+
readonly outcome: {
|
|
236
|
+
/** What a yes grants, in plain words. */
|
|
237
|
+
readonly grants: string;
|
|
238
|
+
/** It binds the person — credit, a contract, a debt (P3): never automatic. */
|
|
239
|
+
readonly binds: boolean;
|
|
240
|
+
/** It touches sensitive data (GDPR art. 9): never automatic. */
|
|
241
|
+
readonly sensitive: boolean;
|
|
242
|
+
/** The author's standing decision: a yes is given when every criterion is met. Refused with `binds` or `sensitive`. */
|
|
243
|
+
readonly automaticYes: boolean;
|
|
244
|
+
};
|
|
245
|
+
readonly reasons: readonly RuleReason[];
|
|
246
|
+
readonly contest: { readonly windowDays: number; readonly answerDays: number; readonly how: string };
|
|
247
|
+
/** P4's first rule: another way to show the same thing. Paper is the floor. */
|
|
248
|
+
readonly anotherWay: string;
|
|
249
|
+
readonly criteria: readonly Criterion[];
|
|
250
|
+
/** The author's written answers to the warnings validation raised. Published with the rule. */
|
|
251
|
+
readonly answers?: readonly { readonly criterion: string; readonly code: string; readonly text: string }[];
|
|
252
|
+
/** Practice rules say so inside what is signed. */
|
|
253
|
+
readonly practice?: boolean;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const OWN_WORD_ANSWER = S.object(
|
|
257
|
+
{ by: S.literal("own-word"), statement: TEXT(280), fields: S.record(INPUT_TYPE, S.string({ pattern: /^[A-Za-z_][A-Za-z0-9_]{0,63}$/ })) },
|
|
258
|
+
["by", "statement", "fields"],
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const RECEIPTS_ANSWER = S.object(
|
|
262
|
+
{
|
|
263
|
+
by: S.literal("receipts"),
|
|
264
|
+
query: S.array(DCQL_CREDENTIAL_QUERY_SCHEMA, { minItems: 1, maxItems: 8 }),
|
|
265
|
+
options: S.optional(S.array(S.array(DCQL_ID, { minItems: 1, maxItems: 8 }), { minItems: 1, maxItems: 8 })),
|
|
266
|
+
names: S.optional(S.record(TEXT(120))),
|
|
267
|
+
},
|
|
268
|
+
["by", "query"],
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
export const CRITERION_SCHEMA: ObjectSchema = S.object(
|
|
272
|
+
{
|
|
273
|
+
id: SLUG,
|
|
274
|
+
label: TEXT(160),
|
|
275
|
+
purpose: TEXT(600),
|
|
276
|
+
answer: S.union(RECEIPTS_ANSWER, OWN_WORD_ANSWER),
|
|
277
|
+
predicate: S.optional(
|
|
278
|
+
S.object(
|
|
279
|
+
{
|
|
280
|
+
issuers: S.array(S.object({ id: DID, name: TEXT(120) }, ["id", "name"]), { minItems: 1, maxItems: 16 }),
|
|
281
|
+
freshDays: S.number({ integer: true, min: 1, max: 366 }),
|
|
282
|
+
},
|
|
283
|
+
["issuers", "freshDays"],
|
|
284
|
+
),
|
|
285
|
+
),
|
|
286
|
+
condition: S.optional(
|
|
287
|
+
S.object(
|
|
288
|
+
{ feel: S.string({ minLength: 1, maxLength: 2048 }), types: S.record(INPUT_TYPE, S.string({ pattern: /^[A-Za-z_][A-Za-z0-9_]{0,63}$/ })) },
|
|
289
|
+
["feel", "types"],
|
|
290
|
+
),
|
|
291
|
+
),
|
|
292
|
+
basis: S.optional(
|
|
293
|
+
S.object(
|
|
294
|
+
{ title: TEXT(200), url: S.optional(S.string({ format: "url" })), clause: S.optional(TEXT(120)), page: S.optional(S.number({ integer: true, min: 1 })), quote: S.optional(TEXT(1000)) },
|
|
295
|
+
["title"],
|
|
296
|
+
),
|
|
297
|
+
),
|
|
298
|
+
},
|
|
299
|
+
["id", "label", "purpose", "answer"],
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
export const KASH_RULE_BODY_SCHEMA: ObjectSchema = S.object(
|
|
303
|
+
{
|
|
304
|
+
id: S.string({ pattern: /^did:kash:[1-9A-HJ-NP-Za-km-z]{32,48}\/rule\/[a-z0-9][a-z0-9-]{0,62}$/ }),
|
|
305
|
+
version: S.number({ integer: true, min: 1 }),
|
|
306
|
+
author: DID,
|
|
307
|
+
title: TEXT(160),
|
|
308
|
+
purpose: TEXT(1000),
|
|
309
|
+
visibility: S.enum("public", "private"),
|
|
310
|
+
parameters: S.optional(
|
|
311
|
+
S.record(
|
|
312
|
+
S.object({ type: INPUT_TYPE, value: S.optional(S.union(S.string({ maxLength: 256 }), S.number(), S.boolean())), sealed: S.optional(HEX64) }, ["type"]),
|
|
313
|
+
S.string({ pattern: /^[A-Za-z_][A-Za-z0-9_]{0,63}$/ }),
|
|
314
|
+
),
|
|
315
|
+
),
|
|
316
|
+
dates: S.object({ published: ISO, renewBy: ISO, endsAt: ISO }, ["published", "renewBy", "endsAt"]),
|
|
317
|
+
decider: S.object({ role: TEXT(64) }, ["role"]),
|
|
318
|
+
outcome: S.object({ grants: TEXT(280), binds: S.boolean(), sensitive: S.boolean(), automaticYes: S.boolean() }, ["grants", "binds", "sensitive", "automaticYes"]),
|
|
319
|
+
reasons: S.array(S.object({ code: SLUG, outcome: S.enum(...DECISION_OUTCOMES), text: TEXT(400) }, ["code", "outcome", "text"]), { minItems: 2, maxItems: 32 }),
|
|
320
|
+
contest: S.object(
|
|
321
|
+
{ windowDays: S.number({ integer: true, min: 1, max: 366 }), answerDays: S.number({ integer: true, min: 1, max: 366 }), how: TEXT(400) },
|
|
322
|
+
["windowDays", "answerDays", "how"],
|
|
323
|
+
),
|
|
324
|
+
anotherWay: TEXT(600),
|
|
325
|
+
criteria: S.array(CRITERION_SCHEMA, { minItems: 1, maxItems: 32 }),
|
|
326
|
+
answers: S.optional(S.array(S.object({ criterion: SLUG, code: S.string({ pattern: /^[A-Z][A-Z_]{1,63}$/ }), text: TEXT(1000) }, ["criterion", "code", "text"]), { maxItems: 64 })),
|
|
327
|
+
practice: S.optional(S.boolean()),
|
|
328
|
+
},
|
|
329
|
+
["id", "version", "author", "title", "purpose", "visibility", "dates", "decider", "outcome", "reasons", "contest", "anotherWay", "criteria"],
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
export const KASH_RULE_LEXICON: LexiconDef = {
|
|
333
|
+
id: KASH_RULE_ID,
|
|
334
|
+
nsid: KASH_RULE_NSID,
|
|
335
|
+
semver: { major: 1, minor: 0, patch: 0 },
|
|
336
|
+
bodySchema: KASH_RULE_BODY_SCHEMA,
|
|
337
|
+
title: "A door's rule",
|
|
338
|
+
description:
|
|
339
|
+
"What a door decides, why, who decides, the reasons it may give, its contest window, renewal and end dates, and its criteria — each an OpenID4VP 1.0 DCQL query plus a FEEL condition. Public, organisation-authored, signed by its author.",
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/* ── kash.rule@1.1.0 — the address, subjects, bounds, drafts ───────── */
|
|
343
|
+
|
|
344
|
+
export const KASH_RULE_1_1_ID = "kash.rule@1.1.0" as const;
|
|
345
|
+
|
|
346
|
+
/** What a criterion is about. d85(b) rule 1 binds when the subject is a person (or an organisation's people). */
|
|
347
|
+
export const SUBJECTS = ["person", "organisation", "product", "place"] as const;
|
|
348
|
+
export type Subject = (typeof SUBJECTS)[number];
|
|
349
|
+
|
|
350
|
+
export const ANCHOR_FAMILIES = ["movement", "knowledge", "work", "community"] as const;
|
|
351
|
+
export const AUTHORITY_KINDS = ["floor", "law", "standard", "institution", "community"] as const;
|
|
352
|
+
export const JOURNEYS = ["move", "learn", "earn-and-spend", "live", "leisure"] as const;
|
|
353
|
+
export const GIVENS = ["area", "spaces", "capitals", "culture", "economy", "time", "relations"] as const;
|
|
354
|
+
export type Given = (typeof GIVENS)[number];
|
|
355
|
+
export const SPACE_KINDS = ["premises", "workplace", "campus", "site", "vehicle", "home", "public-space", "online"] as const;
|
|
356
|
+
export const CAPITAL_KINDS = ["people", "systems", "resources"] as const;
|
|
357
|
+
export const ECONOMY_LEVELS = ["primary", "secondary", "tertiary", "quaternary", "quinary", "uncounted"] as const;
|
|
358
|
+
|
|
359
|
+
/** Bounds an adopter may set a parameter within: a range, or a list. */
|
|
360
|
+
export type ParameterBounds =
|
|
361
|
+
| { readonly min?: string | number; readonly max?: string | number }
|
|
362
|
+
| { readonly allowed: readonly (string | number | boolean)[] };
|
|
363
|
+
|
|
364
|
+
export interface RuleParameter1_1 extends RuleParameter {
|
|
365
|
+
readonly bounds?: ParameterBounds;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export interface Criterion1_1 extends Criterion {
|
|
369
|
+
readonly subject?: Subject;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export interface RuleRef {
|
|
373
|
+
readonly rule: string;
|
|
374
|
+
readonly version?: number;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export interface Givens {
|
|
378
|
+
readonly area?: { readonly text: string; readonly anywhere?: boolean; readonly countries?: readonly string[]; readonly places?: readonly string[] };
|
|
379
|
+
readonly spaces?: { readonly text: string; readonly kinds?: readonly (typeof SPACE_KINDS)[number][]; readonly places?: readonly string[] };
|
|
380
|
+
readonly capitals?: { readonly text: string; readonly needs?: readonly { readonly kind: (typeof CAPITAL_KINDS)[number]; readonly what: string }[] };
|
|
381
|
+
readonly culture?: { readonly text: string; readonly codes?: readonly { readonly title: string; readonly url?: string }[]; readonly languages?: readonly string[] };
|
|
382
|
+
readonly economy?: { readonly text: string; readonly level?: (typeof ECONOMY_LEVELS)[number] };
|
|
383
|
+
readonly time?: { readonly text: string; readonly lasts?: string; readonly renews?: string };
|
|
384
|
+
readonly relations?: { readonly text: string; readonly recognises?: readonly RuleRef[]; readonly within?: readonly { readonly id: string }[] };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Every rule has an address (d85(c)). */
|
|
388
|
+
export interface RuleAddress {
|
|
389
|
+
/** ISIC Revision 5: a section (A–V) or a two-digit division. */
|
|
390
|
+
readonly sector: { readonly classification: "ISIC"; readonly revision: "5"; readonly code: string };
|
|
391
|
+
readonly anchorFamily: (typeof ANCHOR_FAMILIES)[number];
|
|
392
|
+
readonly authority: { readonly kind: (typeof AUTHORITY_KINDS)[number]; readonly name?: string; readonly url?: string };
|
|
393
|
+
readonly journey: (typeof JOURNEYS)[number];
|
|
394
|
+
/** The seven givens. A rule is complete when all seven are answered. */
|
|
395
|
+
readonly givens: Givens;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export interface RuleBody1_1 extends Omit<RuleBody, "parameters" | "criteria"> {
|
|
399
|
+
readonly parameters?: Readonly<Record<string, RuleParameter1_1>>;
|
|
400
|
+
readonly criteria: readonly Criterion1_1[];
|
|
401
|
+
readonly address?: RuleAddress;
|
|
402
|
+
/** Published for reading and comment, never decided under. An incomplete rule publishes only as a draft. */
|
|
403
|
+
readonly draft?: boolean;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const VALUE = S.union(S.string({ maxLength: 256 }), S.number(), S.boolean());
|
|
407
|
+
const PLACE_REF = S.string({ pattern: /^did:kash:[1-9A-HJ-NP-Za-km-z]{32,48}\/place\/[a-z0-9][a-z0-9-]{0,62}$/ });
|
|
408
|
+
const DURATION = S.string({ pattern: /^P(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$/ });
|
|
409
|
+
const RULE_ID = KASH_RULE_BODY_SCHEMA.properties.id!;
|
|
410
|
+
const GIVEN_TEXT = TEXT(600);
|
|
411
|
+
|
|
412
|
+
const PARAMETER_1_1 = S.object(
|
|
413
|
+
{
|
|
414
|
+
type: INPUT_TYPE,
|
|
415
|
+
value: S.optional(VALUE),
|
|
416
|
+
sealed: S.optional(HEX64),
|
|
417
|
+
bounds: S.optional(
|
|
418
|
+
S.union(
|
|
419
|
+
S.object({ min: S.optional(S.union(S.string({ maxLength: 64 }), S.number())), max: S.optional(S.union(S.string({ maxLength: 64 }), S.number())) }),
|
|
420
|
+
S.object({ allowed: S.array(VALUE, { minItems: 1, maxItems: 64 }) }, ["allowed"]),
|
|
421
|
+
),
|
|
422
|
+
),
|
|
423
|
+
},
|
|
424
|
+
["type"],
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const GIVENS_SCHEMA = S.object({
|
|
428
|
+
area: S.optional(
|
|
429
|
+
S.object(
|
|
430
|
+
{ text: GIVEN_TEXT, anywhere: S.optional(S.boolean()), countries: S.optional(S.array(S.string({ pattern: /^[A-Z]{2}$/ }), { minItems: 1, maxItems: 64 })), places: S.optional(S.array(PLACE_REF, { minItems: 1, maxItems: 64 })) },
|
|
431
|
+
["text"],
|
|
432
|
+
),
|
|
433
|
+
),
|
|
434
|
+
spaces: S.optional(S.object({ text: GIVEN_TEXT, kinds: S.optional(S.array(S.enum(...SPACE_KINDS), { minItems: 1, maxItems: 8 })), places: S.optional(S.array(PLACE_REF, { minItems: 1, maxItems: 64 })) }, ["text"])),
|
|
435
|
+
capitals: S.optional(S.object({ text: GIVEN_TEXT, needs: S.optional(S.array(S.object({ kind: S.enum(...CAPITAL_KINDS), what: TEXT(200) }, ["kind", "what"]), { minItems: 1, maxItems: 16 })) }, ["text"])),
|
|
436
|
+
culture: S.optional(
|
|
437
|
+
S.object(
|
|
438
|
+
{ text: GIVEN_TEXT, codes: S.optional(S.array(S.object({ title: TEXT(200), url: S.optional(S.string({ format: "url" })) }, ["title"]), { minItems: 1, maxItems: 16 })), languages: S.optional(S.array(S.string({ format: "bcp47" }), { minItems: 1, maxItems: 16 })) },
|
|
439
|
+
["text"],
|
|
440
|
+
),
|
|
441
|
+
),
|
|
442
|
+
economy: S.optional(S.object({ text: GIVEN_TEXT, level: S.optional(S.enum(...ECONOMY_LEVELS)) }, ["text"])),
|
|
443
|
+
time: S.optional(S.object({ text: GIVEN_TEXT, lasts: S.optional(DURATION), renews: S.optional(DURATION) }, ["text"])),
|
|
444
|
+
relations: S.optional(
|
|
445
|
+
S.object(
|
|
446
|
+
{ text: GIVEN_TEXT, recognises: S.optional(S.array(S.object({ rule: RULE_ID, version: S.optional(S.number({ integer: true, min: 1 })) }, ["rule"]), { minItems: 1, maxItems: 32 })), within: S.optional(S.array(S.object({ id: S.string({ minLength: 1, maxLength: 256 }) }, ["id"]), { minItems: 1, maxItems: 16 })) },
|
|
447
|
+
["text"],
|
|
448
|
+
),
|
|
449
|
+
),
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
export const RULE_ADDRESS_SCHEMA: ObjectSchema = S.object(
|
|
453
|
+
{
|
|
454
|
+
sector: S.object({ classification: S.literal("ISIC"), revision: S.literal("5"), code: S.string({ pattern: /^([A-V]|\d{2})$/ }) }, ["classification", "revision", "code"]),
|
|
455
|
+
anchorFamily: S.enum(...ANCHOR_FAMILIES),
|
|
456
|
+
authority: S.object({ kind: S.enum(...AUTHORITY_KINDS), name: S.optional(TEXT(120)), url: S.optional(S.string({ format: "url" })) }, ["kind"]),
|
|
457
|
+
journey: S.enum(...JOURNEYS),
|
|
458
|
+
givens: GIVENS_SCHEMA,
|
|
459
|
+
},
|
|
460
|
+
["sector", "anchorFamily", "authority", "journey", "givens"],
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
export const CRITERION_1_1_SCHEMA: ObjectSchema = S.object({ ...CRITERION_SCHEMA.properties, subject: S.optional(S.enum(...SUBJECTS)) }, CRITERION_SCHEMA.required);
|
|
464
|
+
|
|
465
|
+
export const KASH_RULE_1_1_BODY_SCHEMA: ObjectSchema = S.object(
|
|
466
|
+
{
|
|
467
|
+
...KASH_RULE_BODY_SCHEMA.properties,
|
|
468
|
+
parameters: S.optional(S.record(PARAMETER_1_1, S.string({ pattern: /^[A-Za-z_][A-Za-z0-9_]{0,63}$/ }))),
|
|
469
|
+
criteria: S.array(CRITERION_1_1_SCHEMA, { minItems: 1, maxItems: 32 }),
|
|
470
|
+
address: S.optional(RULE_ADDRESS_SCHEMA),
|
|
471
|
+
draft: S.optional(S.boolean()),
|
|
472
|
+
},
|
|
473
|
+
KASH_RULE_BODY_SCHEMA.required,
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
export const KASH_RULE_1_1_LEXICON: LexiconDef = {
|
|
477
|
+
id: KASH_RULE_1_1_ID,
|
|
478
|
+
nsid: KASH_RULE_NSID,
|
|
479
|
+
semver: { major: 1, minor: 1, patch: 0 },
|
|
480
|
+
bodySchema: KASH_RULE_1_1_BODY_SCHEMA,
|
|
481
|
+
title: "A door's rule, with its address",
|
|
482
|
+
description:
|
|
483
|
+
"kash.rule@1.0.0 plus the address (ISIC Rev. 5 sector, anchor family, authority, journey, the seven givens), each criterion's subject, parameter bounds for adopters, and draft. A 1.0.0 body is a 1.1.0 body.",
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
/* ── kash.rule@1.2.0 — the countersigner as holder ──────────────────── */
|
|
487
|
+
|
|
488
|
+
export const KASH_RULE_1_2_ID = "kash.rule@1.2.0" as const;
|
|
489
|
+
|
|
490
|
+
/** The DCQL credential query of 1.2.0: 1.1.0's, with `meta.holder`. */
|
|
491
|
+
export const DCQL_CREDENTIAL_QUERY_1_2_SCHEMA: ObjectSchema = S.object(
|
|
492
|
+
{
|
|
493
|
+
...DCQL_CREDENTIAL_QUERY_SCHEMA.properties,
|
|
494
|
+
meta: S.optional(S.object({ ...DCQL_META_PROPERTIES, holder: S.optional(S.enum("countersigner")) })),
|
|
495
|
+
},
|
|
496
|
+
DCQL_CREDENTIAL_QUERY_SCHEMA.required,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
const RECEIPTS_ANSWER_1_2 = S.object({ ...RECEIPTS_ANSWER.properties, query: S.array(DCQL_CREDENTIAL_QUERY_1_2_SCHEMA, { minItems: 1, maxItems: 8 }) }, RECEIPTS_ANSWER.required);
|
|
500
|
+
|
|
501
|
+
export const CRITERION_1_2_SCHEMA: ObjectSchema = S.object({ ...CRITERION_1_1_SCHEMA.properties, answer: S.union(RECEIPTS_ANSWER_1_2, OWN_WORD_ANSWER) }, CRITERION_1_1_SCHEMA.required);
|
|
502
|
+
|
|
503
|
+
export const KASH_RULE_1_2_BODY_SCHEMA: ObjectSchema = S.object(
|
|
504
|
+
{ ...KASH_RULE_1_1_BODY_SCHEMA.properties, criteria: S.array(CRITERION_1_2_SCHEMA, { minItems: 1, maxItems: 32 }) },
|
|
505
|
+
KASH_RULE_1_1_BODY_SCHEMA.required,
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
export const KASH_RULE_1_2_LEXICON: LexiconDef = {
|
|
509
|
+
id: KASH_RULE_1_2_ID,
|
|
510
|
+
nsid: KASH_RULE_NSID,
|
|
511
|
+
semver: { major: 1, minor: 2, patch: 0 },
|
|
512
|
+
bodySchema: KASH_RULE_1_2_BODY_SCHEMA,
|
|
513
|
+
title: "A door's rule, with its address and the countersigner as holder",
|
|
514
|
+
description:
|
|
515
|
+
"kash.rule@1.1.0 plus meta.holder on a kash-artifact query: \"countersigner\" binds the presentation to the one key whose acceptance in the countersign bundle verifies. A 1.1.0 body is a 1.2.0 body.",
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
/* ── kash.adoption@1.0.0 ───────────────────────────────────────────── */
|
|
519
|
+
|
|
520
|
+
export const KASH_ADOPTION_NSID = "kash.adoption" as const;
|
|
521
|
+
export const KASH_ADOPTION_ID = "kash.adoption@1.0.0" as const;
|
|
522
|
+
|
|
523
|
+
export interface AdoptionBody {
|
|
524
|
+
/** `did:kash:<anchor>/adoption/<slug>`. */
|
|
525
|
+
readonly id: string;
|
|
526
|
+
/** The adopting organisation — the anchor whose door decides under it. */
|
|
527
|
+
readonly anchor: string;
|
|
528
|
+
readonly rule: { readonly id: string; readonly version: number; readonly hash: string };
|
|
529
|
+
/** Local values for the parameters the rule bounds — nothing else. */
|
|
530
|
+
readonly parameters: Readonly<Record<string, string | number | boolean>>;
|
|
531
|
+
/** It renews and ends, no later than the rule (d43). */
|
|
532
|
+
readonly dates: { readonly adopted: string; readonly renewBy: string; readonly endsAt: string };
|
|
533
|
+
readonly practice?: boolean;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export const KASH_ADOPTION_BODY_SCHEMA: ObjectSchema = S.object(
|
|
537
|
+
{
|
|
538
|
+
id: S.string({ pattern: /^did:kash:[1-9A-HJ-NP-Za-km-z]{32,48}\/adoption\/[a-z0-9][a-z0-9-]{0,62}$/ }),
|
|
539
|
+
anchor: DID,
|
|
540
|
+
rule: S.object({ id: RULE_ID, version: S.number({ integer: true, min: 1 }), hash: HEX64 }, ["id", "version", "hash"]),
|
|
541
|
+
parameters: S.record(VALUE, S.string({ pattern: /^[A-Za-z_][A-Za-z0-9_]{0,63}$/ })),
|
|
542
|
+
dates: S.object({ adopted: ISO, renewBy: ISO, endsAt: ISO }, ["adopted", "renewBy", "endsAt"]),
|
|
543
|
+
practice: S.optional(S.boolean()),
|
|
544
|
+
},
|
|
545
|
+
["id", "anchor", "rule", "parameters", "dates"],
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
export const KASH_ADOPTION_LEXICON: LexiconDef = {
|
|
549
|
+
id: KASH_ADOPTION_ID,
|
|
550
|
+
nsid: KASH_ADOPTION_NSID,
|
|
551
|
+
semver: { major: 1, minor: 0, patch: 0 },
|
|
552
|
+
bodySchema: KASH_ADOPTION_BODY_SCHEMA,
|
|
553
|
+
title: "An anchor's adoption of a rule",
|
|
554
|
+
description: "An organisation adopts one version of a rule with its own values for the parameters the rule bounds. Public, signed by the anchor.",
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
/* ── bundles ───────────────────────────────────────────────────────── */
|
|
558
|
+
|
|
559
|
+
/** Every door lexicon: the rule at 1.0.0, 1.1.0 and 1.2.0, and the adoption. All public and organisation-authored (d59). */
|
|
560
|
+
export const DOOR_LEXICONS: readonly LexiconDef[] = [KASH_RULE_LEXICON, KASH_RULE_1_1_LEXICON, KASH_RULE_1_2_LEXICON, KASH_ADOPTION_LEXICON];
|