@openvtc/trust-tasks 0.19.1 → 0.19.3

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/dist/_shared/components.d.ts +4 -0
  3. package/dist/_shared/components.d.ts.map +1 -1
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/keys/create/0.1/payload.d.ts +20 -0
  9. package/dist/keys/create/0.1/payload.d.ts.map +1 -1
  10. package/dist/keys/create/0.1/payload.js +10 -0
  11. package/dist/keys/create/0.1/payload.js.map +1 -1
  12. package/dist/keys/export-secret/0.1/payload.d.ts +309 -0
  13. package/dist/keys/export-secret/0.1/payload.d.ts.map +1 -0
  14. package/dist/keys/export-secret/0.1/payload.js +181 -0
  15. package/dist/keys/export-secret/0.1/payload.js.map +1 -0
  16. package/dist/keys/import/0.1/payload.d.ts +20 -0
  17. package/dist/keys/import/0.1/payload.d.ts.map +1 -1
  18. package/dist/keys/import/0.1/payload.js +10 -0
  19. package/dist/keys/import/0.1/payload.js.map +1 -1
  20. package/dist/keys/list/0.1/payload.d.ts +20 -0
  21. package/dist/keys/list/0.1/payload.d.ts.map +1 -1
  22. package/dist/keys/list/0.1/payload.js +10 -0
  23. package/dist/keys/list/0.1/payload.js.map +1 -1
  24. package/dist/keys/set-exportability/0.1/payload.d.ts +560 -0
  25. package/dist/keys/set-exportability/0.1/payload.d.ts.map +1 -0
  26. package/dist/keys/set-exportability/0.1/payload.js +330 -0
  27. package/dist/keys/set-exportability/0.1/payload.js.map +1 -0
  28. package/dist/keys/show/0.1/payload.d.ts +20 -0
  29. package/dist/keys/show/0.1/payload.d.ts.map +1 -1
  30. package/dist/keys/show/0.1/payload.js +10 -0
  31. package/dist/keys/show/0.1/payload.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/_shared/components.ts +4 -0
  34. package/src/index.ts +2 -0
  35. package/src/keys/create/0.1/payload.ts +10 -0
  36. package/src/keys/export-secret/0.1/payload.ts +227 -0
  37. package/src/keys/import/0.1/payload.ts +10 -0
  38. package/src/keys/list/0.1/payload.ts +10 -0
  39. package/src/keys/set-exportability/0.1/payload.ts +371 -0
  40. package/src/keys/show/0.1/payload.ts +10 -0
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Generated by scripts/build-ts-bindings.mjs — DO NOT EDIT BY HAND.
3
+ * Source: specs/keys/export-secret/0.1/payload.schema.json
4
+ */
5
+
6
+ import type { Ext, KeyType } from "../../../_shared/components.js";
7
+
8
+
9
+ /**
10
+ * Names one key whose private half is being asked for. The outer document members (id, type, issuer, recipient, issuedAt, expiresAt, proof) are owned by the framework — SPEC §6.3.
11
+ */
12
+ export interface KeysExportSecretPayload {
13
+ /**
14
+ * The key to release. Custodian-scoped, as everywhere in `keys/*`. One key per request: there is deliberately no list form, so that each release is one decision the custodian can refuse, record and rate-limit on its own terms.
15
+ */
16
+ keyId: string;
17
+ ext?: Ext;
18
+ }
19
+ /**
20
+ * The key material. Failures use trust-task-error, not this shape. Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/export-secret/0.1#response.
21
+ */
22
+ export interface KeysExportSecretResponsePayload {
23
+ /**
24
+ * The key released, echoed so a consumer can confirm it received the one it named rather than matching on request order.
25
+ */
26
+ keyId: string;
27
+ keyType: KeyType;
28
+ /**
29
+ * The public half, multibase-encoded. Returned alongside the private half so a consumer can check the pair agrees before installing it, rather than deriving the public half and hoping.
30
+ */
31
+ publicKeyMultibase: string;
32
+ /**
33
+ * The private half, multibase-encoded (Base58BTC) over its multicodec-prefixed bytes. The prefix identifies the key material; a consumer decodes by prefix rather than trusting `keyType` alone.
34
+ */
35
+ privateKeyMultibase: string;
36
+ }
37
+
38
+ /** Shared definitions this specification references, re-exported under the names it used to declare them with. */
39
+ export type { Ext, KeyType };
40
+
41
+ /** Trust Task type URI. */
42
+ export const TYPE_URI = "https://trusttasks.org/spec/keys/export-secret/0.1" as const;
43
+
44
+ /** Stable alias for this specification's request payload shape. */
45
+ export type Payload = KeysExportSecretPayload;
46
+
47
+ /** Trust Task response type URI (request type URI + "#response"). */
48
+ export const RESPONSE_TYPE_URI = "https://trusttasks.org/spec/keys/export-secret/0.1#response" as const;
49
+
50
+ /** Stable alias for this specification's success-response payload shape. */
51
+ export type Response = KeysExportSecretResponsePayload;
52
+
53
+ /**
54
+ * This specification's payload schema, as a value.
55
+ *
56
+ * SPEC.md §7.2 item 2 is performed against this. It is shipped as data
57
+ * rather than only as a `.json` file because TypeScript types are erased
58
+ * at runtime: without a schema a consumer has nothing to validate, and
59
+ * every REQUIRED payload member is optional in practice. Cross-file
60
+ * `$ref`s are already inlined, so it needs no resolver.
61
+ */
62
+ export const PAYLOAD_SCHEMA = {
63
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
64
+ "$id": "https://trusttasks.org/spec/keys/export-secret/0.1",
65
+ "title": "Keys Export Secret — payload",
66
+ "description": "Names one key whose private half is being asked for. The outer document members (id, type, issuer, recipient, issuedAt, expiresAt, proof) are owned by the framework — SPEC §6.3.",
67
+ "type": "object",
68
+ "additionalProperties": false,
69
+ "required": [
70
+ "keyId"
71
+ ],
72
+ "properties": {
73
+ "keyId": {
74
+ "type": "string",
75
+ "minLength": 1,
76
+ "description": "The key to release. Custodian-scoped, as everywhere in `keys/*`. One key per request: there is deliberately no list form, so that each release is one decision the custodian can refuse, record and rate-limit on its own terms."
77
+ },
78
+ "ext": {
79
+ "$ref": "#/$defs/Ext"
80
+ }
81
+ },
82
+ "$defs": {
83
+ "Response": {
84
+ "$anchor": "response",
85
+ "title": "Keys Export Secret — response payload",
86
+ "type": "object",
87
+ "additionalProperties": false,
88
+ "required": [
89
+ "keyId",
90
+ "keyType",
91
+ "publicKeyMultibase",
92
+ "privateKeyMultibase"
93
+ ],
94
+ "description": "The key material. Failures use trust-task-error, not this shape. Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/export-secret/0.1#response.",
95
+ "properties": {
96
+ "keyId": {
97
+ "type": "string",
98
+ "minLength": 1,
99
+ "description": "The key released, echoed so a consumer can confirm it received the one it named rather than matching on request order."
100
+ },
101
+ "keyType": {
102
+ "$ref": "#/$defs/KeyType"
103
+ },
104
+ "publicKeyMultibase": {
105
+ "type": "string",
106
+ "minLength": 1,
107
+ "description": "The public half, multibase-encoded. Returned alongside the private half so a consumer can check the pair agrees before installing it, rather than deriving the public half and hoping."
108
+ },
109
+ "privateKeyMultibase": {
110
+ "type": "string",
111
+ "minLength": 1,
112
+ "description": "The private half, multibase-encoded (Base58BTC) over its multicodec-prefixed bytes. The prefix identifies the key material; a consumer decodes by prefix rather than trusting `keyType` alone."
113
+ }
114
+ }
115
+ },
116
+ "KeyType": {
117
+ "title": "KeyType",
118
+ "type": "string",
119
+ "enum": [
120
+ "ed25519",
121
+ "x25519",
122
+ "p256"
123
+ ],
124
+ "description": "Cryptographic algorithm the key material belongs to. `ed25519` signs (EdDSA), `x25519` performs key agreement and never signs, `p256` signs (ES256)."
125
+ },
126
+ "Ext": {
127
+ "title": "Ext",
128
+ "description": "Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.",
129
+ "type": "object",
130
+ "minProperties": 1,
131
+ "additionalProperties": true,
132
+ "propertyNames": {
133
+ "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
134
+ }
135
+ }
136
+ }
137
+ } as const;
138
+
139
+ /** As {@link PAYLOAD_SCHEMA}, for the success-response variant. */
140
+ export const RESPONSE_PAYLOAD_SCHEMA = {
141
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
142
+ "$ref": "#/$defs/Response",
143
+ "$defs": {
144
+ "Response": {
145
+ "$anchor": "response",
146
+ "title": "Keys Export Secret — response payload",
147
+ "type": "object",
148
+ "additionalProperties": false,
149
+ "required": [
150
+ "keyId",
151
+ "keyType",
152
+ "publicKeyMultibase",
153
+ "privateKeyMultibase"
154
+ ],
155
+ "description": "The key material. Failures use trust-task-error, not this shape. Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/export-secret/0.1#response.",
156
+ "properties": {
157
+ "keyId": {
158
+ "type": "string",
159
+ "minLength": 1,
160
+ "description": "The key released, echoed so a consumer can confirm it received the one it named rather than matching on request order."
161
+ },
162
+ "keyType": {
163
+ "$ref": "#/$defs/KeyType"
164
+ },
165
+ "publicKeyMultibase": {
166
+ "type": "string",
167
+ "minLength": 1,
168
+ "description": "The public half, multibase-encoded. Returned alongside the private half so a consumer can check the pair agrees before installing it, rather than deriving the public half and hoping."
169
+ },
170
+ "privateKeyMultibase": {
171
+ "type": "string",
172
+ "minLength": 1,
173
+ "description": "The private half, multibase-encoded (Base58BTC) over its multicodec-prefixed bytes. The prefix identifies the key material; a consumer decodes by prefix rather than trusting `keyType` alone."
174
+ }
175
+ }
176
+ },
177
+ "KeyType": {
178
+ "title": "KeyType",
179
+ "type": "string",
180
+ "enum": [
181
+ "ed25519",
182
+ "x25519",
183
+ "p256"
184
+ ],
185
+ "description": "Cryptographic algorithm the key material belongs to. `ed25519` signs (EdDSA), `x25519` performs key agreement and never signs, `p256` signs (ES256)."
186
+ },
187
+ "Ext": {
188
+ "title": "Ext",
189
+ "description": "Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.",
190
+ "type": "object",
191
+ "minProperties": 1,
192
+ "additionalProperties": true,
193
+ "propertyNames": {
194
+ "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
195
+ }
196
+ }
197
+ }
198
+ } as const;
199
+
200
+ /**
201
+ * SPEC.md §7.2 policy for the request variant, from this specification's
202
+ * front matter. Pass to `consumeInbound` — items 5b, 7 and 8 are
203
+ * per-specification and cannot be derived from the document alone, and
204
+ * item 2 needs the schema this carries.
205
+ */
206
+ export const SPEC = {
207
+ typeUri: TYPE_URI,
208
+ isBearer: false,
209
+ isProofRequired: true,
210
+ isRecipientRequired: true,
211
+ isIssuedAtRequired: true,
212
+ payloadSchema: PAYLOAD_SCHEMA,
213
+ } as const;
214
+
215
+ /**
216
+ * SPEC.md §7.2 policy for the success-response variant. `isRecipientRequired`
217
+ * tracks the *issuer* party's requirement because a response swaps the
218
+ * parties (§7.3 item 5).
219
+ */
220
+ export const RESPONSE_SPEC = {
221
+ typeUri: RESPONSE_TYPE_URI,
222
+ isBearer: false,
223
+ isProofRequired: true,
224
+ isRecipientRequired: true,
225
+ isIssuedAtRequired: true,
226
+ payloadSchema: RESPONSE_PAYLOAD_SCHEMA,
227
+ } as const;
@@ -210,6 +210,11 @@ export const PAYLOAD_SCHEMA = {
210
210
  "maxLength": 256,
211
211
  "description": "Optional human-readable label. Operator-facing only; carries no authorization meaning."
212
212
  },
213
+ "exportable": {
214
+ "type": "boolean",
215
+ "description": "Whether the private half may be released to a caller. `false` means the custodian refuses every export of this key and can only be asked to *use* it — signing, key agreement — so the material never leaves. **Absence means the key may be exported.** That is the permissive reading, and it is deliberate: it is what every record written before this member existed already meant, so a custodian adding the member cannot silently retract access to keys its callers already hold. A consumer MUST NOT infer the converse — absence is not a statement that export was considered and allowed. Marking a key non-exportable does not make it unrecoverable: a custodian's own whole-store backup is a different mechanism from an export to a caller, and this member does not speak to it.",
216
+ "$comment": "No JSON Schema `default` is declared here on purpose. A declared default is materialised by the generated bindings — the field becomes non-optional with a serde default — so an absent member reappears as an explicit `true` on re-serialisation, which breaks round-trip idempotence for every record written before this member existed. The permissive reading is stated in prose above, where a binding cannot act on it."
217
+ },
213
218
  "contextId": {
214
219
  "type": "string",
215
220
  "description": "Scope the key belongs to. **Absence is not 'every scope'** — a key with no context is reachable only by a caller with unrestricted authority over the maintainer, which is the more restrictive reading, and a consumer that treats absence as a wildcard inverts the guarantee."
@@ -341,6 +346,11 @@ export const RESPONSE_PAYLOAD_SCHEMA = {
341
346
  "maxLength": 256,
342
347
  "description": "Optional human-readable label. Operator-facing only; carries no authorization meaning."
343
348
  },
349
+ "exportable": {
350
+ "type": "boolean",
351
+ "description": "Whether the private half may be released to a caller. `false` means the custodian refuses every export of this key and can only be asked to *use* it — signing, key agreement — so the material never leaves. **Absence means the key may be exported.** That is the permissive reading, and it is deliberate: it is what every record written before this member existed already meant, so a custodian adding the member cannot silently retract access to keys its callers already hold. A consumer MUST NOT infer the converse — absence is not a statement that export was considered and allowed. Marking a key non-exportable does not make it unrecoverable: a custodian's own whole-store backup is a different mechanism from an export to a caller, and this member does not speak to it.",
352
+ "$comment": "No JSON Schema `default` is declared here on purpose. A declared default is materialised by the generated bindings — the field becomes non-optional with a serde default — so an absent member reappears as an explicit `true` on re-serialisation, which breaks round-trip idempotence for every record written before this member existed. The permissive reading is stated in prose above, where a binding cannot act on it."
353
+ },
344
354
  "contextId": {
345
355
  "type": "string",
346
356
  "description": "Scope the key belongs to. **Absence is not 'every scope'** — a key with no context is reachable only by a caller with unrestricted authority over the maintainer, which is the more restrictive reading, and a consumer that treats absence as a wildcard inverts the guarantee."
@@ -201,6 +201,11 @@ export const PAYLOAD_SCHEMA = {
201
201
  "maxLength": 256,
202
202
  "description": "Optional human-readable label. Operator-facing only; carries no authorization meaning."
203
203
  },
204
+ "exportable": {
205
+ "type": "boolean",
206
+ "description": "Whether the private half may be released to a caller. `false` means the custodian refuses every export of this key and can only be asked to *use* it — signing, key agreement — so the material never leaves. **Absence means the key may be exported.** That is the permissive reading, and it is deliberate: it is what every record written before this member existed already meant, so a custodian adding the member cannot silently retract access to keys its callers already hold. A consumer MUST NOT infer the converse — absence is not a statement that export was considered and allowed. Marking a key non-exportable does not make it unrecoverable: a custodian's own whole-store backup is a different mechanism from an export to a caller, and this member does not speak to it.",
207
+ "$comment": "No JSON Schema `default` is declared here on purpose. A declared default is materialised by the generated bindings — the field becomes non-optional with a serde default — so an absent member reappears as an explicit `true` on re-serialisation, which breaks round-trip idempotence for every record written before this member existed. The permissive reading is stated in prose above, where a binding cannot act on it."
208
+ },
204
209
  "contextId": {
205
210
  "type": "string",
206
211
  "description": "Scope the key belongs to. **Absence is not 'every scope'** — a key with no context is reachable only by a caller with unrestricted authority over the maintainer, which is the more restrictive reading, and a consumer that treats absence as a wildcard inverts the guarantee."
@@ -353,6 +358,11 @@ export const RESPONSE_PAYLOAD_SCHEMA = {
353
358
  "maxLength": 256,
354
359
  "description": "Optional human-readable label. Operator-facing only; carries no authorization meaning."
355
360
  },
361
+ "exportable": {
362
+ "type": "boolean",
363
+ "description": "Whether the private half may be released to a caller. `false` means the custodian refuses every export of this key and can only be asked to *use* it — signing, key agreement — so the material never leaves. **Absence means the key may be exported.** That is the permissive reading, and it is deliberate: it is what every record written before this member existed already meant, so a custodian adding the member cannot silently retract access to keys its callers already hold. A consumer MUST NOT infer the converse — absence is not a statement that export was considered and allowed. Marking a key non-exportable does not make it unrecoverable: a custodian's own whole-store backup is a different mechanism from an export to a caller, and this member does not speak to it.",
364
+ "$comment": "No JSON Schema `default` is declared here on purpose. A declared default is materialised by the generated bindings — the field becomes non-optional with a serde default — so an absent member reappears as an explicit `true` on re-serialisation, which breaks round-trip idempotence for every record written before this member existed. The permissive reading is stated in prose above, where a binding cannot act on it."
365
+ },
356
366
  "contextId": {
357
367
  "type": "string",
358
368
  "description": "Scope the key belongs to. **Absence is not 'every scope'** — a key with no context is reachable only by a caller with unrestricted authority over the maintainer, which is the more restrictive reading, and a consumer that treats absence as a wildcard inverts the guarantee."
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Generated by scripts/build-ts-bindings.mjs — DO NOT EDIT BY HAND.
3
+ * Source: specs/keys/set-exportability/0.1/payload.schema.json
4
+ */
5
+
6
+ import type { Ext, KeyOrigin, KeyRecord, KeyStatus, KeyType } from "../../../_shared/components.js";
7
+
8
+
9
+ /**
10
+ * Names one key and the exportability it should carry afterwards. The outer document members (id, type, issuer, recipient, issuedAt, expiresAt, proof) are owned by the framework — SPEC §6.3.
11
+ */
12
+ export interface KeysSetExportabilityPayload {
13
+ /**
14
+ * The key whose exportability changes. Custodian-scoped, as everywhere in `keys/*`.
15
+ */
16
+ keyId: string;
17
+ /**
18
+ * The state the key should be in when this request succeeds — not a delta. `false` asks the custodian to refuse every future export of the key; `true` asks it to allow them again. Stated absolutely so a repeat of the same request is a no-op rather than a toggle: a producer that retried a lost reply must not discover it has undone itself.
19
+ */
20
+ exportable: boolean;
21
+ ext?: Ext;
22
+ }
23
+ /**
24
+ * The success response: the key record as it now stands. Returning the whole record rather than an acknowledgement lets a producer confirm the state it asked for without a second round trip. Wrapped in `key` to match the rest of the family (`keys/show`, `keys/create`). Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/set-exportability/0.1#response.
25
+ */
26
+ export interface KeysSetExportabilityResponsePayload {
27
+ /**
28
+ * The record the custodian now holds. Never null — a request naming a key the custodian does not hold is `keys:notFound`, not a successful answer with no record. This differs from `keys/show`, where 'no such key' is a legitimate success.
29
+ */
30
+ key: KeyRecord;
31
+ }
32
+
33
+ /** Shared definitions this specification references, re-exported under the names it used to declare them with. */
34
+ export type { Ext, KeyOrigin, KeyRecord, KeyStatus, KeyType };
35
+
36
+ /** Trust Task type URI. */
37
+ export const TYPE_URI = "https://trusttasks.org/spec/keys/set-exportability/0.1" as const;
38
+
39
+ /** Stable alias for this specification's request payload shape. */
40
+ export type Payload = KeysSetExportabilityPayload;
41
+
42
+ /** Trust Task response type URI (request type URI + "#response"). */
43
+ export const RESPONSE_TYPE_URI = "https://trusttasks.org/spec/keys/set-exportability/0.1#response" as const;
44
+
45
+ /** Stable alias for this specification's success-response payload shape. */
46
+ export type Response = KeysSetExportabilityResponsePayload;
47
+
48
+ /**
49
+ * This specification's payload schema, as a value.
50
+ *
51
+ * SPEC.md §7.2 item 2 is performed against this. It is shipped as data
52
+ * rather than only as a `.json` file because TypeScript types are erased
53
+ * at runtime: without a schema a consumer has nothing to validate, and
54
+ * every REQUIRED payload member is optional in practice. Cross-file
55
+ * `$ref`s are already inlined, so it needs no resolver.
56
+ */
57
+ export const PAYLOAD_SCHEMA = {
58
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
59
+ "$id": "https://trusttasks.org/spec/keys/set-exportability/0.1",
60
+ "title": "Keys Set Exportability — payload",
61
+ "description": "Names one key and the exportability it should carry afterwards. The outer document members (id, type, issuer, recipient, issuedAt, expiresAt, proof) are owned by the framework — SPEC §6.3.",
62
+ "type": "object",
63
+ "additionalProperties": false,
64
+ "required": [
65
+ "keyId",
66
+ "exportable"
67
+ ],
68
+ "properties": {
69
+ "keyId": {
70
+ "type": "string",
71
+ "minLength": 1,
72
+ "description": "The key whose exportability changes. Custodian-scoped, as everywhere in `keys/*`."
73
+ },
74
+ "exportable": {
75
+ "type": "boolean",
76
+ "description": "The state the key should be in when this request succeeds — not a delta. `false` asks the custodian to refuse every future export of the key; `true` asks it to allow them again. Stated absolutely so a repeat of the same request is a no-op rather than a toggle: a producer that retried a lost reply must not discover it has undone itself."
77
+ },
78
+ "ext": {
79
+ "$ref": "#/$defs/Ext"
80
+ }
81
+ },
82
+ "$defs": {
83
+ "Response": {
84
+ "$anchor": "response",
85
+ "title": "Keys Set Exportability — response payload",
86
+ "description": "The success response: the key record as it now stands. Returning the whole record rather than an acknowledgement lets a producer confirm the state it asked for without a second round trip. Wrapped in `key` to match the rest of the family (`keys/show`, `keys/create`). Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/set-exportability/0.1#response.",
87
+ "type": "object",
88
+ "additionalProperties": false,
89
+ "required": [
90
+ "key"
91
+ ],
92
+ "properties": {
93
+ "key": {
94
+ "description": "The record the custodian now holds. Never null — a request naming a key the custodian does not hold is `keys:notFound`, not a successful answer with no record. This differs from `keys/show`, where 'no such key' is a legitimate success.",
95
+ "$ref": "#/$defs/KeyRecord"
96
+ }
97
+ }
98
+ },
99
+ "KeyRecord": {
100
+ "title": "KeyRecord",
101
+ "type": "object",
102
+ "additionalProperties": false,
103
+ "required": [
104
+ "keyId",
105
+ "keyType",
106
+ "status",
107
+ "publicKey",
108
+ "createdAt"
109
+ ],
110
+ "properties": {
111
+ "keyId": {
112
+ "type": "string",
113
+ "minLength": 1,
114
+ "description": "Maintainer-scoped identifier for the key. Stable for the key's lifetime except through an explicit `keys/rename`."
115
+ },
116
+ "keyType": {
117
+ "$ref": "#/$defs/KeyType"
118
+ },
119
+ "status": {
120
+ "$ref": "#/$defs/KeyStatus"
121
+ },
122
+ "publicKey": {
123
+ "type": "string",
124
+ "minLength": 1,
125
+ "description": "The public half, multibase-encoded. The private half is never carried by any keys/* response."
126
+ },
127
+ "derivationPath": {
128
+ "type": "string",
129
+ "description": "Hierarchical-deterministic path the key was derived at, when `origin` is `derived`. Absent for imported keys, which have no path."
130
+ },
131
+ "seedId": {
132
+ "type": "integer",
133
+ "minimum": 0,
134
+ "description": "Identifier of the seed the key was derived from, when the maintainer holds more than one. Absent for imported keys."
135
+ },
136
+ "origin": {
137
+ "$ref": "#/$defs/KeyOrigin"
138
+ },
139
+ "label": {
140
+ "type": "string",
141
+ "maxLength": 256,
142
+ "description": "Optional human-readable label. Operator-facing only; carries no authorization meaning."
143
+ },
144
+ "exportable": {
145
+ "type": "boolean",
146
+ "description": "Whether the private half may be released to a caller. `false` means the custodian refuses every export of this key and can only be asked to *use* it — signing, key agreement — so the material never leaves. **Absence means the key may be exported.** That is the permissive reading, and it is deliberate: it is what every record written before this member existed already meant, so a custodian adding the member cannot silently retract access to keys its callers already hold. A consumer MUST NOT infer the converse — absence is not a statement that export was considered and allowed. Marking a key non-exportable does not make it unrecoverable: a custodian's own whole-store backup is a different mechanism from an export to a caller, and this member does not speak to it.",
147
+ "$comment": "No JSON Schema `default` is declared here on purpose. A declared default is materialised by the generated bindings — the field becomes non-optional with a serde default — so an absent member reappears as an explicit `true` on re-serialisation, which breaks round-trip idempotence for every record written before this member existed. The permissive reading is stated in prose above, where a binding cannot act on it."
148
+ },
149
+ "contextId": {
150
+ "type": "string",
151
+ "description": "Scope the key belongs to. **Absence is not 'every scope'** — a key with no context is reachable only by a caller with unrestricted authority over the maintainer, which is the more restrictive reading, and a consumer that treats absence as a wildcard inverts the guarantee."
152
+ },
153
+ "createdAt": {
154
+ "type": "string",
155
+ "format": "date-time",
156
+ "description": "RFC 3339 timestamp at which the key was created or imported."
157
+ },
158
+ "updatedAt": {
159
+ "type": "string",
160
+ "format": "date-time",
161
+ "description": "RFC 3339 timestamp of the last change to the record (rename, revocation)."
162
+ },
163
+ "ext": {
164
+ "$ref": "#/$defs/Ext"
165
+ }
166
+ }
167
+ },
168
+ "KeyOrigin": {
169
+ "title": "KeyOrigin",
170
+ "type": "string",
171
+ "enum": [
172
+ "derived",
173
+ "imported",
174
+ "internal"
175
+ ],
176
+ "description": "Where the private key came from. `derived` means the maintainer generated it from a seed it holds and can reproduce it from `derivationPath`; `imported` means it arrived from outside and exists only as stored material; `internal` means the maintainer generated it from a CSPRNG and it is reproducible from nothing at all. The distinction is operationally load-bearing: a `derived` key survives a seed restore, an `imported` one is lost unless it was backed up separately, and an `internal` one cannot be recovered by any means once the maintainer's storage is gone. This member is also the only way a consumer can confirm that a `keys/create` request for an `internal` key was honoured rather than silently downgraded to a derived one — see that specification's `internal` member.",
177
+ "default": "derived"
178
+ },
179
+ "KeyStatus": {
180
+ "title": "KeyStatus",
181
+ "type": "string",
182
+ "enum": [
183
+ "active",
184
+ "revoked"
185
+ ],
186
+ "description": "Lifecycle state. Only an `active` key may be named in a signing request; a `revoked` key is retained so historic signatures remain attributable, and MUST NOT be reactivated."
187
+ },
188
+ "KeyType": {
189
+ "title": "KeyType",
190
+ "type": "string",
191
+ "enum": [
192
+ "ed25519",
193
+ "x25519",
194
+ "p256"
195
+ ],
196
+ "description": "Cryptographic algorithm the key material belongs to. `ed25519` signs (EdDSA), `x25519` performs key agreement and never signs, `p256` signs (ES256)."
197
+ },
198
+ "Ext": {
199
+ "title": "Ext",
200
+ "description": "Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.",
201
+ "type": "object",
202
+ "minProperties": 1,
203
+ "additionalProperties": true,
204
+ "propertyNames": {
205
+ "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
206
+ }
207
+ }
208
+ }
209
+ } as const;
210
+
211
+ /** As {@link PAYLOAD_SCHEMA}, for the success-response variant. */
212
+ export const RESPONSE_PAYLOAD_SCHEMA = {
213
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
214
+ "$ref": "#/$defs/Response",
215
+ "$defs": {
216
+ "Response": {
217
+ "$anchor": "response",
218
+ "title": "Keys Set Exportability — response payload",
219
+ "description": "The success response: the key record as it now stands. Returning the whole record rather than an acknowledgement lets a producer confirm the state it asked for without a second round trip. Wrapped in `key` to match the rest of the family (`keys/show`, `keys/create`). Carried in a Trust Task document whose type is https://trusttasks.org/spec/keys/set-exportability/0.1#response.",
220
+ "type": "object",
221
+ "additionalProperties": false,
222
+ "required": [
223
+ "key"
224
+ ],
225
+ "properties": {
226
+ "key": {
227
+ "description": "The record the custodian now holds. Never null — a request naming a key the custodian does not hold is `keys:notFound`, not a successful answer with no record. This differs from `keys/show`, where 'no such key' is a legitimate success.",
228
+ "$ref": "#/$defs/KeyRecord"
229
+ }
230
+ }
231
+ },
232
+ "KeyRecord": {
233
+ "title": "KeyRecord",
234
+ "type": "object",
235
+ "additionalProperties": false,
236
+ "required": [
237
+ "keyId",
238
+ "keyType",
239
+ "status",
240
+ "publicKey",
241
+ "createdAt"
242
+ ],
243
+ "properties": {
244
+ "keyId": {
245
+ "type": "string",
246
+ "minLength": 1,
247
+ "description": "Maintainer-scoped identifier for the key. Stable for the key's lifetime except through an explicit `keys/rename`."
248
+ },
249
+ "keyType": {
250
+ "$ref": "#/$defs/KeyType"
251
+ },
252
+ "status": {
253
+ "$ref": "#/$defs/KeyStatus"
254
+ },
255
+ "publicKey": {
256
+ "type": "string",
257
+ "minLength": 1,
258
+ "description": "The public half, multibase-encoded. The private half is never carried by any keys/* response."
259
+ },
260
+ "derivationPath": {
261
+ "type": "string",
262
+ "description": "Hierarchical-deterministic path the key was derived at, when `origin` is `derived`. Absent for imported keys, which have no path."
263
+ },
264
+ "seedId": {
265
+ "type": "integer",
266
+ "minimum": 0,
267
+ "description": "Identifier of the seed the key was derived from, when the maintainer holds more than one. Absent for imported keys."
268
+ },
269
+ "origin": {
270
+ "$ref": "#/$defs/KeyOrigin"
271
+ },
272
+ "label": {
273
+ "type": "string",
274
+ "maxLength": 256,
275
+ "description": "Optional human-readable label. Operator-facing only; carries no authorization meaning."
276
+ },
277
+ "exportable": {
278
+ "type": "boolean",
279
+ "description": "Whether the private half may be released to a caller. `false` means the custodian refuses every export of this key and can only be asked to *use* it — signing, key agreement — so the material never leaves. **Absence means the key may be exported.** That is the permissive reading, and it is deliberate: it is what every record written before this member existed already meant, so a custodian adding the member cannot silently retract access to keys its callers already hold. A consumer MUST NOT infer the converse — absence is not a statement that export was considered and allowed. Marking a key non-exportable does not make it unrecoverable: a custodian's own whole-store backup is a different mechanism from an export to a caller, and this member does not speak to it.",
280
+ "$comment": "No JSON Schema `default` is declared here on purpose. A declared default is materialised by the generated bindings — the field becomes non-optional with a serde default — so an absent member reappears as an explicit `true` on re-serialisation, which breaks round-trip idempotence for every record written before this member existed. The permissive reading is stated in prose above, where a binding cannot act on it."
281
+ },
282
+ "contextId": {
283
+ "type": "string",
284
+ "description": "Scope the key belongs to. **Absence is not 'every scope'** — a key with no context is reachable only by a caller with unrestricted authority over the maintainer, which is the more restrictive reading, and a consumer that treats absence as a wildcard inverts the guarantee."
285
+ },
286
+ "createdAt": {
287
+ "type": "string",
288
+ "format": "date-time",
289
+ "description": "RFC 3339 timestamp at which the key was created or imported."
290
+ },
291
+ "updatedAt": {
292
+ "type": "string",
293
+ "format": "date-time",
294
+ "description": "RFC 3339 timestamp of the last change to the record (rename, revocation)."
295
+ },
296
+ "ext": {
297
+ "$ref": "#/$defs/Ext"
298
+ }
299
+ }
300
+ },
301
+ "KeyOrigin": {
302
+ "title": "KeyOrigin",
303
+ "type": "string",
304
+ "enum": [
305
+ "derived",
306
+ "imported",
307
+ "internal"
308
+ ],
309
+ "description": "Where the private key came from. `derived` means the maintainer generated it from a seed it holds and can reproduce it from `derivationPath`; `imported` means it arrived from outside and exists only as stored material; `internal` means the maintainer generated it from a CSPRNG and it is reproducible from nothing at all. The distinction is operationally load-bearing: a `derived` key survives a seed restore, an `imported` one is lost unless it was backed up separately, and an `internal` one cannot be recovered by any means once the maintainer's storage is gone. This member is also the only way a consumer can confirm that a `keys/create` request for an `internal` key was honoured rather than silently downgraded to a derived one — see that specification's `internal` member.",
310
+ "default": "derived"
311
+ },
312
+ "KeyStatus": {
313
+ "title": "KeyStatus",
314
+ "type": "string",
315
+ "enum": [
316
+ "active",
317
+ "revoked"
318
+ ],
319
+ "description": "Lifecycle state. Only an `active` key may be named in a signing request; a `revoked` key is retained so historic signatures remain attributable, and MUST NOT be reactivated."
320
+ },
321
+ "KeyType": {
322
+ "title": "KeyType",
323
+ "type": "string",
324
+ "enum": [
325
+ "ed25519",
326
+ "x25519",
327
+ "p256"
328
+ ],
329
+ "description": "Cryptographic algorithm the key material belongs to. `ed25519` signs (EdDSA), `x25519` performs key agreement and never signs, `p256` signs (ES256)."
330
+ },
331
+ "Ext": {
332
+ "title": "Ext",
333
+ "description": "Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.",
334
+ "type": "object",
335
+ "minProperties": 1,
336
+ "additionalProperties": true,
337
+ "propertyNames": {
338
+ "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
339
+ }
340
+ }
341
+ }
342
+ } as const;
343
+
344
+ /**
345
+ * SPEC.md §7.2 policy for the request variant, from this specification's
346
+ * front matter. Pass to `consumeInbound` — items 5b, 7 and 8 are
347
+ * per-specification and cannot be derived from the document alone, and
348
+ * item 2 needs the schema this carries.
349
+ */
350
+ export const SPEC = {
351
+ typeUri: TYPE_URI,
352
+ isBearer: false,
353
+ isProofRequired: true,
354
+ isRecipientRequired: true,
355
+ isIssuedAtRequired: true,
356
+ payloadSchema: PAYLOAD_SCHEMA,
357
+ } as const;
358
+
359
+ /**
360
+ * SPEC.md §7.2 policy for the success-response variant. `isRecipientRequired`
361
+ * tracks the *issuer* party's requirement because a response swaps the
362
+ * parties (§7.3 item 5).
363
+ */
364
+ export const RESPONSE_SPEC = {
365
+ typeUri: RESPONSE_TYPE_URI,
366
+ isBearer: false,
367
+ isProofRequired: true,
368
+ isRecipientRequired: true,
369
+ isIssuedAtRequired: true,
370
+ payloadSchema: RESPONSE_PAYLOAD_SCHEMA,
371
+ } as const;