@ketch.to/schema 0.0.0-stage → 0.1.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.
@@ -0,0 +1,601 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://ketch.to/schemas/card.json",
4
+ "title": "Ketch card schema",
5
+ "description": "The one data structure in Ketch. A card is a question an agent asks a person; it has exactly one outcome.",
6
+ "$defs": {
7
+ "Duration": {
8
+ "type": "string",
9
+ "description": "Relative duration: <integer><unit>, unit one of m (minutes), h (hours), d (days), w (weeks).",
10
+ "pattern": "^[1-9][0-9]{0,4}[mhdw]$",
11
+ "examples": [
12
+ "1h",
13
+ "3d",
14
+ "14d",
15
+ "2w"
16
+ ]
17
+ },
18
+ "CardId": {
19
+ "type": "string",
20
+ "pattern": "^card_[0-9A-HJKMNP-TV-Z]{26}$",
21
+ "description": "Server-assigned. 'card_' + ULID."
22
+ },
23
+ "ActionId": {
24
+ "type": "string",
25
+ "pattern": "^[a-z][a-z0-9_]{0,31}$",
26
+ "description": "Producer-chosen id, echoed back in the outcome. 'snooze' and 'not_good' are reserved and always available."
27
+ },
28
+ "ActionStyle": {
29
+ "type": "string",
30
+ "enum": [
31
+ "positive",
32
+ "snooze",
33
+ "neutral"
34
+ ],
35
+ "description": "positive: swipe-right target, accent colour. snooze: tap = 1h, hold = day slider. neutral: plain button."
36
+ },
37
+ "Action": {
38
+ "type": "object",
39
+ "additionalProperties": false,
40
+ "required": [
41
+ "id",
42
+ "label"
43
+ ],
44
+ "properties": {
45
+ "id": {
46
+ "$ref": "#/$defs/ActionId"
47
+ },
48
+ "label": {
49
+ "type": "string",
50
+ "minLength": 1,
51
+ "maxLength": 24
52
+ },
53
+ "style": {
54
+ "$ref": "#/$defs/ActionStyle",
55
+ "default": "neutral"
56
+ },
57
+ "input": {
58
+ "type": "string",
59
+ "enum": [
60
+ "optional",
61
+ "required"
62
+ ],
63
+ "description": "Open a text sheet before posting this action."
64
+ },
65
+ "snooze_for": {
66
+ "$ref": "#/$defs/Duration",
67
+ "description": "Only for style 'snooze': the default snooze on tap. Defaults to 1h."
68
+ }
69
+ }
70
+ },
71
+ "Actions": {
72
+ "type": "array",
73
+ "minItems": 1,
74
+ "maxItems": 4,
75
+ "items": {
76
+ "$ref": "#/$defs/Action"
77
+ }
78
+ },
79
+ "Source": {
80
+ "type": "object",
81
+ "additionalProperties": false,
82
+ "properties": {
83
+ "app": {
84
+ "type": "string",
85
+ "minLength": 1,
86
+ "maxLength": 40,
87
+ "description": "Defaults to the API key's name."
88
+ },
89
+ "context": {
90
+ "type": "string",
91
+ "minLength": 1,
92
+ "maxLength": 120
93
+ }
94
+ }
95
+ },
96
+ "Input": {
97
+ "type": "object",
98
+ "additionalProperties": false,
99
+ "properties": {
100
+ "placeholder": {
101
+ "type": "string",
102
+ "maxLength": 80
103
+ },
104
+ "spawns": {
105
+ "type": "boolean",
106
+ "default": false,
107
+ "description": "Each entry the person types becomes a new card (event card.spawned)."
108
+ }
109
+ }
110
+ },
111
+ "Recurrence": {
112
+ "type": "object",
113
+ "additionalProperties": false,
114
+ "required": [
115
+ "every"
116
+ ],
117
+ "properties": {
118
+ "every": {
119
+ "$ref": "#/$defs/Duration"
120
+ }
121
+ }
122
+ },
123
+ "Kind": {
124
+ "type": "string",
125
+ "enum": [
126
+ "do",
127
+ "choose",
128
+ "checkin",
129
+ "decide",
130
+ "read",
131
+ "recap"
132
+ ],
133
+ "description": "Preset. Fills in default actions (and recurrence/input for some) when the producer omits them. Never requires app code."
134
+ },
135
+ "Priority": {
136
+ "type": "integer",
137
+ "minimum": 1,
138
+ "maximum": 5,
139
+ "default": 3,
140
+ "description": "1 urgent … 5 ambient. ≤2 pushes a notification."
141
+ },
142
+ "Link": {
143
+ "type": "string",
144
+ "pattern": "^(https://|shortcuts://)[^\\s]+$",
145
+ "maxLength": 2048,
146
+ "description": "Opened with the system. https:// or shortcuts:// only."
147
+ },
148
+ "Meta": {
149
+ "type": "object",
150
+ "description": "Producer's own bag. Echoed back untouched in webhooks and outcomes."
151
+ },
152
+ "CardFields": {
153
+ "type": "object",
154
+ "properties": {
155
+ "title": {
156
+ "type": "string",
157
+ "minLength": 1,
158
+ "maxLength": 140
159
+ },
160
+ "body": {
161
+ "type": "string",
162
+ "maxLength": 4000,
163
+ "description": "Plain text or light markdown."
164
+ },
165
+ "source": {
166
+ "$ref": "#/$defs/Source"
167
+ },
168
+ "link": {
169
+ "$ref": "#/$defs/Link"
170
+ },
171
+ "priority": {
172
+ "$ref": "#/$defs/Priority"
173
+ },
174
+ "kind": {
175
+ "$ref": "#/$defs/Kind",
176
+ "default": "do"
177
+ },
178
+ "actions": {
179
+ "$ref": "#/$defs/Actions"
180
+ },
181
+ "input": {
182
+ "$ref": "#/$defs/Input"
183
+ },
184
+ "recurrence": {
185
+ "$ref": "#/$defs/Recurrence"
186
+ },
187
+ "show_after": {
188
+ "type": [
189
+ "string",
190
+ "null"
191
+ ],
192
+ "format": "date-time",
193
+ "description": "Hidden until then. Snooze writes this."
194
+ },
195
+ "expires_at": {
196
+ "type": [
197
+ "string",
198
+ "null"
199
+ ],
200
+ "format": "date-time",
201
+ "description": "Retired automatically with outcome action 'expired'."
202
+ },
203
+ "meta": {
204
+ "$ref": "#/$defs/Meta"
205
+ }
206
+ }
207
+ },
208
+ "NewCard": {
209
+ "description": "What a producer POSTs. Everything but title is optional; kind fills in actions.",
210
+ "allOf": [
211
+ {
212
+ "$ref": "#/$defs/CardFields"
213
+ }
214
+ ],
215
+ "type": "object",
216
+ "additionalProperties": false,
217
+ "required": [
218
+ "title"
219
+ ],
220
+ "properties": {
221
+ "title": true,
222
+ "body": true,
223
+ "source": true,
224
+ "link": true,
225
+ "priority": true,
226
+ "kind": true,
227
+ "actions": true,
228
+ "input": true,
229
+ "recurrence": true,
230
+ "show_after": true,
231
+ "expires_at": true,
232
+ "meta": true
233
+ }
234
+ },
235
+ "CardPatch": {
236
+ "description": "What PATCH /cards/:id accepts. Producer-side edits only.",
237
+ "type": "object",
238
+ "additionalProperties": false,
239
+ "minProperties": 1,
240
+ "properties": {
241
+ "title": {
242
+ "type": "string",
243
+ "minLength": 1,
244
+ "maxLength": 140
245
+ },
246
+ "body": {
247
+ "type": [
248
+ "string",
249
+ "null"
250
+ ],
251
+ "maxLength": 4000
252
+ },
253
+ "actions": {
254
+ "$ref": "#/$defs/Actions"
255
+ },
256
+ "show_after": {
257
+ "type": [
258
+ "string",
259
+ "null"
260
+ ],
261
+ "format": "date-time"
262
+ },
263
+ "priority": {
264
+ "$ref": "#/$defs/Priority"
265
+ },
266
+ "link": {
267
+ "anyOf": [
268
+ {
269
+ "$ref": "#/$defs/Link"
270
+ },
271
+ {
272
+ "type": "null"
273
+ }
274
+ ]
275
+ },
276
+ "expires_at": {
277
+ "type": [
278
+ "string",
279
+ "null"
280
+ ],
281
+ "format": "date-time"
282
+ },
283
+ "meta": {
284
+ "$ref": "#/$defs/Meta"
285
+ }
286
+ }
287
+ },
288
+ "Status": {
289
+ "type": "string",
290
+ "enum": [
291
+ "open",
292
+ "snoozed",
293
+ "done"
294
+ ],
295
+ "description": "open: in the queue. snoozed: open but show_after is in the future. done: retired (acted, deleted or expired)."
296
+ },
297
+ "Outcome": {
298
+ "type": "object",
299
+ "additionalProperties": false,
300
+ "required": [
301
+ "action",
302
+ "text",
303
+ "at",
304
+ "snoozed_until"
305
+ ],
306
+ "properties": {
307
+ "action": {
308
+ "$ref": "#/$defs/ActionId"
309
+ },
310
+ "text": {
311
+ "type": [
312
+ "string",
313
+ "null"
314
+ ],
315
+ "maxLength": 4000
316
+ },
317
+ "at": {
318
+ "type": "string",
319
+ "format": "date-time"
320
+ },
321
+ "snoozed_until": {
322
+ "type": [
323
+ "string",
324
+ "null"
325
+ ],
326
+ "format": "date-time"
327
+ }
328
+ }
329
+ },
330
+ "OutcomeInput": {
331
+ "description": "What the app POSTs to /cards/:id/outcome. One per card (snoozes excepted).",
332
+ "type": "object",
333
+ "additionalProperties": false,
334
+ "required": [
335
+ "action"
336
+ ],
337
+ "properties": {
338
+ "action": {
339
+ "$ref": "#/$defs/ActionId"
340
+ },
341
+ "text": {
342
+ "type": [
343
+ "string",
344
+ "null"
345
+ ],
346
+ "maxLength": 4000
347
+ },
348
+ "snoozed_until": {
349
+ "type": [
350
+ "string",
351
+ "null"
352
+ ],
353
+ "format": "date-time",
354
+ "description": "Required when action is 'snooze' (or any snooze-style action)."
355
+ },
356
+ "spawn": {
357
+ "type": "array",
358
+ "maxItems": 20,
359
+ "items": {
360
+ "type": "string",
361
+ "minLength": 1,
362
+ "maxLength": 140
363
+ },
364
+ "description": "Titles of new cards to create from the card's spawning input."
365
+ }
366
+ }
367
+ },
368
+ "Card": {
369
+ "description": "A card as the server returns it.",
370
+ "allOf": [
371
+ {
372
+ "$ref": "#/$defs/CardFields"
373
+ }
374
+ ],
375
+ "type": "object",
376
+ "additionalProperties": false,
377
+ "required": [
378
+ "id",
379
+ "title",
380
+ "source",
381
+ "priority",
382
+ "kind",
383
+ "actions",
384
+ "status",
385
+ "created_at",
386
+ "updated_at"
387
+ ],
388
+ "properties": {
389
+ "id": {
390
+ "$ref": "#/$defs/CardId"
391
+ },
392
+ "title": true,
393
+ "body": true,
394
+ "link": true,
395
+ "priority": true,
396
+ "kind": true,
397
+ "actions": true,
398
+ "input": true,
399
+ "recurrence": true,
400
+ "show_after": true,
401
+ "expires_at": true,
402
+ "meta": true,
403
+ "source": {
404
+ "type": "object",
405
+ "additionalProperties": false,
406
+ "required": [
407
+ "app"
408
+ ],
409
+ "properties": {
410
+ "app": {
411
+ "type": "string",
412
+ "minLength": 1,
413
+ "maxLength": 40
414
+ },
415
+ "context": {
416
+ "type": "string",
417
+ "minLength": 1,
418
+ "maxLength": 120
419
+ }
420
+ }
421
+ },
422
+ "status": {
423
+ "$ref": "#/$defs/Status"
424
+ },
425
+ "parent_id": {
426
+ "anyOf": [
427
+ {
428
+ "$ref": "#/$defs/CardId"
429
+ },
430
+ {
431
+ "type": "null"
432
+ }
433
+ ],
434
+ "description": "Set on cards spawned from another card's input."
435
+ },
436
+ "outcome": {
437
+ "anyOf": [
438
+ {
439
+ "$ref": "#/$defs/Outcome"
440
+ },
441
+ {
442
+ "type": "null"
443
+ }
444
+ ],
445
+ "description": "The last outcome, if any. Recurring cards keep their latest."
446
+ },
447
+ "created_at": {
448
+ "type": "string",
449
+ "format": "date-time"
450
+ },
451
+ "updated_at": {
452
+ "type": "string",
453
+ "format": "date-time"
454
+ },
455
+ "retired_at": {
456
+ "type": [
457
+ "string",
458
+ "null"
459
+ ],
460
+ "format": "date-time"
461
+ }
462
+ }
463
+ },
464
+ "WebhookEvent": {
465
+ "type": "string",
466
+ "enum": [
467
+ "card.acted",
468
+ "card.snoozed",
469
+ "card.spawned"
470
+ ]
471
+ },
472
+ "NewWebhook": {
473
+ "type": "object",
474
+ "additionalProperties": false,
475
+ "required": [
476
+ "url",
477
+ "secret",
478
+ "events"
479
+ ],
480
+ "properties": {
481
+ "url": {
482
+ "type": "string",
483
+ "pattern": "^https?://[^\\s]+$",
484
+ "maxLength": 2048
485
+ },
486
+ "secret": {
487
+ "type": "string",
488
+ "minLength": 16,
489
+ "maxLength": 256
490
+ },
491
+ "events": {
492
+ "type": "array",
493
+ "minItems": 1,
494
+ "uniqueItems": true,
495
+ "items": {
496
+ "$ref": "#/$defs/WebhookEvent"
497
+ }
498
+ }
499
+ }
500
+ },
501
+ "Webhook": {
502
+ "type": "object",
503
+ "additionalProperties": false,
504
+ "required": [
505
+ "id",
506
+ "url",
507
+ "events",
508
+ "created_at"
509
+ ],
510
+ "properties": {
511
+ "id": {
512
+ "type": "string",
513
+ "pattern": "^wh_[0-9A-HJKMNP-TV-Z]{26}$"
514
+ },
515
+ "url": {
516
+ "type": "string"
517
+ },
518
+ "events": {
519
+ "type": "array",
520
+ "items": {
521
+ "$ref": "#/$defs/WebhookEvent"
522
+ }
523
+ },
524
+ "created_at": {
525
+ "type": "string",
526
+ "format": "date-time"
527
+ }
528
+ }
529
+ },
530
+ "WebhookPayload": {
531
+ "description": "Body of every webhook POST. HMAC-SHA256 hex of the raw body, keyed with the webhook secret, in X-Ketch-Signature.",
532
+ "type": "object",
533
+ "additionalProperties": false,
534
+ "required": [
535
+ "id",
536
+ "event",
537
+ "at"
538
+ ],
539
+ "properties": {
540
+ "id": {
541
+ "type": "string",
542
+ "description": "Delivery id. Same id on every retry; use it to dedupe."
543
+ },
544
+ "event": {
545
+ "$ref": "#/$defs/WebhookEvent"
546
+ },
547
+ "at": {
548
+ "type": "string",
549
+ "format": "date-time"
550
+ },
551
+ "card": {
552
+ "$ref": "#/$defs/Card"
553
+ },
554
+ "outcome": {
555
+ "$ref": "#/$defs/Outcome"
556
+ },
557
+ "parent": {
558
+ "$ref": "#/$defs/Card"
559
+ }
560
+ },
561
+ "oneOf": [
562
+ {
563
+ "properties": {
564
+ "event": {
565
+ "const": "card.acted"
566
+ },
567
+ "parent": false
568
+ },
569
+ "required": [
570
+ "card",
571
+ "outcome"
572
+ ]
573
+ },
574
+ {
575
+ "properties": {
576
+ "event": {
577
+ "const": "card.snoozed"
578
+ },
579
+ "parent": false
580
+ },
581
+ "required": [
582
+ "card",
583
+ "outcome"
584
+ ]
585
+ },
586
+ {
587
+ "properties": {
588
+ "event": {
589
+ "const": "card.spawned"
590
+ },
591
+ "outcome": false
592
+ },
593
+ "required": [
594
+ "parent",
595
+ "card"
596
+ ]
597
+ }
598
+ ]
599
+ }
600
+ }
601
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Fixtures: realistic producer payloads, one per kind, plus edge cases.
3
+ * Used by the schema tests, the API tests and the docs. Each is a NewCard,
4
+ * i.e. exactly what a producer POSTs to /cards.
5
+ */
6
+ import type { Kind, NewCard } from "../src/types.js";
7
+ export declare const doCard: NewCard;
8
+ export declare const chooseCard: NewCard;
9
+ export declare const checkinCard: NewCard;
10
+ export declare const decideCard: NewCard;
11
+ export declare const readCard: NewCard;
12
+ export declare const recapCard: NewCard;
13
+ /** Explicit actions override the preset; snooze_for sets the tap default. */
14
+ export declare const customActionsCard: NewCard;
15
+ /** Minimal: title only. Kind, actions, priority all come from defaults. */
16
+ export declare const minimalCard: NewCard;
17
+ /** Email-in shape: subject = title, body = body, link = message. */
18
+ export declare const emailInCard: NewCard;
19
+ /** Hidden until the morning; a producer-side snooze. */
20
+ export declare const scheduledCard: NewCard;
21
+ export declare const byKind: Record<Kind, NewCard>;
22
+ export declare const validCards: Record<string, NewCard>;
23
+ /** Each must fail validateNewCard; `path` is the expected first error path. */
24
+ export declare const invalidCards: Record<string, {
25
+ card: unknown;
26
+ path: string;
27
+ }>;