@keytrace/lexicon 0.0.10 → 0.0.11

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.
@@ -8,11 +8,11 @@
8
8
  "description": "An identity claim linking this DID to an external account",
9
9
  "record": {
10
10
  "type": "object",
11
- "required": ["type", "claimUri", "identity", "sig", "createdAt"],
11
+ "required": ["type", "claimUri", "identity", "sigs", "createdAt"],
12
12
  "properties": {
13
13
  "type": {
14
14
  "type": "string",
15
- "knownValues": ["github", "dns", "mastodon", "twitter", "website"],
15
+ "knownValues": ["github", "dns", "mastodon", "twitter", "website", "pgp"],
16
16
  "description": "The claim type identifier"
17
17
  },
18
18
  "claimUri": {
@@ -24,23 +24,52 @@
24
24
  "ref": "#identity",
25
25
  "description": "Structured data about the claimed identity"
26
26
  },
27
- "sig": {
28
- "type": "ref",
29
- "ref": "dev.keytrace.signature#main",
30
- "description": "Cryptographic attestation signature from the keytrace service"
27
+ "sigs": {
28
+ "type": "array",
29
+ "items": {
30
+ "type": "ref",
31
+ "ref": "dev.keytrace.signature#main"
32
+ },
33
+ "description": "One or more cryptographic attestation signatures from verification services."
31
34
  },
32
35
  "comment": {
33
36
  "type": "string",
34
37
  "maxLength": 256,
35
38
  "description": "Optional user-provided label for this claim"
36
39
  },
40
+ "status": {
41
+ "type": "string",
42
+ "description": "Current verification status of this claim. Absent on legacy records, treated as 'verified'.",
43
+ "knownValues": ["verified", "failed", "retracted"]
44
+ },
45
+ "lastVerifiedAt": {
46
+ "type": "string",
47
+ "format": "datetime",
48
+ "description": "Timestamp of the most recent successful re-verification by the system"
49
+ },
50
+ "failedAt": {
51
+ "type": "string",
52
+ "format": "datetime",
53
+ "description": "Timestamp when the claim last failed re-verification or was retracted"
54
+ },
37
55
  "createdAt": {
38
56
  "type": "string",
39
- "format": "datetime"
57
+ "format": "datetime",
58
+ "description": "Datetime when this claim was created (ISO 8601)."
59
+ },
60
+ "nonce": {
61
+ "type": "string",
62
+ "maxGraphemes": 128,
63
+ "description": "Random one-time value embedded in the challenge text posted to the external service. Used by verifiers to confirm the proof was created specifically for this claim session."
40
64
  },
41
65
  "prerelease": {
42
66
  "type": "boolean",
43
67
  "description": "Whether this claim was created during the prerelease/alpha period"
68
+ },
69
+ "retractedAt": {
70
+ "type": "string",
71
+ "format": "datetime",
72
+ "description": "Datetime when this claim was retracted. Present only if the claim has been retracted (ISO 8601)."
44
73
  }
45
74
  }
46
75
  }
@@ -0,0 +1,30 @@
1
+ {
2
+ "lexicon": 1,
3
+ "id": "dev.keytrace.profile",
4
+ "defs": {
5
+ "main": {
6
+ "type": "record",
7
+ "key": "literal:self",
8
+ "description": "Keytrace profile settings. Singleton record stored in the user's ATProto repo.",
9
+ "record": {
10
+ "type": "object",
11
+ "properties": {
12
+ "displayName": {
13
+ "type": "string",
14
+ "maxGraphemes": 128,
15
+ "description": "Display name override for the keytrace profile. Falls back to Bluesky display name if absent."
16
+ },
17
+ "bio": {
18
+ "type": "string",
19
+ "maxGraphemes": 1024,
20
+ "description": "Bio or description shown on the keytrace profile page."
21
+ },
22
+ "createdAt": {
23
+ "type": "string",
24
+ "format": "datetime"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "lexicon": 1,
3
- "id": "dev.keytrace.key",
3
+ "id": "dev.keytrace.serverPublicKey",
4
4
  "defs": {
5
5
  "main": {
6
6
  "type": "record",
@@ -16,11 +16,18 @@
16
16
  },
17
17
  "validFrom": {
18
18
  "type": "string",
19
- "format": "datetime"
19
+ "format": "datetime",
20
+ "description": "Datetime from which this key is valid (ISO 8601)."
20
21
  },
21
22
  "validUntil": {
22
23
  "type": "string",
23
- "format": "datetime"
24
+ "format": "datetime",
25
+ "description": "Datetime until which this key is valid (ISO 8601)."
26
+ },
27
+ "comment": {
28
+ "type": "string",
29
+ "maxGraphemes": 512,
30
+ "description": "Optional comment or description."
24
31
  }
25
32
  }
26
33
  }
@@ -5,25 +5,40 @@
5
5
  "main": {
6
6
  "type": "object",
7
7
  "description": "A cryptographic signature attesting to a claim",
8
- "required": ["kid", "src", "signedAt", "attestation"],
8
+ "required": ["kid", "src", "signedAt", "attestation", "signedFields"],
9
9
  "properties": {
10
10
  "kid": {
11
11
  "type": "string",
12
- "description": "Key identifier (e.g., date in YYYY-MM-DD format)"
12
+ "description": "Key identifier (e.g., date in YYYY-MM-DD format)."
13
13
  },
14
14
  "src": {
15
15
  "type": "string",
16
16
  "format": "at-uri",
17
- "description": "AT URI reference to the signing key record (e.g., at://did:plc:xxx/dev.keytrace.key/2024-01-15)"
17
+ "description": "AT URI reference to the signing key record published by the verification service (e.g., at://did:plc:serviceaccount/dev.keytrace.serverPublicKey/2024-01-15)."
18
18
  },
19
19
  "signedAt": {
20
20
  "type": "string",
21
21
  "format": "datetime",
22
- "description": "Timestamp when the signature was created"
22
+ "description": "Datetime when the signature was created (ISO 8601)."
23
23
  },
24
24
  "attestation": {
25
25
  "type": "string",
26
- "description": "The cryptographic signature (base64-encoded)"
26
+ "description": "The cryptographic signature (base64-encoded)."
27
+ },
28
+ "retractedAt": {
29
+ "type": "string",
30
+ "format": "datetime",
31
+ "description": "Datetime when this signature was retracted. Present only if the signature has been retracted (ISO 8601)."
32
+ },
33
+ "signedFields": {
34
+ "type": "array",
35
+ "items": { "type": "string" },
36
+ "description": "Ordered list of field names included in the signed payload (e.g., ['did', 'subject', 'type', 'verifiedAt'])"
37
+ },
38
+ "comment": {
39
+ "type": "string",
40
+ "maxGraphemes": 512,
41
+ "description": "Optional comment or description."
27
42
  }
28
43
  }
29
44
  }
@@ -0,0 +1,47 @@
1
+ {
2
+ "lexicon": 1,
3
+ "id": "dev.keytrace.statement",
4
+ "defs": {
5
+ "main": {
6
+ "type": "record",
7
+ "key": "tid",
8
+ "description": "A public statement signed by one of the user's own published public keys (dev.keytrace.userPublicKey).",
9
+ "record": {
10
+ "type": "object",
11
+ "required": ["content", "keyRef", "sig", "createdAt"],
12
+ "properties": {
13
+ "content": {
14
+ "type": "string",
15
+ "maxLength": 10000,
16
+ "maxGraphemes": 10000,
17
+ "description": "The statement text that was signed."
18
+ },
19
+ "subject": {
20
+ "type": "string",
21
+ "maxGraphemes": 256,
22
+ "description": "Optional short subject or title for the statement."
23
+ },
24
+ "keyRef": {
25
+ "type": "string",
26
+ "format": "at-uri",
27
+ "description": "AT URI of the dev.keytrace.userPublicKey record whose private key produced this signature (e.g., at://did:plc:xxx/dev.keytrace.userPublicKey/3k4...)"
28
+ },
29
+ "sig": {
30
+ "type": "string",
31
+ "description": "Cryptographic signature of the content field, produced by the key referenced in keyRef (PGP cleartext or detached, base64-encoded binary signature)."
32
+ },
33
+ "retractedAt": {
34
+ "type": "string",
35
+ "format": "datetime",
36
+ "description": "Datetime when this statement was retracted. Present only if the statement has been retracted (ISO 8601)."
37
+ },
38
+ "createdAt": {
39
+ "type": "string",
40
+ "format": "datetime",
41
+ "description": "Datetime when this statement was created (ISO 8601)."
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "lexicon": 1,
3
+ "id": "dev.keytrace.userPublicKey",
4
+ "defs": {
5
+ "main": {
6
+ "type": "record",
7
+ "key": "tid",
8
+ "description": "A user-published public key.",
9
+ "record": {
10
+ "type": "object",
11
+ "required": ["keyType", "publicKey", "createdAt"],
12
+ "properties": {
13
+ "keyType": {
14
+ "type": "string",
15
+ "knownValues": ["pgp", "ssh-ed25519", "ssh-ecdsa"],
16
+ "description": "Format of the public key."
17
+ },
18
+ "publicKeyArmored": {
19
+ "type": "string",
20
+ "maxLength": 16384,
21
+ "maxGraphemes": 16384,
22
+ "description": "Full public key in standard text armored format."
23
+ },
24
+ "fingerprint": {
25
+ "type": "string",
26
+ "maxGraphemes": 256,
27
+ "description": "Key fingerprint."
28
+ },
29
+ "label": {
30
+ "type": "string",
31
+ "maxGraphemes": 128,
32
+ "description": "Human-readable label for this key (e.g., 'work laptop', 'signing key')."
33
+ },
34
+ "comment": {
35
+ "type": "string",
36
+ "maxGraphemes": 512,
37
+ "description": "Optional comment or description."
38
+ },
39
+ "expiresAt": {
40
+ "type": "string",
41
+ "format": "datetime",
42
+ "description": "Datetime when this key expires (ISO 8601)."
43
+ },
44
+ "retractedAt": {
45
+ "type": "string",
46
+ "format": "datetime",
47
+ "description": "Datetime when this key was retracted. Present only if the key has been retracted (ISO 8601)."
48
+ },
49
+ "createdAt": {
50
+ "type": "string",
51
+ "format": "datetime",
52
+ "description": "Datetime when this key was created (ISO 8601)."
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
package/package.json CHANGED
@@ -1,16 +1,25 @@
1
1
  {
2
2
  "name": "@keytrace/lexicon",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "files": [
5
- "lexicons"
5
+ "lexicons",
6
+ "types"
6
7
  ],
7
8
  "type": "module",
8
9
  "exports": {
9
- "./lexicons/*": "./lexicons/*"
10
+ "./lexicons/*": "./lexicons/*",
11
+ "./types": "./types/index.js",
12
+ "./types/*": "./types/*"
13
+ },
14
+ "scripts": {
15
+ "codegen": "lex gen-api --yes ./types ./lexicons/dev/keytrace/*"
10
16
  },
11
17
  "repository": {
12
18
  "type": "git",
13
19
  "url": "https://github.com/orta/keytrace",
14
20
  "directory": "packages/lexicon"
21
+ },
22
+ "devDependencies": {
23
+ "@atproto/lex-cli": "^0.9.8"
15
24
  }
16
25
  }
package/types/index.ts ADDED
@@ -0,0 +1,387 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import {
5
+ XrpcClient,
6
+ type FetchHandler,
7
+ type FetchHandlerOptions,
8
+ } from '@atproto/xrpc'
9
+ import { schemas } from './lexicons.js'
10
+ import { CID } from 'multiformats/cid'
11
+ import { type OmitKey, type Un$Typed } from './util.js'
12
+ import * as DevKeytraceClaim from './types/dev/keytrace/claim.js'
13
+ import * as DevKeytraceProfile from './types/dev/keytrace/profile.js'
14
+ import * as DevKeytraceServerPublicKey from './types/dev/keytrace/serverPublicKey.js'
15
+ import * as DevKeytraceSignature from './types/dev/keytrace/signature.js'
16
+ import * as DevKeytraceStatement from './types/dev/keytrace/statement.js'
17
+
18
+ export * as DevKeytraceClaim from './types/dev/keytrace/claim.js'
19
+ export * as DevKeytraceProfile from './types/dev/keytrace/profile.js'
20
+ export * as DevKeytraceServerPublicKey from './types/dev/keytrace/serverPublicKey.js'
21
+ export * as DevKeytraceSignature from './types/dev/keytrace/signature.js'
22
+ export * as DevKeytraceStatement from './types/dev/keytrace/statement.js'
23
+
24
+ export class AtpBaseClient extends XrpcClient {
25
+ dev: DevNS
26
+
27
+ constructor(options: FetchHandler | FetchHandlerOptions) {
28
+ super(options, schemas)
29
+ this.dev = new DevNS(this)
30
+ }
31
+
32
+ /** @deprecated use `this` instead */
33
+ get xrpc(): XrpcClient {
34
+ return this
35
+ }
36
+ }
37
+
38
+ export class DevNS {
39
+ _client: XrpcClient
40
+ keytrace: DevKeytraceNS
41
+
42
+ constructor(client: XrpcClient) {
43
+ this._client = client
44
+ this.keytrace = new DevKeytraceNS(client)
45
+ }
46
+ }
47
+
48
+ export class DevKeytraceNS {
49
+ _client: XrpcClient
50
+ claim: DevKeytraceClaimRecord
51
+ profile: DevKeytraceProfileRecord
52
+ serverPublicKey: DevKeytraceServerPublicKeyRecord
53
+ statement: DevKeytraceStatementRecord
54
+
55
+ constructor(client: XrpcClient) {
56
+ this._client = client
57
+ this.claim = new DevKeytraceClaimRecord(client)
58
+ this.profile = new DevKeytraceProfileRecord(client)
59
+ this.serverPublicKey = new DevKeytraceServerPublicKeyRecord(client)
60
+ this.statement = new DevKeytraceStatementRecord(client)
61
+ }
62
+ }
63
+
64
+ export class DevKeytraceClaimRecord {
65
+ _client: XrpcClient
66
+
67
+ constructor(client: XrpcClient) {
68
+ this._client = client
69
+ }
70
+
71
+ async list(
72
+ params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>,
73
+ ): Promise<{
74
+ cursor?: string
75
+ records: { uri: string; value: DevKeytraceClaim.Record }[]
76
+ }> {
77
+ const res = await this._client.call('com.atproto.repo.listRecords', {
78
+ collection: 'dev.keytrace.claim',
79
+ ...params,
80
+ })
81
+ return res.data
82
+ }
83
+
84
+ async get(
85
+ params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>,
86
+ ): Promise<{ uri: string; cid: string; value: DevKeytraceClaim.Record }> {
87
+ const res = await this._client.call('com.atproto.repo.getRecord', {
88
+ collection: 'dev.keytrace.claim',
89
+ ...params,
90
+ })
91
+ return res.data
92
+ }
93
+
94
+ async create(
95
+ params: OmitKey<
96
+ ComAtprotoRepoCreateRecord.InputSchema,
97
+ 'collection' | 'record'
98
+ >,
99
+ record: Un$Typed<DevKeytraceClaim.Record>,
100
+ headers?: Record<string, string>,
101
+ ): Promise<{ uri: string; cid: string }> {
102
+ const collection = 'dev.keytrace.claim'
103
+ const res = await this._client.call(
104
+ 'com.atproto.repo.createRecord',
105
+ undefined,
106
+ { collection, ...params, record: { ...record, $type: collection } },
107
+ { encoding: 'application/json', headers },
108
+ )
109
+ return res.data
110
+ }
111
+
112
+ async put(
113
+ params: OmitKey<
114
+ ComAtprotoRepoPutRecord.InputSchema,
115
+ 'collection' | 'record'
116
+ >,
117
+ record: Un$Typed<DevKeytraceClaim.Record>,
118
+ headers?: Record<string, string>,
119
+ ): Promise<{ uri: string; cid: string }> {
120
+ const collection = 'dev.keytrace.claim'
121
+ const res = await this._client.call(
122
+ 'com.atproto.repo.putRecord',
123
+ undefined,
124
+ { collection, ...params, record: { ...record, $type: collection } },
125
+ { encoding: 'application/json', headers },
126
+ )
127
+ return res.data
128
+ }
129
+
130
+ async delete(
131
+ params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>,
132
+ headers?: Record<string, string>,
133
+ ): Promise<void> {
134
+ await this._client.call(
135
+ 'com.atproto.repo.deleteRecord',
136
+ undefined,
137
+ { collection: 'dev.keytrace.claim', ...params },
138
+ { headers },
139
+ )
140
+ }
141
+ }
142
+
143
+ export class DevKeytraceProfileRecord {
144
+ _client: XrpcClient
145
+
146
+ constructor(client: XrpcClient) {
147
+ this._client = client
148
+ }
149
+
150
+ async list(
151
+ params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>,
152
+ ): Promise<{
153
+ cursor?: string
154
+ records: { uri: string; value: DevKeytraceProfile.Record }[]
155
+ }> {
156
+ const res = await this._client.call('com.atproto.repo.listRecords', {
157
+ collection: 'dev.keytrace.profile',
158
+ ...params,
159
+ })
160
+ return res.data
161
+ }
162
+
163
+ async get(
164
+ params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>,
165
+ ): Promise<{ uri: string; cid: string; value: DevKeytraceProfile.Record }> {
166
+ const res = await this._client.call('com.atproto.repo.getRecord', {
167
+ collection: 'dev.keytrace.profile',
168
+ ...params,
169
+ })
170
+ return res.data
171
+ }
172
+
173
+ async create(
174
+ params: OmitKey<
175
+ ComAtprotoRepoCreateRecord.InputSchema,
176
+ 'collection' | 'record'
177
+ >,
178
+ record: Un$Typed<DevKeytraceProfile.Record>,
179
+ headers?: Record<string, string>,
180
+ ): Promise<{ uri: string; cid: string }> {
181
+ const collection = 'dev.keytrace.profile'
182
+ const res = await this._client.call(
183
+ 'com.atproto.repo.createRecord',
184
+ undefined,
185
+ {
186
+ collection,
187
+ rkey: 'self',
188
+ ...params,
189
+ record: { ...record, $type: collection },
190
+ },
191
+ { encoding: 'application/json', headers },
192
+ )
193
+ return res.data
194
+ }
195
+
196
+ async put(
197
+ params: OmitKey<
198
+ ComAtprotoRepoPutRecord.InputSchema,
199
+ 'collection' | 'record'
200
+ >,
201
+ record: Un$Typed<DevKeytraceProfile.Record>,
202
+ headers?: Record<string, string>,
203
+ ): Promise<{ uri: string; cid: string }> {
204
+ const collection = 'dev.keytrace.profile'
205
+ const res = await this._client.call(
206
+ 'com.atproto.repo.putRecord',
207
+ undefined,
208
+ { collection, ...params, record: { ...record, $type: collection } },
209
+ { encoding: 'application/json', headers },
210
+ )
211
+ return res.data
212
+ }
213
+
214
+ async delete(
215
+ params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>,
216
+ headers?: Record<string, string>,
217
+ ): Promise<void> {
218
+ await this._client.call(
219
+ 'com.atproto.repo.deleteRecord',
220
+ undefined,
221
+ { collection: 'dev.keytrace.profile', ...params },
222
+ { headers },
223
+ )
224
+ }
225
+ }
226
+
227
+ export class DevKeytraceServerPublicKeyRecord {
228
+ _client: XrpcClient
229
+
230
+ constructor(client: XrpcClient) {
231
+ this._client = client
232
+ }
233
+
234
+ async list(
235
+ params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>,
236
+ ): Promise<{
237
+ cursor?: string
238
+ records: { uri: string; value: DevKeytraceServerPublicKey.Record }[]
239
+ }> {
240
+ const res = await this._client.call('com.atproto.repo.listRecords', {
241
+ collection: 'dev.keytrace.serverPublicKey',
242
+ ...params,
243
+ })
244
+ return res.data
245
+ }
246
+
247
+ async get(
248
+ params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>,
249
+ ): Promise<{
250
+ uri: string
251
+ cid: string
252
+ value: DevKeytraceServerPublicKey.Record
253
+ }> {
254
+ const res = await this._client.call('com.atproto.repo.getRecord', {
255
+ collection: 'dev.keytrace.serverPublicKey',
256
+ ...params,
257
+ })
258
+ return res.data
259
+ }
260
+
261
+ async create(
262
+ params: OmitKey<
263
+ ComAtprotoRepoCreateRecord.InputSchema,
264
+ 'collection' | 'record'
265
+ >,
266
+ record: Un$Typed<DevKeytraceServerPublicKey.Record>,
267
+ headers?: Record<string, string>,
268
+ ): Promise<{ uri: string; cid: string }> {
269
+ const collection = 'dev.keytrace.serverPublicKey'
270
+ const res = await this._client.call(
271
+ 'com.atproto.repo.createRecord',
272
+ undefined,
273
+ { collection, ...params, record: { ...record, $type: collection } },
274
+ { encoding: 'application/json', headers },
275
+ )
276
+ return res.data
277
+ }
278
+
279
+ async put(
280
+ params: OmitKey<
281
+ ComAtprotoRepoPutRecord.InputSchema,
282
+ 'collection' | 'record'
283
+ >,
284
+ record: Un$Typed<DevKeytraceServerPublicKey.Record>,
285
+ headers?: Record<string, string>,
286
+ ): Promise<{ uri: string; cid: string }> {
287
+ const collection = 'dev.keytrace.serverPublicKey'
288
+ const res = await this._client.call(
289
+ 'com.atproto.repo.putRecord',
290
+ undefined,
291
+ { collection, ...params, record: { ...record, $type: collection } },
292
+ { encoding: 'application/json', headers },
293
+ )
294
+ return res.data
295
+ }
296
+
297
+ async delete(
298
+ params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>,
299
+ headers?: Record<string, string>,
300
+ ): Promise<void> {
301
+ await this._client.call(
302
+ 'com.atproto.repo.deleteRecord',
303
+ undefined,
304
+ { collection: 'dev.keytrace.serverPublicKey', ...params },
305
+ { headers },
306
+ )
307
+ }
308
+ }
309
+
310
+ export class DevKeytraceStatementRecord {
311
+ _client: XrpcClient
312
+
313
+ constructor(client: XrpcClient) {
314
+ this._client = client
315
+ }
316
+
317
+ async list(
318
+ params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>,
319
+ ): Promise<{
320
+ cursor?: string
321
+ records: { uri: string; value: DevKeytraceStatement.Record }[]
322
+ }> {
323
+ const res = await this._client.call('com.atproto.repo.listRecords', {
324
+ collection: 'dev.keytrace.statement',
325
+ ...params,
326
+ })
327
+ return res.data
328
+ }
329
+
330
+ async get(
331
+ params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>,
332
+ ): Promise<{ uri: string; cid: string; value: DevKeytraceStatement.Record }> {
333
+ const res = await this._client.call('com.atproto.repo.getRecord', {
334
+ collection: 'dev.keytrace.statement',
335
+ ...params,
336
+ })
337
+ return res.data
338
+ }
339
+
340
+ async create(
341
+ params: OmitKey<
342
+ ComAtprotoRepoCreateRecord.InputSchema,
343
+ 'collection' | 'record'
344
+ >,
345
+ record: Un$Typed<DevKeytraceStatement.Record>,
346
+ headers?: Record<string, string>,
347
+ ): Promise<{ uri: string; cid: string }> {
348
+ const collection = 'dev.keytrace.statement'
349
+ const res = await this._client.call(
350
+ 'com.atproto.repo.createRecord',
351
+ undefined,
352
+ { collection, ...params, record: { ...record, $type: collection } },
353
+ { encoding: 'application/json', headers },
354
+ )
355
+ return res.data
356
+ }
357
+
358
+ async put(
359
+ params: OmitKey<
360
+ ComAtprotoRepoPutRecord.InputSchema,
361
+ 'collection' | 'record'
362
+ >,
363
+ record: Un$Typed<DevKeytraceStatement.Record>,
364
+ headers?: Record<string, string>,
365
+ ): Promise<{ uri: string; cid: string }> {
366
+ const collection = 'dev.keytrace.statement'
367
+ const res = await this._client.call(
368
+ 'com.atproto.repo.putRecord',
369
+ undefined,
370
+ { collection, ...params, record: { ...record, $type: collection } },
371
+ { encoding: 'application/json', headers },
372
+ )
373
+ return res.data
374
+ }
375
+
376
+ async delete(
377
+ params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>,
378
+ headers?: Record<string, string>,
379
+ ): Promise<void> {
380
+ await this._client.call(
381
+ 'com.atproto.repo.deleteRecord',
382
+ undefined,
383
+ { collection: 'dev.keytrace.statement', ...params },
384
+ { headers },
385
+ )
386
+ }
387
+ }
@@ -0,0 +1,333 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import {
5
+ type LexiconDoc,
6
+ Lexicons,
7
+ ValidationError,
8
+ type ValidationResult,
9
+ } from '@atproto/lexicon'
10
+ import { type $Typed, is$typed, maybe$typed } from './util.js'
11
+
12
+ export const schemaDict = {
13
+ DevKeytraceClaim: {
14
+ lexicon: 1,
15
+ id: 'dev.keytrace.claim',
16
+ defs: {
17
+ main: {
18
+ type: 'record',
19
+ key: 'tid',
20
+ description:
21
+ 'An identity claim linking this DID to an external account',
22
+ record: {
23
+ type: 'object',
24
+ required: ['type', 'claimUri', 'identity', 'sigs', 'createdAt'],
25
+ properties: {
26
+ type: {
27
+ type: 'string',
28
+ knownValues: [
29
+ 'github',
30
+ 'dns',
31
+ 'mastodon',
32
+ 'twitter',
33
+ 'website',
34
+ 'pgp',
35
+ ],
36
+ description: 'The claim type identifier',
37
+ },
38
+ claimUri: {
39
+ type: 'string',
40
+ description:
41
+ 'The identity claim URI (e.g., for github: https://gist.github.com/username/id, dns:example.com)',
42
+ },
43
+ identity: {
44
+ type: 'ref',
45
+ ref: 'lex:dev.keytrace.claim#identity',
46
+ description: 'Structured data about the claimed identity',
47
+ },
48
+ sigs: {
49
+ type: 'array',
50
+ items: {
51
+ type: 'ref',
52
+ ref: 'lex:dev.keytrace.signature#main',
53
+ },
54
+ description:
55
+ 'One or more cryptographic attestation signatures from verification services.',
56
+ },
57
+ comment: {
58
+ type: 'string',
59
+ maxLength: 256,
60
+ description: 'Optional user-provided label for this claim',
61
+ },
62
+ status: {
63
+ type: 'string',
64
+ description:
65
+ "Current verification status of this claim. Absent on legacy records, treated as 'verified'.",
66
+ knownValues: ['verified', 'failed', 'retracted'],
67
+ },
68
+ lastVerifiedAt: {
69
+ type: 'string',
70
+ format: 'datetime',
71
+ description:
72
+ 'Timestamp of the most recent successful re-verification by the system',
73
+ },
74
+ failedAt: {
75
+ type: 'string',
76
+ format: 'datetime',
77
+ description:
78
+ 'Timestamp when the claim last failed re-verification or was retracted',
79
+ },
80
+ createdAt: {
81
+ type: 'string',
82
+ format: 'datetime',
83
+ description: 'Datetime when this claim was created (ISO 8601).',
84
+ },
85
+ nonce: {
86
+ type: 'string',
87
+ maxGraphemes: 128,
88
+ description:
89
+ 'Random one-time value embedded in the challenge text posted to the external service. Used by verifiers to confirm the proof was created specifically for this claim session.',
90
+ },
91
+ prerelease: {
92
+ type: 'boolean',
93
+ description:
94
+ 'Whether this claim was created during the prerelease/alpha period',
95
+ },
96
+ retractedAt: {
97
+ type: 'string',
98
+ format: 'datetime',
99
+ description:
100
+ 'Datetime when this claim was retracted. Present only if the claim has been retracted (ISO 8601).',
101
+ },
102
+ },
103
+ },
104
+ },
105
+ identity: {
106
+ type: 'object',
107
+ description: 'Generic identity data for the claimed account',
108
+ required: ['subject'],
109
+ properties: {
110
+ subject: {
111
+ type: 'string',
112
+ description: 'Primary identifier (username, domain, handle, etc.)',
113
+ },
114
+ avatarUrl: {
115
+ type: 'string',
116
+ format: 'uri',
117
+ description: 'Avatar/profile image URL',
118
+ },
119
+ profileUrl: {
120
+ type: 'string',
121
+ format: 'uri',
122
+ description: 'Profile page URL',
123
+ },
124
+ displayName: {
125
+ type: 'string',
126
+ description: 'Display name if different from subject',
127
+ },
128
+ },
129
+ },
130
+ },
131
+ },
132
+ DevKeytraceProfile: {
133
+ lexicon: 1,
134
+ id: 'dev.keytrace.profile',
135
+ defs: {
136
+ main: {
137
+ type: 'record',
138
+ key: 'literal:self',
139
+ description:
140
+ "Keytrace profile settings. Singleton record stored in the user's ATProto repo.",
141
+ record: {
142
+ type: 'object',
143
+ properties: {
144
+ displayName: {
145
+ type: 'string',
146
+ maxGraphemes: 128,
147
+ description:
148
+ 'Display name override for the keytrace profile. Falls back to Bluesky display name if absent.',
149
+ },
150
+ bio: {
151
+ type: 'string',
152
+ maxGraphemes: 1024,
153
+ description:
154
+ 'Bio or description shown on the keytrace profile page.',
155
+ },
156
+ createdAt: {
157
+ type: 'string',
158
+ format: 'datetime',
159
+ },
160
+ },
161
+ },
162
+ },
163
+ },
164
+ },
165
+ DevKeytraceServerPublicKey: {
166
+ lexicon: 1,
167
+ id: 'dev.keytrace.serverPublicKey',
168
+ defs: {
169
+ main: {
170
+ type: 'record',
171
+ key: 'any',
172
+ description:
173
+ 'A daily signing key for claim attestations. Record key is the date in YYYY-MM-DD format.',
174
+ record: {
175
+ type: 'object',
176
+ required: ['publicJwk', 'validFrom', 'validUntil'],
177
+ properties: {
178
+ publicJwk: {
179
+ type: 'string',
180
+ description: 'JWK public key as a JSON string (RFC 7517 format)',
181
+ },
182
+ validFrom: {
183
+ type: 'string',
184
+ format: 'datetime',
185
+ description: 'Datetime from which this key is valid (ISO 8601).',
186
+ },
187
+ validUntil: {
188
+ type: 'string',
189
+ format: 'datetime',
190
+ description: 'Datetime until which this key is valid (ISO 8601).',
191
+ },
192
+ },
193
+ },
194
+ },
195
+ },
196
+ },
197
+ DevKeytraceSignature: {
198
+ lexicon: 1,
199
+ id: 'dev.keytrace.signature',
200
+ defs: {
201
+ main: {
202
+ type: 'object',
203
+ description: 'A cryptographic signature attesting to a claim',
204
+ required: ['kid', 'src', 'signedAt', 'attestation', 'signedFields'],
205
+ properties: {
206
+ kid: {
207
+ type: 'string',
208
+ description: 'Key identifier (e.g., date in YYYY-MM-DD format).',
209
+ },
210
+ src: {
211
+ type: 'string',
212
+ format: 'at-uri',
213
+ description:
214
+ 'AT URI reference to the signing key record published by the verification service (e.g., at://did:plc:serviceaccount/dev.keytrace.serverPublicKey/2024-01-15).',
215
+ },
216
+ signedAt: {
217
+ type: 'string',
218
+ format: 'datetime',
219
+ description: 'Datetime when the signature was created (ISO 8601).',
220
+ },
221
+ attestation: {
222
+ type: 'string',
223
+ description: 'The cryptographic signature (base64-encoded).',
224
+ },
225
+ retractedAt: {
226
+ type: 'string',
227
+ format: 'datetime',
228
+ description:
229
+ 'Datetime when this signature was retracted. Present only if the signature has been retracted (ISO 8601).',
230
+ },
231
+ signedFields: {
232
+ type: 'array',
233
+ items: {
234
+ type: 'string',
235
+ },
236
+ description:
237
+ "Ordered list of field names included in the signed payload (e.g., ['did', 'subject', 'type', 'verifiedAt'])",
238
+ },
239
+ },
240
+ },
241
+ },
242
+ },
243
+ DevKeytraceStatement: {
244
+ lexicon: 1,
245
+ id: 'dev.keytrace.statement',
246
+ defs: {
247
+ main: {
248
+ type: 'record',
249
+ key: 'tid',
250
+ description:
251
+ "A public statement signed by one of the user's own published public keys (dev.keytrace.userPublicKey).",
252
+ record: {
253
+ type: 'object',
254
+ required: ['content', 'keyRef', 'sig', 'createdAt'],
255
+ properties: {
256
+ content: {
257
+ type: 'string',
258
+ maxLength: 10000,
259
+ maxGraphemes: 10000,
260
+ description: 'The statement text that was signed.',
261
+ },
262
+ subject: {
263
+ type: 'string',
264
+ maxGraphemes: 256,
265
+ description: 'Optional short subject or title for the statement.',
266
+ },
267
+ keyRef: {
268
+ type: 'string',
269
+ format: 'at-uri',
270
+ description:
271
+ 'AT URI of the dev.keytrace.userPublicKey record whose private key produced this signature (e.g., at://did:plc:xxx/dev.keytrace.userPublicKey/3k4...)',
272
+ },
273
+ sig: {
274
+ type: 'string',
275
+ description:
276
+ 'Cryptographic signature of the content field, produced by the key referenced in keyRef (PGP cleartext or detached, base64-encoded binary signature).',
277
+ },
278
+ retractedAt: {
279
+ type: 'string',
280
+ format: 'datetime',
281
+ description:
282
+ 'Datetime when this statement was retracted. Present only if the statement has been retracted (ISO 8601).',
283
+ },
284
+ createdAt: {
285
+ type: 'string',
286
+ format: 'datetime',
287
+ description:
288
+ 'Datetime when this statement was created (ISO 8601).',
289
+ },
290
+ },
291
+ },
292
+ },
293
+ },
294
+ },
295
+ } as const satisfies Record<string, LexiconDoc>
296
+ export const schemas = Object.values(schemaDict) satisfies LexiconDoc[]
297
+ export const lexicons: Lexicons = new Lexicons(schemas)
298
+
299
+ export function validate<T extends { $type: string }>(
300
+ v: unknown,
301
+ id: string,
302
+ hash: string,
303
+ requiredType: true,
304
+ ): ValidationResult<T>
305
+ export function validate<T extends { $type?: string }>(
306
+ v: unknown,
307
+ id: string,
308
+ hash: string,
309
+ requiredType?: false,
310
+ ): ValidationResult<T>
311
+ export function validate(
312
+ v: unknown,
313
+ id: string,
314
+ hash: string,
315
+ requiredType?: boolean,
316
+ ): ValidationResult {
317
+ return (requiredType ? is$typed : maybe$typed)(v, id, hash)
318
+ ? lexicons.validate(`${id}#${hash}`, v)
319
+ : {
320
+ success: false,
321
+ error: new ValidationError(
322
+ `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`,
323
+ ),
324
+ }
325
+ }
326
+
327
+ export const ids = {
328
+ DevKeytraceClaim: 'dev.keytrace.claim',
329
+ DevKeytraceProfile: 'dev.keytrace.profile',
330
+ DevKeytraceServerPublicKey: 'dev.keytrace.serverPublicKey',
331
+ DevKeytraceSignature: 'dev.keytrace.signature',
332
+ DevKeytraceStatement: 'dev.keytrace.statement',
333
+ } as const
@@ -0,0 +1,86 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
+ import { CID } from 'multiformats/cid'
6
+ import { validate as _validate } from '../../../lexicons'
7
+ import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
+ import type * as DevKeytraceSignature from './signature.js'
9
+
10
+ const is$typed = _is$typed,
11
+ validate = _validate
12
+ const id = 'dev.keytrace.claim'
13
+
14
+ export interface Main {
15
+ $type: 'dev.keytrace.claim'
16
+ /** The claim type identifier */
17
+ type:
18
+ | 'github'
19
+ | 'dns'
20
+ | 'mastodon'
21
+ | 'twitter'
22
+ | 'website'
23
+ | 'pgp'
24
+ | (string & {})
25
+ /** The identity claim URI (e.g., for github: https://gist.github.com/username/id, dns:example.com) */
26
+ claimUri: string
27
+ identity: Identity
28
+ /** One or more cryptographic attestation signatures from verification services. */
29
+ sigs: DevKeytraceSignature.Main[]
30
+ /** Optional user-provided label for this claim */
31
+ comment?: string
32
+ /** Current verification status of this claim. Absent on legacy records, treated as 'verified'. */
33
+ status?: 'verified' | 'failed' | 'retracted' | (string & {})
34
+ /** Timestamp of the most recent successful re-verification by the system */
35
+ lastVerifiedAt?: string
36
+ /** Timestamp when the claim last failed re-verification or was retracted */
37
+ failedAt?: string
38
+ /** Datetime when this claim was created (ISO 8601). */
39
+ createdAt: string
40
+ /** Random one-time value embedded in the challenge text posted to the external service. Used by verifiers to confirm the proof was created specifically for this claim session. */
41
+ nonce?: string
42
+ /** Whether this claim was created during the prerelease/alpha period */
43
+ prerelease?: boolean
44
+ /** Datetime when this claim was retracted. Present only if the claim has been retracted (ISO 8601). */
45
+ retractedAt?: string
46
+ [k: string]: unknown
47
+ }
48
+
49
+ const hashMain = 'main'
50
+
51
+ export function isMain<V>(v: V) {
52
+ return is$typed(v, id, hashMain)
53
+ }
54
+
55
+ export function validateMain<V>(v: V) {
56
+ return validate<Main & V>(v, id, hashMain, true)
57
+ }
58
+
59
+ export {
60
+ type Main as Record,
61
+ isMain as isRecord,
62
+ validateMain as validateRecord,
63
+ }
64
+
65
+ /** Generic identity data for the claimed account */
66
+ export interface Identity {
67
+ $type?: 'dev.keytrace.claim#identity'
68
+ /** Primary identifier (username, domain, handle, etc.) */
69
+ subject: string
70
+ /** Avatar/profile image URL */
71
+ avatarUrl?: string
72
+ /** Profile page URL */
73
+ profileUrl?: string
74
+ /** Display name if different from subject */
75
+ displayName?: string
76
+ }
77
+
78
+ const hashIdentity = 'identity'
79
+
80
+ export function isIdentity<V>(v: V) {
81
+ return is$typed(v, id, hashIdentity)
82
+ }
83
+
84
+ export function validateIdentity<V>(v: V) {
85
+ return validate<Identity & V>(v, id, hashIdentity)
86
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
+ import { CID } from 'multiformats/cid'
6
+ import { validate as _validate } from '../../../lexicons'
7
+ import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
+
9
+ const is$typed = _is$typed,
10
+ validate = _validate
11
+ const id = 'dev.keytrace.profile'
12
+
13
+ export interface Main {
14
+ $type: 'dev.keytrace.profile'
15
+ /** Display name override for the keytrace profile. Falls back to Bluesky display name if absent. */
16
+ displayName?: string
17
+ /** Bio or description shown on the keytrace profile page. */
18
+ bio?: string
19
+ createdAt?: string
20
+ [k: string]: unknown
21
+ }
22
+
23
+ const hashMain = 'main'
24
+
25
+ export function isMain<V>(v: V) {
26
+ return is$typed(v, id, hashMain)
27
+ }
28
+
29
+ export function validateMain<V>(v: V) {
30
+ return validate<Main & V>(v, id, hashMain, true)
31
+ }
32
+
33
+ export {
34
+ type Main as Record,
35
+ isMain as isRecord,
36
+ validateMain as validateRecord,
37
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
+ import { CID } from 'multiformats/cid'
6
+ import { validate as _validate } from '../../../lexicons'
7
+ import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
+
9
+ const is$typed = _is$typed,
10
+ validate = _validate
11
+ const id = 'dev.keytrace.serverPublicKey'
12
+
13
+ export interface Main {
14
+ $type: 'dev.keytrace.serverPublicKey'
15
+ /** JWK public key as a JSON string (RFC 7517 format) */
16
+ publicJwk: string
17
+ /** Datetime from which this key is valid (ISO 8601). */
18
+ validFrom: string
19
+ /** Datetime until which this key is valid (ISO 8601). */
20
+ validUntil: string
21
+ [k: string]: unknown
22
+ }
23
+
24
+ const hashMain = 'main'
25
+
26
+ export function isMain<V>(v: V) {
27
+ return is$typed(v, id, hashMain)
28
+ }
29
+
30
+ export function validateMain<V>(v: V) {
31
+ return validate<Main & V>(v, id, hashMain, true)
32
+ }
33
+
34
+ export {
35
+ type Main as Record,
36
+ isMain as isRecord,
37
+ validateMain as validateRecord,
38
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
+ import { CID } from 'multiformats/cid'
6
+ import { validate as _validate } from '../../../lexicons'
7
+ import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
+
9
+ const is$typed = _is$typed,
10
+ validate = _validate
11
+ const id = 'dev.keytrace.signature'
12
+
13
+ /** A cryptographic signature attesting to a claim */
14
+ export interface Main {
15
+ $type?: 'dev.keytrace.signature'
16
+ /** Key identifier (e.g., date in YYYY-MM-DD format). */
17
+ kid: string
18
+ /** AT URI reference to the signing key record published by the verification service (e.g., at://did:plc:serviceaccount/dev.keytrace.serverPublicKey/2024-01-15). */
19
+ src: string
20
+ /** Datetime when the signature was created (ISO 8601). */
21
+ signedAt: string
22
+ /** The cryptographic signature (base64-encoded). */
23
+ attestation: string
24
+ /** Datetime when this signature was retracted. Present only if the signature has been retracted (ISO 8601). */
25
+ retractedAt?: string
26
+ /** Ordered list of field names included in the signed payload (e.g., ['did', 'subject', 'type', 'verifiedAt']) */
27
+ signedFields: string[]
28
+ }
29
+
30
+ const hashMain = 'main'
31
+
32
+ export function isMain<V>(v: V) {
33
+ return is$typed(v, id, hashMain)
34
+ }
35
+
36
+ export function validateMain<V>(v: V) {
37
+ return validate<Main & V>(v, id, hashMain)
38
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
+ import { CID } from 'multiformats/cid'
6
+ import { validate as _validate } from '../../../lexicons'
7
+ import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
+
9
+ const is$typed = _is$typed,
10
+ validate = _validate
11
+ const id = 'dev.keytrace.statement'
12
+
13
+ export interface Main {
14
+ $type: 'dev.keytrace.statement'
15
+ /** The statement text that was signed. */
16
+ content: string
17
+ /** Optional short subject or title for the statement. */
18
+ subject?: string
19
+ /** AT URI of the dev.keytrace.userPublicKey record whose private key produced this signature (e.g., at://did:plc:xxx/dev.keytrace.userPublicKey/3k4...) */
20
+ keyRef: string
21
+ /** Cryptographic signature of the content field, produced by the key referenced in keyRef (PGP cleartext or detached, base64-encoded binary signature). */
22
+ sig: string
23
+ /** Datetime when this statement was retracted. Present only if the statement has been retracted (ISO 8601). */
24
+ retractedAt?: string
25
+ /** Datetime when this statement was created (ISO 8601). */
26
+ createdAt: string
27
+ [k: string]: unknown
28
+ }
29
+
30
+ const hashMain = 'main'
31
+
32
+ export function isMain<V>(v: V) {
33
+ return is$typed(v, id, hashMain)
34
+ }
35
+
36
+ export function validateMain<V>(v: V) {
37
+ return validate<Main & V>(v, id, hashMain, true)
38
+ }
39
+
40
+ export {
41
+ type Main as Record,
42
+ isMain as isRecord,
43
+ validateMain as validateRecord,
44
+ }
package/types/util.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+
5
+ import { type ValidationResult } from '@atproto/lexicon'
6
+
7
+ export type OmitKey<T, K extends keyof T> = {
8
+ [K2 in keyof T as K2 extends K ? never : K2]: T[K2]
9
+ }
10
+
11
+ export type $Typed<V, T extends string = string> = V & { $type: T }
12
+ export type Un$Typed<V extends { $type?: string }> = OmitKey<V, '$type'>
13
+
14
+ export type $Type<Id extends string, Hash extends string> = Hash extends 'main'
15
+ ? Id
16
+ : `${Id}#${Hash}`
17
+
18
+ function isObject<V>(v: V): v is V & object {
19
+ return v != null && typeof v === 'object'
20
+ }
21
+
22
+ function is$type<Id extends string, Hash extends string>(
23
+ $type: unknown,
24
+ id: Id,
25
+ hash: Hash,
26
+ ): $type is $Type<Id, Hash> {
27
+ return hash === 'main'
28
+ ? $type === id
29
+ : // $type === `${id}#${hash}`
30
+ typeof $type === 'string' &&
31
+ $type.length === id.length + 1 + hash.length &&
32
+ $type.charCodeAt(id.length) === 35 /* '#' */ &&
33
+ $type.startsWith(id) &&
34
+ $type.endsWith(hash)
35
+ }
36
+
37
+ export type $TypedObject<
38
+ V,
39
+ Id extends string,
40
+ Hash extends string,
41
+ > = V extends {
42
+ $type: $Type<Id, Hash>
43
+ }
44
+ ? V
45
+ : V extends { $type?: string }
46
+ ? V extends { $type?: infer T extends $Type<Id, Hash> }
47
+ ? V & { $type: T }
48
+ : never
49
+ : V & { $type: $Type<Id, Hash> }
50
+
51
+ export function is$typed<V, Id extends string, Hash extends string>(
52
+ v: V,
53
+ id: Id,
54
+ hash: Hash,
55
+ ): v is $TypedObject<V, Id, Hash> {
56
+ return isObject(v) && '$type' in v && is$type(v.$type, id, hash)
57
+ }
58
+
59
+ export function maybe$typed<V, Id extends string, Hash extends string>(
60
+ v: V,
61
+ id: Id,
62
+ hash: Hash,
63
+ ): v is V & object & { $type?: $Type<Id, Hash> } {
64
+ return (
65
+ isObject(v) &&
66
+ ('$type' in v ? v.$type === undefined || is$type(v.$type, id, hash) : true)
67
+ )
68
+ }
69
+
70
+ export type Validator<R = unknown> = (v: unknown) => ValidationResult<R>
71
+ export type ValidatorParam<V extends Validator> =
72
+ V extends Validator<infer R> ? R : never
73
+
74
+ /**
75
+ * Utility function that allows to convert a "validate*" utility function into a
76
+ * type predicate.
77
+ */
78
+ export function asPredicate<V extends Validator>(validate: V) {
79
+ return function <T>(v: T): v is T & ValidatorParam<V> {
80
+ return validate(v).success
81
+ }
82
+ }