usedesign 0.2.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,984 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/nistoc/usedesign/schema/operation-card.schema.json",
4
+ "title": "Operation Card",
5
+ "description": "Front matter of a single operation description. See SPEC.md.",
6
+ "type": "object",
7
+ "required": [
8
+ "id",
9
+ "title",
10
+ "actors",
11
+ "maturity",
12
+ "steps",
13
+ "concurrency",
14
+ "interfaces",
15
+ "data",
16
+ "provenance",
17
+ "reversibility"
18
+ ],
19
+ "anyOf": [
20
+ {
21
+ "required": [
22
+ "scenario"
23
+ ]
24
+ },
25
+ {
26
+ "required": [
27
+ "serves_step"
28
+ ]
29
+ }
30
+ ],
31
+ "properties": {
32
+ "id": {
33
+ "description": "Stable identifier '<area>.<object>.<action>'. Never changes.",
34
+ "type": "string",
35
+ "pattern": "^[a-z0-9]+(\\.[a-z0-9-]+){2,}$"
36
+ },
37
+ "title": {
38
+ "type": "string",
39
+ "minLength": 3
40
+ },
41
+ "scenario": {
42
+ "description": "The user goal this operation is part of.",
43
+ "type": "string"
44
+ },
45
+ "serves_step": {
46
+ "description": "This operation is a helper serving a step of another operation.",
47
+ "type": "object",
48
+ "required": [
49
+ "operation",
50
+ "step",
51
+ "purpose"
52
+ ],
53
+ "properties": {
54
+ "operation": {
55
+ "type": "string"
56
+ },
57
+ "step": {
58
+ "type": "string"
59
+ },
60
+ "purpose": {
61
+ "type": "string"
62
+ }
63
+ },
64
+ "additionalProperties": false
65
+ },
66
+ "actors": {
67
+ "description": "Who may invoke the operation.",
68
+ "type": "array",
69
+ "minItems": 1,
70
+ "items": {
71
+ "type": "string"
72
+ }
73
+ },
74
+ "maturity": {
75
+ "description": "Axis A — how far the operation is built.",
76
+ "enum": [
77
+ "conceived",
78
+ "designed",
79
+ "implemented",
80
+ "tested",
81
+ "in_production",
82
+ "deprecated"
83
+ ]
84
+ },
85
+ "maturity_evidence": {
86
+ "description": "Proof for the claimed maturity level. Which keys are required depends on the level — see the conditional rules at the end of this schema.",
87
+ "type": "object",
88
+ "minProperties": 1,
89
+ "properties": {
90
+ "implemented": {
91
+ "$comment": "One path or several — a card whose operation spans an endpoint and a worker names both, and that case is already in the examples. A trailing :line is a human aid and is discarded by checkers (SPEC 5.7).",
92
+ "oneOf": [
93
+ {
94
+ "type": "string"
95
+ },
96
+ {
97
+ "type": "array",
98
+ "items": {
99
+ "type": "string"
100
+ },
101
+ "minItems": 1
102
+ }
103
+ ]
104
+ },
105
+ "tested": {
106
+ "$comment": "Advisory prose. The enforced rule is not this text but check 2: a card claiming 'tested' or above must name a test that exists and passes.",
107
+ "type": "string"
108
+ },
109
+ "deployed": {
110
+ "$comment": "Not verifiable by any checker, so it is the one claim that expires: give it a date and it can go stale, leave it bare and it never asks to be looked at again. See design/maturity-evidence.md.",
111
+ "oneOf": [
112
+ {
113
+ "type": "string"
114
+ },
115
+ {
116
+ "type": "object",
117
+ "required": [
118
+ "env",
119
+ "since"
120
+ ],
121
+ "additionalProperties": false,
122
+ "properties": {
123
+ "env": {
124
+ "type": "string"
125
+ },
126
+ "since": {
127
+ "type": "string",
128
+ "format": "date"
129
+ }
130
+ }
131
+ }
132
+ ]
133
+ }
134
+ },
135
+ "additionalProperties": false
136
+ },
137
+ "data_transition": {
138
+ "description": "Axis C — what the operation does to the record's lifecycle state. Null for read-only. `to` may be a set of states when the destination comes from the record's own history, as it does for every undo operation.",
139
+ "oneOf": [
140
+ {
141
+ "type": "null"
142
+ },
143
+ {
144
+ "type": "object",
145
+ "required": [
146
+ "from",
147
+ "to"
148
+ ],
149
+ "additionalProperties": false,
150
+ "properties": {
151
+ "from": {
152
+ "type": "string"
153
+ },
154
+ "to": {
155
+ "oneOf": [
156
+ {
157
+ "type": "string"
158
+ },
159
+ {
160
+ "type": "array",
161
+ "minItems": 2,
162
+ "items": {
163
+ "type": "string"
164
+ }
165
+ }
166
+ ]
167
+ },
168
+ "determined_by": {
169
+ "type": "string"
170
+ }
171
+ },
172
+ "if": {
173
+ "properties": {
174
+ "to": {
175
+ "type": "array"
176
+ }
177
+ },
178
+ "required": [
179
+ "to"
180
+ ]
181
+ },
182
+ "then": {
183
+ "required": [
184
+ "determined_by"
185
+ ]
186
+ }
187
+ }
188
+ ]
189
+ },
190
+ "mutates": {
191
+ "description": "Fields written when data_transition is null but the operation still writes.",
192
+ "type": "array",
193
+ "items": {
194
+ "type": "string"
195
+ }
196
+ },
197
+ "since": {
198
+ "description": "Axis D — since which migration or release the operation behaves this way.",
199
+ "type": "object",
200
+ "properties": {
201
+ "migration": {
202
+ "type": "string"
203
+ },
204
+ "release": {
205
+ "type": "string"
206
+ },
207
+ "note": {
208
+ "type": "string"
209
+ }
210
+ }
211
+ },
212
+ "steps": {
213
+ "description": "Axis B — stages of execution, including failure branches.",
214
+ "type": "array",
215
+ "minItems": 1,
216
+ "items": {
217
+ "type": "object",
218
+ "required": [
219
+ "id",
220
+ "text"
221
+ ],
222
+ "properties": {
223
+ "id": {
224
+ "type": "string",
225
+ "pattern": "^s[0-9]+-[a-z0-9-]+$"
226
+ },
227
+ "text": {
228
+ "type": "string"
229
+ },
230
+ "on_violation": {
231
+ "type": "object",
232
+ "required": [
233
+ "error"
234
+ ],
235
+ "properties": {
236
+ "error": {
237
+ "type": "string"
238
+ },
239
+ "http": {
240
+ "type": "integer",
241
+ "minimum": 100,
242
+ "maximum": 599
243
+ },
244
+ "jsonrpc_code": {
245
+ "type": "integer"
246
+ },
247
+ "payload": {
248
+ "type": "array",
249
+ "items": {
250
+ "type": "string"
251
+ }
252
+ }
253
+ },
254
+ "additionalProperties": false
255
+ },
256
+ "emits_notice": {
257
+ "type": "string"
258
+ },
259
+ "rationale": {
260
+ "type": "string"
261
+ },
262
+ "source": {
263
+ "type": "string",
264
+ "pattern": ".+:[0-9]+"
265
+ }
266
+ },
267
+ "additionalProperties": false
268
+ }
269
+ },
270
+ "concurrency": {
271
+ "type": "object",
272
+ "required": [
273
+ "mode",
274
+ "source"
275
+ ],
276
+ "properties": {
277
+ "mode": {
278
+ "enum": [
279
+ "etag_required",
280
+ "idempotency_by_header",
281
+ "idempotency_by_formula",
282
+ "none_by_design"
283
+ ]
284
+ },
285
+ "rationale": {
286
+ "type": "string"
287
+ },
288
+ "formula": {
289
+ "type": "string"
290
+ },
291
+ "on_duplicate": {
292
+ "type": "object"
293
+ },
294
+ "source": {
295
+ "type": "string"
296
+ }
297
+ },
298
+ "allOf": [
299
+ {
300
+ "if": {
301
+ "properties": {
302
+ "mode": {
303
+ "not": {
304
+ "const": "etag_required"
305
+ }
306
+ }
307
+ }
308
+ },
309
+ "then": {
310
+ "required": [
311
+ "rationale"
312
+ ]
313
+ }
314
+ },
315
+ {
316
+ "if": {
317
+ "properties": {
318
+ "mode": {
319
+ "const": "idempotency_by_formula"
320
+ }
321
+ },
322
+ "required": [
323
+ "mode"
324
+ ]
325
+ },
326
+ "then": {
327
+ "required": [
328
+ "formula",
329
+ "on_duplicate"
330
+ ]
331
+ }
332
+ }
333
+ ]
334
+ },
335
+ "quota": {
336
+ "description": "Present when call frequency is limited.",
337
+ "type": "object",
338
+ "required": [
339
+ "scheme",
340
+ "applies_to",
341
+ "on_exceeded"
342
+ ],
343
+ "properties": {
344
+ "scheme": {
345
+ "type": "string"
346
+ },
347
+ "categories": {
348
+ "type": "object",
349
+ "additionalProperties": {
350
+ "type": "object",
351
+ "required": [
352
+ "bucket",
353
+ "window_min"
354
+ ],
355
+ "properties": {
356
+ "bucket": {
357
+ "type": "integer",
358
+ "minimum": 1
359
+ },
360
+ "window_min": {
361
+ "type": "number",
362
+ "exclusiveMinimum": 0
363
+ }
364
+ }
365
+ }
366
+ },
367
+ "applies_to": {
368
+ "type": "string"
369
+ },
370
+ "on_exceeded": {
371
+ "type": "object"
372
+ },
373
+ "storage": {
374
+ "type": "string"
375
+ }
376
+ }
377
+ },
378
+ "async_execution": {
379
+ "description": "Axis E — required when the response does not mean the work is done.",
380
+ "type": "object",
381
+ "required": [
382
+ "job_states",
383
+ "terminal",
384
+ "worker",
385
+ "observe_via"
386
+ ],
387
+ "properties": {
388
+ "job_states": {
389
+ "type": "array",
390
+ "minItems": 2,
391
+ "items": {
392
+ "type": "string"
393
+ }
394
+ },
395
+ "terminal": {
396
+ "type": "array",
397
+ "minItems": 1,
398
+ "items": {
399
+ "type": "string"
400
+ }
401
+ },
402
+ "worker": {
403
+ "type": "string"
404
+ },
405
+ "observe_via": {
406
+ "type": "string"
407
+ },
408
+ "cancel_via": {
409
+ "type": "string"
410
+ },
411
+ "s2s_dependency": {
412
+ "type": "object",
413
+ "required": [
414
+ "service",
415
+ "purpose",
416
+ "failure_mode"
417
+ ],
418
+ "properties": {
419
+ "service": {
420
+ "type": "string"
421
+ },
422
+ "purpose": {
423
+ "type": "string"
424
+ },
425
+ "failure_mode": {
426
+ "type": "string"
427
+ }
428
+ }
429
+ }
430
+ }
431
+ },
432
+ "interfaces": {
433
+ "description": "At least one. Each declares its transport; rules may be overridden per transport.",
434
+ "type": "object",
435
+ "minProperties": 1,
436
+ "additionalProperties": {
437
+ "type": "object",
438
+ "required": [
439
+ "transport"
440
+ ],
441
+ "properties": {
442
+ "transport": {
443
+ "enum": [
444
+ "http_rest",
445
+ "json_rpc",
446
+ "in_process",
447
+ "ui"
448
+ ]
449
+ },
450
+ "method": {
451
+ "type": "string"
452
+ },
453
+ "path": {
454
+ "type": "string"
455
+ },
456
+ "headers_required": {
457
+ "type": "array",
458
+ "items": {
459
+ "type": "string"
460
+ }
461
+ },
462
+ "responses": {
463
+ "type": "array",
464
+ "description": "Every status the operation can return, including those named by on_violation and outcomes.",
465
+ "items": {
466
+ "type": "integer",
467
+ "minimum": 100,
468
+ "maximum": 599
469
+ }
470
+ },
471
+ "contract_version": {
472
+ "type": "string"
473
+ },
474
+ "tool": {
475
+ "type": "string"
476
+ },
477
+ "scope_required": {
478
+ "type": "string"
479
+ },
480
+ "screen": {
481
+ "type": "string"
482
+ },
483
+ "control": {
484
+ "type": "string"
485
+ },
486
+ "covers_steps": {
487
+ "type": "array",
488
+ "items": {
489
+ "type": "string"
490
+ }
491
+ },
492
+ "quota": {
493
+ "type": "object"
494
+ },
495
+ "concurrency": {
496
+ "type": "object"
497
+ },
498
+ "actors": {
499
+ "type": "array",
500
+ "items": {
501
+ "type": "string"
502
+ }
503
+ },
504
+ "note": {
505
+ "type": "string"
506
+ },
507
+ "source": {
508
+ "type": "string"
509
+ },
510
+ "parameters": {
511
+ "description": "Parameters whose declared meaning is not their real one — what you sent is not what was used.",
512
+ "type": "array",
513
+ "minItems": 1,
514
+ "items": {
515
+ "type": "object",
516
+ "required": [
517
+ "name",
518
+ "handling"
519
+ ],
520
+ "additionalProperties": false,
521
+ "properties": {
522
+ "name": {
523
+ "type": "string"
524
+ },
525
+ "handling": {
526
+ "enum": [
527
+ "clamped",
528
+ "defaulted",
529
+ "normalised",
530
+ "decorative"
531
+ ]
532
+ },
533
+ "range": {
534
+ "type": "array",
535
+ "minItems": 2,
536
+ "maxItems": 2,
537
+ "items": {
538
+ "type": "integer"
539
+ }
540
+ },
541
+ "note": {
542
+ "type": "string"
543
+ }
544
+ }
545
+ }
546
+ },
547
+ "dispatch": {
548
+ "description": "Several operations share this route and are told apart by a request field.",
549
+ "type": "object",
550
+ "required": [
551
+ "by",
552
+ "value"
553
+ ],
554
+ "additionalProperties": false,
555
+ "properties": {
556
+ "by": {
557
+ "type": "string"
558
+ },
559
+ "value": {
560
+ "type": "string"
561
+ },
562
+ "note": {
563
+ "type": "string"
564
+ }
565
+ }
566
+ }
567
+ }
568
+ }
569
+ },
570
+ "data": {
571
+ "type": "object",
572
+ "required": [
573
+ "entities"
574
+ ],
575
+ "properties": {
576
+ "entities": {
577
+ "type": "array",
578
+ "minItems": 1,
579
+ "items": {
580
+ "type": "string"
581
+ }
582
+ },
583
+ "fields_touched": {
584
+ "type": "array",
585
+ "items": {
586
+ "type": "string"
587
+ }
588
+ },
589
+ "migrations": {
590
+ "type": "array",
591
+ "items": {
592
+ "type": "string"
593
+ }
594
+ }
595
+ }
596
+ },
597
+ "provenance": {
598
+ "description": "What is recorded about who did what, or 'none' for read-only operations.",
599
+ "oneOf": [
600
+ {
601
+ "const": "none"
602
+ },
603
+ {
604
+ "type": "object",
605
+ "required": [
606
+ "activity_kind"
607
+ ],
608
+ "properties": {
609
+ "activity_kind": {
610
+ "type": "string"
611
+ },
612
+ "attributed_to": {
613
+ "type": "string"
614
+ },
615
+ "on_failure": {
616
+ "type": "string"
617
+ }
618
+ }
619
+ }
620
+ ]
621
+ },
622
+ "reversibility": {
623
+ "description": "Whether the effect can be undone, and by what. `not_applicable` for operations that produce no effect — a read has nothing to reverse, and calling it 'reversible' answers a different question than the one asked.",
624
+ "oneOf": [
625
+ {
626
+ "enum": [
627
+ "reversible",
628
+ "irreversible",
629
+ "not_applicable"
630
+ ]
631
+ },
632
+ {
633
+ "type": "object",
634
+ "required": [
635
+ "reversible_via"
636
+ ],
637
+ "properties": {
638
+ "reversible_via": {
639
+ "type": "string"
640
+ },
641
+ "note": {
642
+ "type": "string"
643
+ }
644
+ },
645
+ "additionalProperties": false
646
+ }
647
+ ]
648
+ },
649
+ "sensitivity": {
650
+ "type": "object",
651
+ "properties": {
652
+ "response_contains_secret": {
653
+ "type": "boolean"
654
+ },
655
+ "disclosure": {
656
+ "enum": [
657
+ "one_time",
658
+ "repeatable",
659
+ "never"
660
+ ]
661
+ },
662
+ "storage": {
663
+ "type": "string"
664
+ },
665
+ "logging_rule": {
666
+ "type": "string"
667
+ }
668
+ }
669
+ },
670
+ "consumer_boundary": {
671
+ "description": "Present when the owning scenario belongs to another system.",
672
+ "type": "object",
673
+ "required": [
674
+ "owner",
675
+ "our_role"
676
+ ],
677
+ "properties": {
678
+ "owner": {
679
+ "type": "string"
680
+ },
681
+ "our_role": {
682
+ "type": "string"
683
+ },
684
+ "adr": {
685
+ "type": "string"
686
+ }
687
+ }
688
+ },
689
+ "taxonomy_refs": {
690
+ "type": "array",
691
+ "items": {
692
+ "type": "string"
693
+ }
694
+ },
695
+ "tests": {
696
+ "type": "array",
697
+ "items": {
698
+ "type": "object",
699
+ "required": [
700
+ "id",
701
+ "covers",
702
+ "level"
703
+ ],
704
+ "properties": {
705
+ "id": {
706
+ "type": "string"
707
+ },
708
+ "covers": {
709
+ "oneOf": [
710
+ {
711
+ "type": "string"
712
+ },
713
+ {
714
+ "type": "array",
715
+ "items": {
716
+ "type": "string"
717
+ }
718
+ }
719
+ ]
720
+ },
721
+ "level": {
722
+ "enum": [
723
+ "unit",
724
+ "integration",
725
+ "ui",
726
+ "contract"
727
+ ]
728
+ }
729
+ },
730
+ "additionalProperties": false
731
+ }
732
+ },
733
+ "coverage_gaps": {
734
+ "type": "array",
735
+ "items": {
736
+ "type": "object",
737
+ "required": [
738
+ "step",
739
+ "gap"
740
+ ],
741
+ "properties": {
742
+ "step": {
743
+ "type": "string"
744
+ },
745
+ "gap": {
746
+ "type": "string"
747
+ }
748
+ },
749
+ "additionalProperties": false
750
+ }
751
+ },
752
+ "adr": {
753
+ "type": "array",
754
+ "items": {
755
+ "type": "string"
756
+ }
757
+ },
758
+ "outcomes": {
759
+ "description": "Terminal outcomes that are neither violations nor the single default success. Present only when an operation has more than one and they differ in shape.",
760
+ "type": "array",
761
+ "minItems": 2,
762
+ "items": {
763
+ "type": "object",
764
+ "required": [
765
+ "id",
766
+ "means"
767
+ ],
768
+ "additionalProperties": false,
769
+ "properties": {
770
+ "id": {
771
+ "type": "string",
772
+ "pattern": "^[a-z0-9_]+$"
773
+ },
774
+ "means": {
775
+ "type": "string"
776
+ },
777
+ "http": {
778
+ "type": "integer",
779
+ "minimum": 100,
780
+ "maximum": 599
781
+ },
782
+ "carries": {
783
+ "type": "array",
784
+ "items": {
785
+ "type": "string"
786
+ }
787
+ },
788
+ "rationale": {
789
+ "type": "string"
790
+ }
791
+ }
792
+ }
793
+ },
794
+ "continuation": {
795
+ "description": "Axis F — the operation stops and cannot continue until somebody decides. Distinct from async_execution: nothing progresses on its own, and there is nothing to observe.",
796
+ "type": "object",
797
+ "required": [
798
+ "after",
799
+ "resumed_by",
800
+ "carries"
801
+ ],
802
+ "additionalProperties": false,
803
+ "properties": {
804
+ "after": {
805
+ "type": "string",
806
+ "description": "The outcome that suspends the operation."
807
+ },
808
+ "resumed_by": {
809
+ "enum": [
810
+ "human",
811
+ "caller"
812
+ ]
813
+ },
814
+ "carries": {
815
+ "type": "string",
816
+ "description": "What the resuming call must bring."
817
+ },
818
+ "same_operation": {
819
+ "type": "boolean"
820
+ },
821
+ "rationale": {
822
+ "type": "string"
823
+ }
824
+ }
825
+ },
826
+ "per_item": {
827
+ "description": "Present when the operation processes a list and a failure on one item does not stop the others. Per-item failures carry no status of their own — they are data inside somebody else's success.",
828
+ "type": "object",
829
+ "required": [
830
+ "applies_to",
831
+ "independent",
832
+ "reported_in"
833
+ ],
834
+ "additionalProperties": false,
835
+ "properties": {
836
+ "applies_to": {
837
+ "type": "string"
838
+ },
839
+ "independent": {
840
+ "type": "boolean"
841
+ },
842
+ "reported_in": {
843
+ "type": "string"
844
+ },
845
+ "failures": {
846
+ "type": "array",
847
+ "items": {
848
+ "type": "object",
849
+ "required": [
850
+ "code",
851
+ "means"
852
+ ],
853
+ "additionalProperties": false,
854
+ "properties": {
855
+ "code": {
856
+ "type": "string"
857
+ },
858
+ "means": {
859
+ "type": "string"
860
+ }
861
+ }
862
+ }
863
+ },
864
+ "rationale": {
865
+ "type": "string"
866
+ }
867
+ }
868
+ }
869
+ },
870
+ "allOf": [
871
+ {
872
+ "$comment": "Claiming 'implemented' or beyond requires pointing at the implementation.",
873
+ "if": {
874
+ "properties": {
875
+ "maturity": {
876
+ "enum": [
877
+ "implemented",
878
+ "tested",
879
+ "in_production",
880
+ "deprecated"
881
+ ]
882
+ }
883
+ },
884
+ "required": [
885
+ "maturity"
886
+ ]
887
+ },
888
+ "then": {
889
+ "required": [
890
+ "maturity_evidence"
891
+ ],
892
+ "properties": {
893
+ "maturity_evidence": {
894
+ "required": [
895
+ "implemented"
896
+ ]
897
+ }
898
+ }
899
+ }
900
+ },
901
+ {
902
+ "$comment": "Claiming 'tested' or beyond requires at least one recorded test and a tested note.",
903
+ "if": {
904
+ "properties": {
905
+ "maturity": {
906
+ "enum": [
907
+ "tested",
908
+ "in_production"
909
+ ]
910
+ }
911
+ },
912
+ "required": [
913
+ "maturity"
914
+ ]
915
+ },
916
+ "then": {
917
+ "properties": {
918
+ "tests": {
919
+ "minItems": 1
920
+ },
921
+ "maturity_evidence": {
922
+ "required": [
923
+ "tested"
924
+ ]
925
+ }
926
+ },
927
+ "required": [
928
+ "tests"
929
+ ]
930
+ }
931
+ },
932
+ {
933
+ "$comment": "Claiming 'in_production' requires naming the environment it runs in.",
934
+ "if": {
935
+ "properties": {
936
+ "maturity": {
937
+ "const": "in_production"
938
+ }
939
+ },
940
+ "required": [
941
+ "maturity"
942
+ ]
943
+ },
944
+ "then": {
945
+ "properties": {
946
+ "maturity_evidence": {
947
+ "required": [
948
+ "deployed"
949
+ ]
950
+ }
951
+ }
952
+ }
953
+ },
954
+ {
955
+ "$comment": "A write that does not move lifecycle state must say what it changes.",
956
+ "if": {
957
+ "properties": {
958
+ "data_transition": {
959
+ "type": "null"
960
+ }
961
+ },
962
+ "required": [
963
+ "data_transition"
964
+ ]
965
+ },
966
+ "then": {
967
+ "anyOf": [
968
+ {
969
+ "required": [
970
+ "mutates"
971
+ ]
972
+ },
973
+ {
974
+ "properties": {
975
+ "provenance": {
976
+ "const": "none"
977
+ }
978
+ }
979
+ }
980
+ ]
981
+ }
982
+ }
983
+ ]
984
+ }