-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgenerate.py
More file actions
1573 lines (1445 loc) · 71.4 KB
/
generate.py
File metadata and controls
1573 lines (1445 loc) · 71.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
"""Generate the Delimit documentation site from templates and change type metadata."""
import os
import html
# ── Change Type Metadata ────────────────────────────────────────────────────
CHANGE_TYPES = [
# Breaking changes (17)
{
"id": "endpoint-removed",
"enum": "ENDPOINT_REMOVED",
"value": "endpoint_removed",
"title": "Endpoint Removed",
"breaking": True,
"severity": "high",
"description": "An entire API endpoint (URL path) has been removed from the specification. All consumers calling this endpoint will receive 404 errors.",
"why_breaking": "Any client that sends requests to the removed endpoint will immediately fail. There is no graceful fallback -- the server will return a 404 Not Found or similar error, breaking all integrations that depend on it.",
"detection": "Delimit compares the <code>paths</code> keys between the old and new OpenAPI specs. Any path present in the old spec but absent in the new spec is flagged as <code>endpoint_removed</code>.",
"before": '''paths:
/users:
get:
summary: List users
/users/{id}:
get:
summary: Get user by ID''',
"after": '''paths:
/users:
get:
summary: List users
# /users/{id} has been removed''',
"migration": "1. Announce the deprecation with a timeline (e.g., 90 days).\n2. Add a <code>deprecated: true</code> flag to the endpoint first.\n3. Return <code>410 Gone</code> with a body indicating the replacement.\n4. Provide a replacement endpoint or migration guide.\n5. Only remove the endpoint after the deprecation period.",
"related": ["method-removed", "deprecated-added", "response-removed"],
"keywords": "openapi endpoint removed breaking change api path deleted 404"
},
{
"id": "method-removed",
"enum": "METHOD_REMOVED",
"value": "method_removed",
"title": "Method Removed",
"breaking": True,
"severity": "high",
"description": "An HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) has been removed from an existing endpoint. Consumers using that method will receive 405 Method Not Allowed errors.",
"why_breaking": "Clients explicitly choose HTTP methods for their requests. Removing a method means any consumer relying on it will fail with a 405 error, even though the endpoint path still exists.",
"detection": "Delimit iterates over each shared path and compares the HTTP methods (get, post, put, delete, patch, head, options). Any method present in the old spec but absent in the new spec triggers this change.",
"before": '''paths:
/users/{id}:
get:
summary: Get user
delete:
summary: Delete user''',
"after": '''paths:
/users/{id}:
get:
summary: Get user
# DELETE method removed''',
"migration": "1. Mark the method as <code>deprecated: true</code> before removal.\n2. Return <code>405 Method Not Allowed</code> with an <code>Allow</code> header listing valid methods.\n3. Document the alternative approach (e.g., soft-delete via PATCH).\n4. Give consumers time to migrate before removing the method.",
"related": ["endpoint-removed", "deprecated-added"],
"keywords": "openapi http method removed breaking change delete put post 405"
},
{
"id": "required-param-added",
"enum": "REQUIRED_PARAM_ADDED",
"value": "required_param_added",
"title": "Required Parameter Added",
"breaking": True,
"severity": "high",
"description": "A new required parameter has been added to an existing operation. Existing clients that do not send this parameter will receive validation errors.",
"why_breaking": "Existing consumers do not know about the new parameter and will not include it in their requests. The server will reject these requests with a 400 Bad Request error, breaking all existing integrations.",
"detection": "Delimit compares the <code>parameters</code> arrays for each operation. Any new parameter with <code>required: true</code> that was not present in the old spec is flagged.",
"before": '''paths:
/users:
get:
parameters:
- name: limit
in: query
required: false
schema:
type: integer''',
"after": '''paths:
/users:
get:
parameters:
- name: limit
in: query
required: false
schema:
type: integer
- name: organization_id
in: query
required: true
schema:
type: string''',
"migration": "1. Make the new parameter optional with a sensible default value.\n2. If the parameter must be required, version the endpoint (e.g., <code>/v2/users</code>).\n3. Communicate the change to consumers before enforcing it.\n4. Consider using a header or context-based default for backward compatibility.",
"related": ["optional-param-added", "param-removed", "param-required-changed"],
"keywords": "openapi required parameter added breaking change query header 400"
},
{
"id": "param-removed",
"enum": "PARAM_REMOVED",
"value": "param_removed",
"title": "Parameter Removed",
"breaking": True,
"severity": "high",
"description": "An existing parameter has been removed from an operation. Clients sending this parameter may receive errors or experience silent behavior changes.",
"why_breaking": "Consumers that include the removed parameter may get validation errors (strict servers) or have their parameter silently ignored (lenient servers). Either way, the intended behavior changes.",
"detection": "Delimit identifies parameters by their <code>name</code> and <code>in</code> location (query, header, path, cookie). A parameter present in the old spec but absent in the new spec is flagged.",
"before": '''parameters:
- name: sort_by
in: query
schema:
type: string
- name: include_deleted
in: query
schema:
type: boolean''',
"after": '''parameters:
- name: sort_by
in: query
schema:
type: string
# include_deleted parameter removed''',
"migration": "1. Deprecate the parameter first by adding <code>deprecated: true</code>.\n2. Continue accepting the parameter but ignore it server-side.\n3. Log a warning when the deprecated parameter is received.\n4. Remove after a deprecation period.",
"related": ["required-param-added", "optional-param-added"],
"keywords": "openapi parameter removed breaking change query header path deleted"
},
{
"id": "response-removed",
"enum": "RESPONSE_REMOVED",
"value": "response_removed",
"title": "Response Removed",
"breaking": True,
"severity": "high",
"description": "A success response (2xx) has been removed from an operation. Clients expecting this response status code will not handle the new response correctly.",
"why_breaking": "Consumers build their error handling and response parsing around documented status codes. Removing a 2xx response means clients may not properly handle the new response, leading to incorrect application behavior.",
"detection": "Delimit compares response status codes for each operation. Only 2xx response removals are flagged as breaking since error response changes are generally non-breaking.",
"before": '''responses:
"200":
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"204":
description: No content''',
"after": '''responses:
"200":
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/User"
# 204 response removed''',
"migration": "1. Document the new response behavior clearly.\n2. If consolidating responses, ensure the remaining response covers all use cases.\n3. Communicate the change in advance so consumers can update their response handling.\n4. Consider versioning the API if the response semantics change significantly.",
"related": ["response-added", "endpoint-removed", "response-type-changed"],
"keywords": "openapi response removed breaking change status code 2xx"
},
{
"id": "required-field-added",
"enum": "REQUIRED_FIELD_ADDED",
"value": "required_field_added",
"title": "Required Field Added",
"breaking": True,
"severity": "high",
"description": "A new required field has been added to a request body schema. Existing clients that do not include this field will receive validation errors.",
"why_breaking": "Existing consumers construct request bodies based on the current schema. A new required field means every consumer must update their code to include it, or their requests will be rejected with a 400/422 error.",
"detection": "Delimit performs deep schema comparison on request body and response schemas. A field that appears in the <code>required</code> array of the new spec but was not present in the old spec is flagged.",
"before": '''components:
schemas:
CreateUser:
type: object
required: [name, email]
properties:
name:
type: string
email:
type: string''',
"after": '''components:
schemas:
CreateUser:
type: object
required: [name, email, organization_id]
properties:
name:
type: string
email:
type: string
organization_id:
type: string''',
"migration": "1. Make the field optional with a default value instead.\n2. If it must be required, provide a migration window.\n3. Consider adding the field as optional first, then making it required in a later version.\n4. Auto-populate the field server-side for existing consumers during the transition.",
"related": ["optional-field-added", "field-removed", "required-param-added"],
"keywords": "openapi required field added breaking change request body schema validation"
},
{
"id": "field-removed",
"enum": "FIELD_REMOVED",
"value": "field_removed",
"title": "Field Removed",
"breaking": True,
"severity": "high",
"description": "A field has been removed from a schema (request body, response, or component). Consumers that read or write this field will break.",
"why_breaking": "Removing a field from a response breaks consumers that read it. Removing a field from a request body means consumers sending it will either get errors or have data silently dropped.",
"detection": "Delimit recursively compares object properties in schemas. A property present in the old spec's <code>properties</code> but absent in the new spec is flagged, especially if it was in the <code>required</code> array.",
"before": '''components:
schemas:
User:
type: object
properties:
id:
type: string
name:
type: string
legacy_role:
type: string''',
"after": '''components:
schemas:
User:
type: object
properties:
id:
type: string
name:
type: string
# legacy_role removed''',
"migration": "1. Mark the field as <code>deprecated: true</code> before removing it.\n2. Continue returning the field with a null or default value.\n3. For response fields, add a sunset header indicating when the field will be removed.\n4. Provide a replacement field if applicable.",
"related": ["required-field-added", "optional-field-added", "deprecated-added"],
"keywords": "openapi field removed breaking change schema property deleted response body"
},
{
"id": "type-changed",
"enum": "TYPE_CHANGED",
"value": "type_changed",
"title": "Type Changed",
"breaking": True,
"severity": "high",
"description": "The data type of a field, parameter, or schema has changed (e.g., from <code>string</code> to <code>integer</code>). All consumers parsing this value will break.",
"why_breaking": "Type changes are among the most disruptive breaking changes. Every consumer that parses, validates, or stores this value based on the old type will fail. Strongly-typed clients will fail at compile time; dynamic clients will fail at runtime.",
"detection": "Delimit compares the <code>type</code> property at every level of the schema hierarchy -- parameters, request bodies, responses, and nested objects. Any mismatch is flagged.",
"before": '''properties:
user_id:
type: string
description: User identifier''',
"after": '''properties:
user_id:
type: integer
description: User identifier''',
"migration": "1. Never change a field's type in-place. Add a new field with the new type.\n2. Deprecate the old field and keep returning it with the original type.\n3. If you must change the type, version the API.\n4. Consider using a union type during transition if your spec supports it.",
"related": ["param-type-changed", "response-type-changed", "format-changed"],
"keywords": "openapi type changed breaking change string integer boolean schema property"
},
{
"id": "format-changed",
"enum": "FORMAT_CHANGED",
"value": "format_changed",
"title": "Format Changed",
"breaking": True,
"severity": "high",
"description": "The format of a field has changed (e.g., from <code>date-time</code> to <code>date</code>, or <code>int32</code> to <code>int64</code>). Consumers parsing this value with format-specific logic will break.",
"why_breaking": "Many consumers use format information to choose parsing strategies. Changing from <code>date-time</code> to <code>date</code> or from <code>uuid</code> to plain <code>string</code> can break validation, storage, and display logic.",
"detection": "Delimit compares the <code>format</code> property on schema fields. A change in format value between old and new specs is flagged as breaking.",
"before": '''properties:
created_at:
type: string
format: date-time''',
"after": '''properties:
created_at:
type: string
format: date''',
"migration": "1. Add a new field with the new format rather than changing the existing one.\n2. If the format change is widening (e.g., date to date-time), it may be safe but should still be communicated.\n3. Provide both formats during a transition period.\n4. Update all SDK code generators after the change.",
"related": ["type-changed", "param-type-changed"],
"keywords": "openapi format changed breaking change date-time uuid int32 int64"
},
{
"id": "enum-value-removed",
"enum": "ENUM_VALUE_REMOVED",
"value": "enum_value_removed",
"title": "Enum Value Removed",
"breaking": True,
"severity": "high",
"description": "A value has been removed from an enum list. Consumers sending the removed value will receive validation errors; consumers parsing it will encounter unexpected behavior.",
"why_breaking": "Consumers may be sending the removed enum value in requests (causing 400 errors) or expecting to receive it in responses (causing parsing failures). Either direction is a breaking change.",
"detection": "Delimit compares <code>enum</code> arrays on parameters and schema properties. Values present in the old enum set but absent in the new set are flagged.",
"before": '''properties:
status:
type: string
enum: [active, inactive, pending, archived]''',
"after": '''properties:
status:
type: string
enum: [active, inactive, pending]
# "archived" removed''',
"migration": "1. Keep the old enum value but mark it as deprecated in the description.\n2. Map the old value to a new value server-side during a transition period.\n3. Return a warning header when the deprecated value is used.\n4. Only remove after confirming no consumers send or expect this value.",
"related": ["enum-value-added", "type-changed"],
"keywords": "openapi enum value removed breaking change enumeration status validation"
},
{
"id": "param-type-changed",
"enum": "PARAM_TYPE_CHANGED",
"value": "param_type_changed",
"title": "Parameter Type Changed",
"breaking": True,
"severity": "high",
"description": "The data type of a parameter has changed (e.g., query parameter changed from <code>string</code> to <code>integer</code>). Consumers sending the old type will fail validation.",
"why_breaking": "Consumers construct parameter values based on the documented type. Changing a query parameter from string to integer means existing string-based requests will fail server-side validation.",
"detection": "Delimit compares the <code>schema.type</code> of parameters that exist in both old and new specs. A type mismatch triggers both <code>param_type_changed</code> and the general <code>type_changed</code> change.",
"before": '''parameters:
- name: user_id
in: query
schema:
type: string''',
"after": '''parameters:
- name: user_id
in: query
schema:
type: integer''',
"migration": "1. Add a new parameter with the new type and deprecate the old one.\n2. Accept both types server-side during a transition period.\n3. If string-to-number, consider continuing to accept quoted numbers.\n4. Version the endpoint if the type change is fundamental.",
"related": ["type-changed", "param-required-changed", "param-removed"],
"keywords": "openapi parameter type changed breaking change query header string integer"
},
{
"id": "param-required-changed",
"enum": "PARAM_REQUIRED_CHANGED",
"value": "param_required_changed",
"title": "Parameter Required Changed",
"breaking": True,
"severity": "high",
"description": "A parameter has changed from optional to required. Existing clients that do not send this parameter will start receiving validation errors.",
"why_breaking": "Consumers that previously omitted this optional parameter will now receive 400 errors because the server requires it. This is functionally identical to adding a new required parameter for affected consumers.",
"detection": "Delimit compares the <code>required</code> flag on parameters that exist in both old and new specs. A change from <code>false</code> (or absent) to <code>true</code> is flagged.",
"before": '''parameters:
- name: api_version
in: header
required: false
schema:
type: string''',
"after": '''parameters:
- name: api_version
in: header
required: true
schema:
type: string''',
"migration": "1. Instead of making it required, add a default value so existing requests work.\n2. If it must be required, provide a migration period where the server supplies a default.\n3. Communicate the change well in advance.\n4. Consider using API versioning to introduce the requirement.",
"related": ["required-param-added", "param-type-changed", "param-removed"],
"keywords": "openapi parameter optional required changed breaking change validation"
},
{
"id": "response-type-changed",
"enum": "RESPONSE_TYPE_CHANGED",
"value": "response_type_changed",
"title": "Response Type Changed",
"breaking": True,
"severity": "high",
"description": "The type of a response schema has changed (e.g., from <code>object</code> to <code>array</code>, or <code>string</code> to <code>integer</code>). All consumers parsing this response will break.",
"why_breaking": "Response type changes are the most impactful breaking changes for consumers. Every client that deserializes the response based on the old type will fail. This causes runtime errors in production.",
"detection": "Delimit performs deep schema comparison on response bodies. When a type mismatch is found within a response context (identified by status code in the path), it emits <code>response_type_changed</code> in addition to the general <code>type_changed</code>.",
"before": '''responses:
"200":
content:
application/json:
schema:
type: object
properties:
users:
type: array''',
"after": '''responses:
"200":
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"''',
"migration": "1. Never change response types in-place. Create a new endpoint version.\n2. If wrapping/unwrapping a response, keep the old format and add a new field.\n3. Use content negotiation (Accept header) to serve both formats.\n4. Provide SDK migration guides for each supported language.",
"related": ["type-changed", "response-removed", "field-removed"],
"keywords": "openapi response type changed breaking change object array schema deserialization"
},
{
"id": "security-removed",
"enum": "SECURITY_REMOVED",
"value": "security_removed",
"title": "Security Removed",
"breaking": True,
"severity": "high",
"description": "A security scheme has been removed from the spec or from an operation. Consumers configured to authenticate with this scheme will need to change their authentication approach.",
"why_breaking": "Removing a security scheme breaks consumers that are configured to use it. Their authentication tokens or credentials become invalid for the API, and they must reconfigure their authentication.",
"detection": "Delimit compares both global security schemes (under <code>components/securitySchemes</code>) and per-operation security requirements. Schemes present in the old spec but absent in the new spec are flagged.",
"before": '''components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
BearerAuth:
type: http
scheme: bearer''',
"after": '''components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
# ApiKeyAuth removed''',
"migration": "1. Announce the deprecation of the old auth scheme with a timeline.\n2. Support both authentication methods during a transition period.\n3. Provide migration documentation showing the new authentication flow.\n4. Invalidate old credentials only after the transition period ends.",
"related": ["security-added", "security-scope-removed"],
"keywords": "openapi security scheme removed breaking change authentication apikey bearer oauth"
},
{
"id": "security-scope-removed",
"enum": "SECURITY_SCOPE_REMOVED",
"value": "security_scope_removed",
"title": "Security Scope Removed",
"breaking": True,
"severity": "high",
"description": "An OAuth scope has been removed from a security requirement. Consumers whose tokens include this scope may experience authorization changes.",
"why_breaking": "Removing an OAuth scope can change authorization behavior. Consumers may have tokens that were specifically granted this scope, and removing it from the spec may indicate the server no longer recognizes it.",
"detection": "Delimit compares the scope arrays within security requirements for each scheme. Scopes present in the old spec but absent in the new spec are flagged.",
"before": '''security:
- OAuth2:
- read:users
- write:users
- admin:users''',
"after": '''security:
- OAuth2:
- read:users
- write:users
# admin:users scope removed''',
"migration": "1. Map the old scope to an equivalent new scope server-side.\n2. Continue accepting the old scope during a transition period.\n3. Notify consumers to request updated tokens.\n4. Update OAuth consent screens and documentation.",
"related": ["security-removed", "security-added"],
"keywords": "openapi oauth scope removed breaking change authorization token permissions"
},
{
"id": "max-length-decreased",
"enum": "MAX_LENGTH_DECREASED",
"value": "max_length_decreased",
"title": "Max Length Decreased",
"breaking": True,
"severity": "high",
"description": "A <code>maxLength</code> or <code>maxItems</code> constraint has been decreased or newly added. Values that were previously valid may now be rejected.",
"why_breaking": "Consumers sending values that fit within the old maximum but exceed the new maximum will have their requests rejected. This is especially problematic for stored data that was valid under the old constraint.",
"detection": "Delimit compares <code>maxLength</code> and <code>maxItems</code> constraints. A decrease in value, or adding a max constraint where none existed, is flagged as breaking.",
"before": '''properties:
description:
type: string
maxLength: 1000''',
"after": '''properties:
description:
type: string
maxLength: 500''',
"migration": "1. Validate existing data against the new constraint before deploying.\n2. Truncate or migrate values that exceed the new maximum.\n3. Communicate the new limit to consumers before enforcing it.\n4. Consider a warning period where over-limit values are accepted but logged.",
"related": ["min-length-increased", "type-changed"],
"keywords": "openapi maxLength decreased breaking change validation constraint maxItems limit"
},
{
"id": "min-length-increased",
"enum": "MIN_LENGTH_INCREASED",
"value": "min_length_increased",
"title": "Min Length Increased",
"breaking": True,
"severity": "high",
"description": "A <code>minLength</code> or <code>minItems</code> constraint has been increased or newly added. Values that were previously valid may now be rejected.",
"why_breaking": "Consumers sending values that met the old minimum but fall below the new minimum will have their requests rejected. This affects any consumer providing short values or empty collections.",
"detection": "Delimit compares <code>minLength</code> and <code>minItems</code> constraints. An increase in value, or adding a min constraint where none existed (greater than 0), is flagged.",
"before": '''properties:
username:
type: string
minLength: 1''',
"after": '''properties:
username:
type: string
minLength: 3''',
"migration": "1. Validate existing data against the new constraint before deploying.\n2. Pad or migrate values that fall below the new minimum.\n3. Apply the new constraint only to new values, grandfathering existing ones.\n4. Provide clear error messages explaining the new minimum requirement.",
"related": ["max-length-decreased", "type-changed"],
"keywords": "openapi minLength increased breaking change validation constraint minItems minimum"
},
# Non-breaking changes (10)
{
"id": "endpoint-added",
"enum": "ENDPOINT_ADDED",
"value": "endpoint_added",
"title": "Endpoint Added",
"breaking": False,
"severity": "low",
"description": "A new endpoint has been added to the API. This is purely additive and does not affect existing consumers.",
"why_breaking": "This is a non-breaking change. New endpoints do not affect existing consumers. They can start using the new endpoint at their convenience.",
"detection": "Delimit compares the <code>paths</code> keys between specs. Any path present in the new spec but absent in the old spec is flagged as <code>endpoint_added</code>.",
"before": '''paths:
/users:
get:
summary: List users''',
"after": '''paths:
/users:
get:
summary: List users
/teams:
get:
summary: List teams''',
"migration": "No migration needed. Document the new endpoint and notify consumers of the new capability. Update SDKs and client libraries to expose the new functionality.",
"related": ["endpoint-removed", "method-added"],
"keywords": "openapi endpoint added non-breaking change new api path"
},
{
"id": "method-added",
"enum": "METHOD_ADDED",
"value": "method_added",
"title": "Method Added",
"breaking": False,
"severity": "low",
"description": "A new HTTP method has been added to an existing endpoint. This is additive and does not affect existing consumers.",
"why_breaking": "This is a non-breaking change. Adding a new method to an existing endpoint does not affect consumers using other methods on the same endpoint.",
"detection": "Delimit compares HTTP methods for each shared path. Methods present in the new spec but absent in the old spec are flagged as <code>method_added</code>.",
"before": '''paths:
/users/{id}:
get:
summary: Get user''',
"after": '''paths:
/users/{id}:
get:
summary: Get user
patch:
summary: Update user''',
"migration": "No migration needed. Document the new method, update SDKs, and notify consumers.",
"related": ["method-removed", "endpoint-added"],
"keywords": "openapi http method added non-breaking change new operation"
},
{
"id": "optional-param-added",
"enum": "OPTIONAL_PARAM_ADDED",
"value": "optional_param_added",
"title": "Optional Parameter Added",
"breaking": False,
"severity": "low",
"description": "A new optional parameter has been added to an operation. Existing clients will continue to work without changes.",
"why_breaking": "This is a non-breaking change. Optional parameters have defaults or can be omitted. Existing consumers are unaffected and can adopt the parameter at their own pace.",
"detection": "Delimit checks new parameters for their <code>required</code> flag. Parameters with <code>required: false</code> (or no required flag, which defaults to false) are classified as optional additions.",
"before": '''parameters:
- name: limit
in: query
required: false
schema:
type: integer''',
"after": '''parameters:
- name: limit
in: query
required: false
schema:
type: integer
- name: cursor
in: query
required: false
schema:
type: string''',
"migration": "No migration needed. Document the new parameter with its default behavior. Update SDKs to expose the new option.",
"related": ["required-param-added", "param-removed"],
"keywords": "openapi optional parameter added non-breaking change query"
},
{
"id": "response-added",
"enum": "RESPONSE_ADDED",
"value": "response_added",
"title": "Response Added",
"breaking": False,
"severity": "low",
"description": "A new response status code has been added to an operation. This is additive and does not affect existing consumers.",
"why_breaking": "This is a non-breaking change. Properly implemented clients should handle unexpected status codes gracefully. The new response provides additional information about the API's behavior.",
"detection": "Delimit compares response status codes between old and new specs. New status codes are flagged as <code>response_added</code>.",
"before": '''responses:
"200":
description: Success''',
"after": '''responses:
"200":
description: Success
"429":
description: Rate limited''',
"migration": "No migration needed. Update client error handling to take advantage of the new response code for better error messages and retry logic.",
"related": ["response-removed", "response-type-changed"],
"keywords": "openapi response added non-breaking change status code"
},
{
"id": "optional-field-added",
"enum": "OPTIONAL_FIELD_ADDED",
"value": "optional_field_added",
"title": "Optional Field Added",
"breaking": False,
"severity": "low",
"description": "A new optional field has been added to a schema. Existing clients will continue to work; the new field can be adopted at their convenience.",
"why_breaking": "This is a non-breaking change. Optional fields can be omitted in requests and may or may not appear in responses. Well-behaved clients should ignore unknown fields.",
"detection": "Delimit checks new properties in schemas. Properties that are not in the <code>required</code> array are classified as optional field additions.",
"before": '''components:
schemas:
User:
type: object
properties:
id:
type: string
name:
type: string''',
"after": '''components:
schemas:
User:
type: object
properties:
id:
type: string
name:
type: string
avatar_url:
type: string''',
"migration": "No migration needed. Update SDKs to include the new field. Consumers can start using it when ready.",
"related": ["required-field-added", "field-removed"],
"keywords": "openapi optional field added non-breaking change schema property"
},
{
"id": "enum-value-added",
"enum": "ENUM_VALUE_ADDED",
"value": "enum_value_added",
"title": "Enum Value Added",
"breaking": False,
"severity": "low",
"description": "A new value has been added to an enum list. Existing values continue to work. Consumers should handle unknown enum values gracefully.",
"why_breaking": "This is a non-breaking change in principle, though consumers with strict enum validation may need updates. Well-designed clients should handle unknown enum values.",
"detection": "Delimit compares enum arrays. Values present in the new spec but absent in the old spec are flagged as <code>enum_value_added</code>.",
"before": '''properties:
status:
type: string
enum: [active, inactive]''',
"after": '''properties:
status:
type: string
enum: [active, inactive, pending]''',
"migration": "No migration strictly needed. Consumers should ensure they handle unknown enum values gracefully. Update any client-side enum types or validation to include the new value.",
"related": ["enum-value-removed"],
"keywords": "openapi enum value added non-breaking change enumeration"
},
{
"id": "description-changed",
"enum": "DESCRIPTION_CHANGED",
"value": "description_changed",
"title": "Description Changed",
"breaking": False,
"severity": "low",
"description": "A description field has been modified. This is a documentation-only change with no functional impact on consumers.",
"why_breaking": "This is a non-breaking change. Description changes are purely cosmetic and do not affect API behavior, request/response formats, or validation.",
"detection": "Delimit compares <code>description</code> fields on operations, parameters, schemas, and properties. Any text change is flagged as <code>description_changed</code>.",
"before": '''paths:
/users:
get:
description: Get all users''',
"after": '''paths:
/users:
get:
description: Retrieve a paginated list of all users in the organization''',
"migration": "No migration needed. Updated descriptions improve documentation quality. Regenerate SDK documentation if auto-generated.",
"related": ["deprecated-added"],
"keywords": "openapi description changed non-breaking change documentation"
},
{
"id": "security-added",
"enum": "SECURITY_ADDED",
"value": "security_added",
"title": "Security Added",
"breaking": False,
"severity": "low",
"description": "A new security scheme has been added to the spec or to an operation. Existing authentication methods continue to work.",
"why_breaking": "This is a non-breaking change when added as an additional authentication option. Consumers can continue using existing auth methods. Note: if this replaces all existing auth, it could be breaking in practice.",
"detection": "Delimit compares security schemes in <code>components/securitySchemes</code> and per-operation security arrays. New schemes are flagged as <code>security_added</code>.",
"before": '''components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer''',
"after": '''components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key''',
"migration": "No migration needed. Document the new authentication option. Consumers can adopt it if preferred.",
"related": ["security-removed", "security-scope-removed"],
"keywords": "openapi security scheme added non-breaking change authentication"
},
{
"id": "deprecated-added",
"enum": "DEPRECATED_ADDED",
"value": "deprecated_added",
"title": "Deprecated Added",
"breaking": False,
"severity": "low",
"description": "An operation or field has been marked as <code>deprecated: true</code>. This signals intent to remove but does not break existing consumers.",
"why_breaking": "This is a non-breaking change. Deprecation is a warning, not a removal. The deprecated element continues to function. It signals that consumers should plan to migrate.",
"detection": "Delimit compares the <code>deprecated</code> flag on operations and schema properties. A change from <code>false</code> (or absent) to <code>true</code> is flagged.",
"before": '''paths:
/users/search:
get:
summary: Search users''',
"after": '''paths:
/users/search:
get:
summary: Search users
deprecated: true''',
"migration": "No immediate migration needed. Plan to migrate away from the deprecated element before it is removed. Check the API changelog for the planned removal date.",
"related": ["endpoint-removed", "method-removed", "field-removed"],
"keywords": "openapi deprecated added non-breaking change sunset warning"
},
{
"id": "default-changed",
"enum": "DEFAULT_CHANGED",
"value": "default_changed",
"title": "Default Changed",
"breaking": False,
"severity": "low",
"description": "The default value of a parameter or field has changed. Consumers relying on the old default behavior will see different results.",
"why_breaking": "Classified as non-breaking because consumers explicitly setting the value are unaffected. However, consumers relying on the default (by omitting the parameter) will experience behavior changes. Review carefully.",
"detection": "Delimit compares <code>default</code> values on parameters and schema properties. Any change in the default value is flagged as <code>default_changed</code>.",
"before": '''parameters:
- name: limit
in: query
schema:
type: integer
default: 20''',
"after": '''parameters:
- name: limit
in: query
schema:
type: integer
default: 50''',
"migration": "1. Communicate the default value change to consumers.\n2. Consumers relying on the default should explicitly set the old value if they need the previous behavior.\n3. Update documentation to reflect the new default.\n4. Consider whether this warrants a MINOR version bump in your API.",
"related": ["type-changed", "format-changed"],
"keywords": "openapi default value changed non-breaking change parameter schema"
},
]
# ── HTML Template ────────────────────────────────────────────────────────────
BASE_URL = "https://delimit-ai.github.io/docs"
def nav_html(active=""):
items = [
("Home", "/docs/"),
("Quick Start", "/docs/quickstart/"),
("CLI Reference", "/docs/cli/"),
("GitHub Action", "/docs/action/"),
("MCP Server", "/docs/mcp/"),
("Policies", "/docs/policies/"),
("Hooks", "/docs/hooks/"),
("Change Types", "/docs/changes/"),
("Integrations", "/docs/integrations/claude-code.html"),
]
out = '<nav class="sidebar">\n'
out += ' <a href="/docs/" class="logo">Delimit</a>\n'
out += ' <ul>\n'
for label, href in items:
cls = ' class="active"' if label == active else ""
out += f' <li><a href="{href}"{cls}>{label}</a></li>\n'
out += ' </ul>\n'
out += ' <div class="sidebar-footer">\n'
out += ' <a href="https://github.com/delimit-ai/delimit-action" target="_blank">GitHub</a>\n'
out += ' <a href="https://www.npmjs.com/package/delimit-cli" target="_blank">npm</a>\n'
out += ' <a href="https://delimit.ai" target="_blank">delimit.ai</a>\n'
out += ' </div>\n'
out += '</nav>\n'
return out
def page(title, description, body_html, canonical_path, active_nav="", json_ld=None):
canonical = f"{BASE_URL}{canonical_path}"
ld_block = ""
if json_ld:
import json
ld_block = f'<script type="application/ld+json">{json.dumps(json_ld)}</script>'
return f'''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{html.escape(title)} - Delimit Docs</title>
<meta name="description" content="{html.escape(description)}">
<link rel="canonical" href="{canonical}">
<link rel="stylesheet" href="/docs/style.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🛡</text></svg>">
{ld_block}
</head>
<body>
{nav_html(active_nav)}
<main>
{body_html}
</main>
<footer>
<p>Delimit — API governance for AI coding assistants ·
<a href="https://delimit.ai">delimit.ai</a> ·
<a href="https://github.com/delimit-ai/delimit-action">GitHub</a> ·
<a href="https://www.npmjs.com/package/delimit-cli">npm</a>
</p>
</footer>
</body>
</html>'''
# ── Page Generators ──────────────────────────────────────────────────────────
def generate_index():
body = '''
<h1>Delimit Documentation</h1>
<p class="lead">API governance for AI coding assistants. Catch breaking changes before merge with deterministic diff analysis, policy enforcement, and semver classification.</p>
<div class="card-grid">
<a href="/docs/quickstart/" class="card">
<h3>Quick Start</h3>
<p>Get up and running in 5 minutes with the CLI or GitHub Action.</p>
</a>
<a href="/docs/cli/" class="card">
<h3>CLI Reference</h3>
<p>All commands: init, lint, diff, explain, doctor, setup, activate.</p>
</a>
<a href="/docs/action/" class="card">
<h3>GitHub Action</h3>
<p>Add API governance to your CI pipeline in one step.</p>
</a>
<a href="/docs/changes/" class="card">
<h3>27 Change Types</h3>
<p>Reference for every breaking and non-breaking change Delimit detects.</p>
</a>
<a href="/docs/policies/" class="card">
<h3>Policies</h3>
<p>Presets (strict, default, relaxed) and custom YAML policy configuration.</p>
</a>
<a href="/docs/mcp/" class="card">
<h3>MCP Server</h3>
<p>Use Delimit with Claude Code, Codex, and Gemini CLI via MCP.</p>
</a>
</div>
<h2>Install</h2>
<pre><code>npx delimit-cli setup</code></pre>
<p>Or install globally:</p>
<pre><code>npm install -g delimit-cli</code></pre>
<h2>What Delimit Does</h2>
<p>Delimit compares two versions of an OpenAPI specification and detects <strong>27 types of changes</strong> — 17 breaking and 10 non-breaking. It classifies changes by severity, recommends semver bumps, generates migration guides, and enforces custom policies.</p>
<h2>How It Works</h2>
<ol>
<li><strong>Diff</strong> — Compare old and new OpenAPI specs to find all changes.</li>
<li><strong>Classify</strong> — Each change is categorized as breaking or non-breaking with a severity level.</li>
<li><strong>Enforce</strong> — Apply policy presets or custom YAML rules to pass/fail the change.</li>
<li><strong>Report</strong> — Generate PR comments, CI annotations, and migration guides.</li>
</ol>
<h2>Integrations</h2>
<ul>
<li><a href="/docs/integrations/claude-code.html">Claude Code</a> — MCP integration for API governance in Claude.</li>
<li><a href="/docs/integrations/codex.html">OpenAI Codex</a> — MCP integration for Codex CLI.</li>
<li><a href="/docs/integrations/gemini-cli.html">Gemini CLI</a> — MCP integration for Google Gemini.</li>
</ul>
'''
return page(
"Delimit Documentation",
"API governance for AI coding assistants. Detect 27 types of breaking changes in OpenAPI specs. CLI, GitHub Action, and MCP server.",
body, "/",
active_nav="Home",
json_ld={
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Delimit Documentation",
"url": BASE_URL,
"description": "API governance for AI coding assistants",
"publisher": {
"@type": "Organization",
"name": "Delimit AI",
"url": "https://delimit.ai"
}
}
)
def generate_quickstart():
body = '''
<h1>Quick Start</h1>
<p class="lead">Get API governance running in under 5 minutes.</p>
<h2>Option 1: CLI (Local Development)</h2>
<pre><code># Install
npx delimit-cli setup
# Initialize in your project
delimit init
# Compare two specs
delimit lint --old api/v1.yaml --new api/v2.yaml
# Or diff without policy enforcement
delimit diff --old api/v1.yaml --new api/v2.yaml</code></pre>
<h2>Option 2: GitHub Action (CI/CD)</h2>
<p>Add this to <code>.github/workflows/api-governance.yml</code>:</p>
<pre><code>name: API Governance
on:
pull_request:
paths: ["openapi.yaml", "api/**"]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: delimit-ai/delimit-action@v1
with:
spec: openapi.yaml
mode: advisory</code></pre>
<p>That's it. Delimit will automatically compare your OpenAPI spec against the base branch and comment on the PR with any breaking changes.</p>
<h2>Option 3: MCP Server (AI Assistants)</h2>
<pre><code># Add to your MCP config
npx delimit-cli mcp</code></pre>
<p>See the <a href="/docs/mcp/">MCP setup guide</a> for Claude Code, Codex, and Gemini CLI configuration.</p>
<h2>What Happens Next</h2>
<ol>
<li>Delimit diffs your old and new OpenAPI specs.</li>
<li>It detects up to <a href="/docs/changes/">27 types of changes</a> (17 breaking, 10 non-breaking).</li>
<li>It applies your <a href="/docs/policies/">policy</a> (strict, default, or relaxed).</li>
<li>In CI, it posts a PR comment with a detailed report.</li>
<li>In enforce mode, it blocks the merge if breaking changes are found.</li>
</ol>
<h2>Next Steps</h2>
<ul>
<li><a href="/docs/cli/">CLI Reference</a> — All available commands and options</li>
<li><a href="/docs/action/">GitHub Action</a> — Advanced CI configuration</li>
<li><a href="/docs/policies/">Policies</a> — Customize what's allowed and what's blocked</li>
<li><a href="/docs/changes/">Change Types</a> — Reference for all 27 detectable changes</li>
</ul>
'''
return page(
"Quick Start",
"Get Delimit API governance running in under 5 minutes. CLI, GitHub Action, or MCP server setup.",
body, "/quickstart/",
active_nav="Quick Start"
)
def generate_cli():
body = '''
<h1>CLI Reference</h1>
<p class="lead">All Delimit CLI commands and options.</p>
<h2>Installation</h2>
<pre><code># Run without installing
npx delimit-cli [command]
# Install globally
npm install -g delimit-cli
# Verify installation
delimit --version</code></pre>
<h2>Commands</h2>
<h3><code>delimit init [--preset]</code></h3>
<p>Initialize Delimit in your project. Creates a <code>.delimit/</code> directory with default configuration.</p>
<pre><code># Use default preset
delimit init
# Use strict preset (all violations are errors)
delimit init --preset strict
# Use relaxed preset (all violations are warnings)
delimit init --preset relaxed</code></pre>
<h3><code>delimit lint</code></h3>
<p>Lint two OpenAPI specs for breaking changes and policy violations. This is the primary CI command.</p>
<pre><code>delimit lint --old api/v1.yaml --new api/v2.yaml
delimit lint --old api/v1.yaml --new api/v2.yaml --policy .delimit/policies.yml</code></pre>
<h3><code>delimit diff</code></h3>
<p>Pure diff between two specs. Lists all changes without applying policy rules.</p>
<pre><code>delimit diff --old api/v1.yaml --new api/v2.yaml</code></pre>
<h3><code>delimit explain</code></h3>
<p>Explain a specific change type or policy rule in detail.</p>
<pre><code>delimit explain endpoint_removed
delimit explain no_type_changes</code></pre>
<h3><code>delimit doctor</code></h3>
<p>Diagnose your Delimit setup. Checks configuration, spec files, and policy validity.</p>
<pre><code>delimit doctor</code></pre>
<h3><code>delimit setup</code></h3>
<p>Interactive setup wizard. Configures Delimit for your project, installs hooks, and validates your spec.</p>
<pre><code>delimit setup</code></pre>
<h3><code>delimit activate</code></h3>
<p>Activate the governance layer. Installs pre-commit hooks and PATH shims for continuous governance.</p>
<pre><code>delimit activate</code></pre>
<h2>Global Options</h2>
<table>