delimit-cli 3.14.28 → 3.14.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/gateway/ai/backends/deploy_bridge.py +56 -2
  2. package/gateway/ai/backends/gateway_core.py +212 -1
  3. package/gateway/ai/backends/generate_bridge.py +84 -13
  4. package/gateway/ai/backends/governance_bridge.py +63 -16
  5. package/gateway/ai/backends/memory_bridge.py +77 -76
  6. package/gateway/ai/backends/ops_bridge.py +76 -6
  7. package/gateway/ai/backends/os_bridge.py +23 -3
  8. package/gateway/ai/backends/repo_bridge.py +156 -17
  9. package/gateway/ai/backends/tools_design.py +116 -9
  10. package/gateway/ai/backends/tools_infra.py +200 -72
  11. package/gateway/ai/backends/tools_real.py +8 -0
  12. package/gateway/ai/backends/ui_bridge.py +115 -5
  13. package/gateway/ai/backends/vault_bridge.py +69 -114
  14. package/gateway/ai/content_engine.py +1276 -0
  15. package/gateway/ai/context_fs.py +193 -0
  16. package/gateway/ai/daemon.py +500 -0
  17. package/gateway/ai/data_plane.py +291 -0
  18. package/gateway/ai/deliberation.py +1033 -6
  19. package/gateway/ai/events.py +39 -0
  20. package/gateway/ai/founding_users.py +162 -0
  21. package/gateway/ai/governance.py +698 -4
  22. package/gateway/ai/inbox_daemon.py +78 -17
  23. package/gateway/ai/integrations/__init__.py +1 -0
  24. package/gateway/ai/integrations/opensage_wrapper.py +288 -0
  25. package/gateway/ai/key_resolver.py +95 -0
  26. package/gateway/ai/ledger_manager.py +289 -1
  27. package/gateway/ai/license.py +62 -4
  28. package/gateway/ai/license_core.py +208 -7
  29. package/gateway/ai/local_server.py +215 -0
  30. package/gateway/ai/loop_engine.py +408 -0
  31. package/gateway/ai/mcp_bridge.py +178 -0
  32. package/gateway/ai/release_sync.py +2 -2
  33. package/gateway/ai/screen_record.py +374 -0
  34. package/gateway/ai/secrets_broker.py +235 -0
  35. package/gateway/ai/social.py +189 -27
  36. package/gateway/ai/social_target.py +1635 -0
  37. package/gateway/ai/supabase_sync.py +190 -0
  38. package/gateway/ai/tracing.py +195 -0
  39. package/gateway/core/contract_ledger.py +1 -1
  40. package/gateway/core/dependency_graph.py +1 -1
  41. package/gateway/core/dependency_manifest.py +1 -1
  42. package/gateway/core/diff_engine_v2.py +272 -78
  43. package/gateway/core/event_backbone.py +2 -2
  44. package/gateway/core/event_schema.py +1 -1
  45. package/gateway/core/impact_analyzer.py +1 -1
  46. package/gateway/core/policy_engine.py +4 -0
  47. package/package.json +1 -1
@@ -19,7 +19,14 @@ class ChangeType(Enum):
19
19
  TYPE_CHANGED = "type_changed"
20
20
  FORMAT_CHANGED = "format_changed"
21
21
  ENUM_VALUE_REMOVED = "enum_value_removed"
22
-
22
+ PARAM_TYPE_CHANGED = "param_type_changed"
23
+ PARAM_REQUIRED_CHANGED = "param_required_changed"
24
+ RESPONSE_TYPE_CHANGED = "response_type_changed"
25
+ SECURITY_REMOVED = "security_removed"
26
+ SECURITY_SCOPE_REMOVED = "security_scope_removed"
27
+ MAX_LENGTH_DECREASED = "max_length_decreased"
28
+ MIN_LENGTH_INCREASED = "min_length_increased"
29
+
23
30
  # Non-breaking changes
24
31
  ENDPOINT_ADDED = "endpoint_added"
25
32
  METHOD_ADDED = "method_added"
@@ -28,6 +35,9 @@ class ChangeType(Enum):
28
35
  OPTIONAL_FIELD_ADDED = "optional_field_added"
29
36
  ENUM_VALUE_ADDED = "enum_value_added"
30
37
  DESCRIPTION_CHANGED = "description_changed"
38
+ SECURITY_ADDED = "security_added"
39
+ DEPRECATED_ADDED = "deprecated_added"
40
+ DEFAULT_CHANGED = "default_changed"
31
41
 
32
42
  @dataclass
33
43
  class Change:
@@ -50,6 +60,13 @@ class Change:
50
60
  ChangeType.TYPE_CHANGED,
51
61
  ChangeType.FORMAT_CHANGED,
52
62
  ChangeType.ENUM_VALUE_REMOVED,
63
+ ChangeType.PARAM_TYPE_CHANGED,
64
+ ChangeType.PARAM_REQUIRED_CHANGED,
65
+ ChangeType.RESPONSE_TYPE_CHANGED,
66
+ ChangeType.SECURITY_REMOVED,
67
+ ChangeType.SECURITY_SCOPE_REMOVED,
68
+ ChangeType.MAX_LENGTH_DECREASED,
69
+ ChangeType.MIN_LENGTH_INCREASED,
53
70
  ]
54
71
 
55
72
  class OpenAPIDiffEngine:
@@ -57,30 +74,26 @@ class OpenAPIDiffEngine:
57
74
 
58
75
  def __init__(self):
59
76
  self.changes: List[Change] = []
60
- self._old_spec: Dict = {}
61
- self._new_spec: Dict = {}
62
- self._ref_trail: Set[Tuple[str, str]] = set()
63
-
77
+
64
78
  def compare(self, old_spec: Dict, new_spec: Dict) -> List[Change]:
65
79
  """Compare two OpenAPI specifications and return all changes."""
66
80
  self.changes = []
67
- self._old_spec = old_spec or {}
68
- self._new_spec = new_spec or {}
69
- self._ref_trail = set()
81
+ old_spec = old_spec or {}
82
+ new_spec = new_spec or {}
70
83
 
71
84
  # Compare paths
72
- self._compare_paths(self._old_spec.get("paths", {}), self._new_spec.get("paths", {}))
85
+ self._compare_paths(old_spec.get("paths", {}), new_spec.get("paths", {}))
73
86
 
74
87
  # Compare components/schemas
75
88
  self._compare_schemas(
76
- self._old_spec.get("components", {}).get("schemas", {}),
77
- self._new_spec.get("components", {}).get("schemas", {})
89
+ old_spec.get("components", {}).get("schemas", {}),
90
+ new_spec.get("components", {}).get("schemas", {})
78
91
  )
79
92
 
80
93
  # Compare security schemes
81
94
  self._compare_security(
82
- self._old_spec.get("components", {}).get("securitySchemes", {}),
83
- self._new_spec.get("components", {}).get("securitySchemes", {})
95
+ old_spec.get("components", {}).get("securitySchemes", {}),
96
+ new_spec.get("components", {}).get("securitySchemes", {})
84
97
  )
85
98
 
86
99
  return self.changes
@@ -167,6 +180,18 @@ class OpenAPIDiffEngine:
167
180
  message=f"Required parameter added: {param['name']} to {operation_id}"
168
181
  ))
169
182
 
183
+ # Check added optional parameters (non-breaking)
184
+ for param_key in set(new_params.keys()) - set(old_params.keys()):
185
+ param = new_params[param_key]
186
+ if not param.get("required", False):
187
+ self.changes.append(Change(
188
+ type=ChangeType.OPTIONAL_PARAM_ADDED,
189
+ path=operation_id,
190
+ details={"parameter": param["name"], "in": param["in"]},
191
+ severity="low",
192
+ message=f"Optional parameter added: {param['name']} to {operation_id}"
193
+ ))
194
+
170
195
  # Check parameter schema changes
171
196
  for param_key in set(old_params.keys()) & set(new_params.keys()):
172
197
  self._compare_parameter_schemas(
@@ -174,7 +199,27 @@ class OpenAPIDiffEngine:
174
199
  old_params[param_key],
175
200
  new_params[param_key]
176
201
  )
177
-
202
+
203
+ # Compare operation-level security
204
+ if "security" in old_op or "security" in new_op:
205
+ self._compare_operation_security(
206
+ operation_id,
207
+ old_op.get("security"),
208
+ new_op.get("security")
209
+ )
210
+
211
+ # Check deprecated flag
212
+ old_deprecated = old_op.get("deprecated", False)
213
+ new_deprecated = new_op.get("deprecated", False)
214
+ if not old_deprecated and new_deprecated:
215
+ self.changes.append(Change(
216
+ type=ChangeType.DEPRECATED_ADDED,
217
+ path=operation_id,
218
+ details={"target": "operation"},
219
+ severity="low",
220
+ message=f"Operation marked as deprecated: {operation_id}"
221
+ ))
222
+
178
223
  # Compare request body
179
224
  if "requestBody" in old_op or "requestBody" in new_op:
180
225
  self._compare_request_body(
@@ -191,30 +236,64 @@ class OpenAPIDiffEngine:
191
236
  )
192
237
 
193
238
  def _compare_parameter_schemas(self, operation_id: str, old_param: Dict, new_param: Dict):
194
- """Compare parameter schemas for type changes."""
239
+ """Compare parameter schemas for type changes, required changes, and constraints."""
195
240
  old_schema = old_param.get("schema", {})
196
241
  new_schema = new_param.get("schema", {})
242
+ param_name = old_param["name"]
197
243
 
198
- # Resolve $ref in parameter schemas
199
- if "$ref" in old_schema:
200
- old_schema = self._resolve_schema(old_schema, self._old_spec)
201
- if "$ref" in new_schema:
202
- new_schema = self._resolve_schema(new_schema, self._new_spec)
203
-
204
- # Check type changes
244
+ # Check type changes emit both PARAM_TYPE_CHANGED (specific) and TYPE_CHANGED (legacy)
205
245
  if old_schema.get("type") != new_schema.get("type"):
246
+ self.changes.append(Change(
247
+ type=ChangeType.PARAM_TYPE_CHANGED,
248
+ path=operation_id,
249
+ details={
250
+ "parameter": param_name,
251
+ "old_type": old_schema.get("type"),
252
+ "new_type": new_schema.get("type")
253
+ },
254
+ severity="high",
255
+ message=f"Parameter type changed: {param_name} from {old_schema.get('type')} to {new_schema.get('type')} in {operation_id}"
256
+ ))
206
257
  self.changes.append(Change(
207
258
  type=ChangeType.TYPE_CHANGED,
208
259
  path=operation_id,
209
260
  details={
210
- "parameter": old_param["name"],
261
+ "parameter": param_name,
211
262
  "old_type": old_schema.get("type"),
212
263
  "new_type": new_schema.get("type")
213
264
  },
214
265
  severity="high",
215
- message=f"Parameter type changed: {old_param['name']} from {old_schema.get('type')} to {new_schema.get('type')}"
266
+ message=f"Parameter type changed: {param_name} from {old_schema.get('type')} to {new_schema.get('type')}"
216
267
  ))
217
-
268
+
269
+ # Check required changed (optional -> required)
270
+ old_required = old_param.get("required", False)
271
+ new_required = new_param.get("required", False)
272
+ if not old_required and new_required:
273
+ self.changes.append(Change(
274
+ type=ChangeType.PARAM_REQUIRED_CHANGED,
275
+ path=operation_id,
276
+ details={"parameter": param_name, "old_required": False, "new_required": True},
277
+ severity="high",
278
+ message=f"Parameter changed from optional to required: {param_name} in {operation_id}"
279
+ ))
280
+
281
+ # Check constraint changes
282
+ self._compare_constraints(f"{operation_id}:{param_name}", old_schema, new_schema)
283
+
284
+ # Check default value changes
285
+ if "default" in old_schema or "default" in new_schema:
286
+ old_default = old_schema.get("default")
287
+ new_default = new_schema.get("default")
288
+ if old_default != new_default:
289
+ self.changes.append(Change(
290
+ type=ChangeType.DEFAULT_CHANGED,
291
+ path=f"{operation_id}:{param_name}",
292
+ details={"old_default": old_default, "new_default": new_default},
293
+ severity="low",
294
+ message=f"Default value changed for {param_name} from {old_default} to {new_default}"
295
+ ))
296
+
218
297
  # Check enum changes
219
298
  if "enum" in old_schema or "enum" in new_schema:
220
299
  self._compare_enums(
@@ -286,58 +365,34 @@ class OpenAPIDiffEngine:
286
365
  new_content[content_type].get("schema", {})
287
366
  )
288
367
 
289
- def _resolve_ref(self, ref_string: str, spec: Dict) -> Optional[Dict]:
290
- """Resolve a JSON $ref pointer like #/components/schemas/User."""
291
- if not ref_string.startswith('#/'):
292
- return None
293
- parts = ref_string[2:].split('/')
294
- current = spec
295
- for part in parts:
296
- part = part.replace('~1', '/').replace('~0', '~')
297
- if isinstance(current, dict) and part in current:
298
- current = current[part]
299
- else:
300
- return None
301
- return current if isinstance(current, dict) else None
302
-
303
- def _resolve_schema(self, schema: Dict, spec: Dict, visited: Optional[Set[str]] = None) -> Dict:
304
- """Follow $ref chains, detecting circular references."""
305
- if visited is None:
306
- visited = set()
307
- if '$ref' not in schema:
308
- return schema
309
- ref = schema['$ref']
310
- if ref in visited:
311
- return schema # circular — return as-is
312
- visited.add(ref)
313
- resolved = self._resolve_ref(ref, spec)
314
- if resolved is None:
315
- return schema # unresolvable — return as-is
316
- if '$ref' in resolved:
317
- return self._resolve_schema(resolved, spec, visited)
318
- return resolved
319
-
320
368
  def _compare_schema_deep(self, path: str, old_schema: Dict, new_schema: Dict, required_fields: Optional[Set[str]] = None):
321
369
  """Deep comparison of schemas including nested objects."""
322
370
 
323
- # Handle references — resolve before comparing
371
+ # Handle references
324
372
  if "$ref" in old_schema or "$ref" in new_schema:
325
- old_resolved = self._resolve_schema(old_schema, self._old_spec) if "$ref" in old_schema else old_schema
326
- new_resolved = self._resolve_schema(new_schema, self._new_spec) if "$ref" in new_schema else new_schema
327
- # Track ref pairs to avoid infinite loops on circular schemas
328
- ref_key = (old_schema.get("$ref", ""), new_schema.get("$ref", ""))
329
- if ref_key in self._ref_trail:
330
- return
331
- self._ref_trail.add(ref_key)
332
- # Compare the resolved schemas
333
- self._compare_schema_deep(path, old_resolved, new_resolved, required_fields)
373
+ # TODO: Resolve references properly
334
374
  return
335
-
375
+
336
376
  # Compare types
337
377
  old_type = old_schema.get("type")
338
378
  new_type = new_schema.get("type")
339
-
379
+
340
380
  if old_type != new_type and old_type is not None:
381
+ # Determine if this is a response context for RESPONSE_TYPE_CHANGED
382
+ is_response = bool(
383
+ ":" in path and any(
384
+ code in path for code in
385
+ ["200", "201", "202", "204", "301", "400", "401", "403", "404", "500"]
386
+ )
387
+ )
388
+ if is_response:
389
+ self.changes.append(Change(
390
+ type=ChangeType.RESPONSE_TYPE_CHANGED,
391
+ path=path,
392
+ details={"old_type": old_type, "new_type": new_type},
393
+ severity="high",
394
+ message=f"Response type changed from {old_type} to {new_type} at {path}"
395
+ ))
341
396
  self.changes.append(Change(
342
397
  type=ChangeType.TYPE_CHANGED,
343
398
  path=path,
@@ -346,14 +401,14 @@ class OpenAPIDiffEngine:
346
401
  message=f"Type changed from {old_type} to {new_type} at {path}"
347
402
  ))
348
403
  return
349
-
404
+
350
405
  # Compare object properties
351
406
  if old_type == "object":
352
407
  old_props = old_schema.get("properties", {})
353
408
  new_props = new_schema.get("properties", {})
354
409
  old_required = set(old_schema.get("required", []))
355
410
  new_required = set(new_schema.get("required", []))
356
-
411
+
357
412
  # Check removed fields
358
413
  for prop in set(old_props.keys()) - set(new_props.keys()):
359
414
  if prop in old_required:
@@ -364,7 +419,7 @@ class OpenAPIDiffEngine:
364
419
  severity="high",
365
420
  message=f"Required field '{prop}' removed at {path}"
366
421
  ))
367
-
422
+
368
423
  # Check new required fields
369
424
  for prop in new_required - old_required:
370
425
  if prop not in old_props:
@@ -375,16 +430,45 @@ class OpenAPIDiffEngine:
375
430
  severity="high",
376
431
  message=f"New required field '{prop}' added at {path}"
377
432
  ))
378
-
433
+
379
434
  # Recursively compare nested properties
380
435
  for prop in set(old_props.keys()) & set(new_props.keys()):
436
+ old_prop_schema = old_props[prop]
437
+ new_prop_schema = new_props[prop]
438
+
439
+ # Check deprecated on fields
440
+ if not old_prop_schema.get("deprecated", False) and new_prop_schema.get("deprecated", False):
441
+ self.changes.append(Change(
442
+ type=ChangeType.DEPRECATED_ADDED,
443
+ path=f"{path}.{prop}",
444
+ details={"target": "field", "field": prop},
445
+ severity="low",
446
+ message=f"Field '{prop}' marked as deprecated at {path}"
447
+ ))
448
+
449
+ # Check default value changes on fields
450
+ if "default" in old_prop_schema or "default" in new_prop_schema:
451
+ old_default = old_prop_schema.get("default")
452
+ new_default = new_prop_schema.get("default")
453
+ if old_default != new_default:
454
+ self.changes.append(Change(
455
+ type=ChangeType.DEFAULT_CHANGED,
456
+ path=f"{path}.{prop}",
457
+ details={"old_default": old_default, "new_default": new_default},
458
+ severity="low",
459
+ message=f"Default value changed for '{prop}' from {old_default} to {new_default} at {path}"
460
+ ))
461
+
462
+ # Check constraint changes on fields
463
+ self._compare_constraints(f"{path}.{prop}", old_prop_schema, new_prop_schema)
464
+
381
465
  self._compare_schema_deep(
382
466
  f"{path}.{prop}",
383
- old_props[prop],
384
- new_props[prop],
467
+ old_prop_schema,
468
+ new_prop_schema,
385
469
  old_required if prop in old_required else None
386
470
  )
387
-
471
+
388
472
  # Compare arrays
389
473
  elif old_type == "array":
390
474
  if "items" in old_schema and "items" in new_schema:
@@ -393,10 +477,14 @@ class OpenAPIDiffEngine:
393
477
  old_schema["items"],
394
478
  new_schema["items"]
395
479
  )
396
-
480
+
397
481
  # Compare enums
398
482
  if "enum" in old_schema or "enum" in new_schema:
399
483
  self._compare_enums(path, old_schema.get("enum", []), new_schema.get("enum", []))
484
+
485
+ # Compare constraints at top level of schema (non-object)
486
+ if old_type != "object":
487
+ self._compare_constraints(path, old_schema, new_schema)
400
488
 
401
489
  def _compare_enums(self, path: str, old_enum: List, new_enum: List):
402
490
  """Compare enum values."""
@@ -443,17 +531,123 @@ class OpenAPIDiffEngine:
443
531
  new_schemas[schema_name]
444
532
  )
445
533
 
534
+ def _compare_constraints(self, path: str, old_schema: Dict, new_schema: Dict):
535
+ """Compare schema constraints (maxLength, minLength, maxItems, minItems)."""
536
+ # maxLength / maxItems decreased = breaking (stricter)
537
+ for prop in ("maxLength", "maxItems"):
538
+ old_val = old_schema.get(prop)
539
+ new_val = new_schema.get(prop)
540
+ if old_val is not None and new_val is not None and new_val < old_val:
541
+ self.changes.append(Change(
542
+ type=ChangeType.MAX_LENGTH_DECREASED,
543
+ path=path,
544
+ details={"constraint": prop, "old_value": old_val, "new_value": new_val},
545
+ severity="high",
546
+ message=f"{prop} decreased from {old_val} to {new_val} at {path}"
547
+ ))
548
+ elif old_val is None and new_val is not None:
549
+ # Adding a max constraint where there was none is also stricter
550
+ self.changes.append(Change(
551
+ type=ChangeType.MAX_LENGTH_DECREASED,
552
+ path=path,
553
+ details={"constraint": prop, "old_value": None, "new_value": new_val},
554
+ severity="high",
555
+ message=f"{prop} added ({new_val}) at {path} where none existed"
556
+ ))
557
+
558
+ # minLength / minItems increased = breaking (stricter)
559
+ for prop in ("minLength", "minItems"):
560
+ old_val = old_schema.get(prop)
561
+ new_val = new_schema.get(prop)
562
+ if old_val is not None and new_val is not None and new_val > old_val:
563
+ self.changes.append(Change(
564
+ type=ChangeType.MIN_LENGTH_INCREASED,
565
+ path=path,
566
+ details={"constraint": prop, "old_value": old_val, "new_value": new_val},
567
+ severity="high",
568
+ message=f"{prop} increased from {old_val} to {new_val} at {path}"
569
+ ))
570
+ elif old_val is None and new_val is not None and new_val > 0:
571
+ # Adding a min constraint where there was none is stricter
572
+ self.changes.append(Change(
573
+ type=ChangeType.MIN_LENGTH_INCREASED,
574
+ path=path,
575
+ details={"constraint": prop, "old_value": None, "new_value": new_val},
576
+ severity="high",
577
+ message=f"{prop} added ({new_val}) at {path} where none existed"
578
+ ))
579
+
580
+ def _compare_operation_security(self, operation_id: str, old_security: Optional[list], new_security: Optional[list]):
581
+ """Compare operation-level security requirements."""
582
+ if old_security is None:
583
+ old_security = []
584
+ if new_security is None:
585
+ new_security = []
586
+
587
+ # Build maps: scheme_name -> set of scopes
588
+ def _security_map(sec_list):
589
+ result = {}
590
+ for item in sec_list:
591
+ for scheme, scopes in item.items():
592
+ result[scheme] = set(scopes) if scopes else set()
593
+ return result
594
+
595
+ old_map = _security_map(old_security)
596
+ new_map = _security_map(new_security)
597
+
598
+ # Removed security schemes from operation
599
+ for scheme in set(old_map.keys()) - set(new_map.keys()):
600
+ self.changes.append(Change(
601
+ type=ChangeType.SECURITY_REMOVED,
602
+ path=operation_id,
603
+ details={"scheme": scheme},
604
+ severity="high",
605
+ message=f"Security scheme '{scheme}' removed from {operation_id}"
606
+ ))
607
+
608
+ # Added security schemes to operation
609
+ for scheme in set(new_map.keys()) - set(old_map.keys()):
610
+ self.changes.append(Change(
611
+ type=ChangeType.SECURITY_ADDED,
612
+ path=operation_id,
613
+ details={"scheme": scheme},
614
+ severity="low",
615
+ message=f"Security scheme '{scheme}' added to {operation_id}"
616
+ ))
617
+
618
+ # Check scope changes for shared schemes
619
+ for scheme in set(old_map.keys()) & set(new_map.keys()):
620
+ removed_scopes = old_map[scheme] - new_map[scheme]
621
+ for scope in removed_scopes:
622
+ self.changes.append(Change(
623
+ type=ChangeType.SECURITY_SCOPE_REMOVED,
624
+ path=operation_id,
625
+ details={"scheme": scheme, "scope": scope},
626
+ severity="high",
627
+ message=f"OAuth scope '{scope}' removed from scheme '{scheme}' at {operation_id}"
628
+ ))
629
+
446
630
  def _compare_security(self, old_security: Dict, new_security: Dict):
447
631
  """Compare security schemes."""
448
- # Security scheme changes are usually breaking
632
+ # Security scheme removal is breaking
449
633
  for scheme in set(old_security.keys()) - set(new_security.keys()):
450
634
  self.changes.append(Change(
451
- type=ChangeType.FIELD_REMOVED,
635
+ type=ChangeType.SECURITY_REMOVED,
452
636
  path=f"#/components/securitySchemes/{scheme}",
453
637
  details={"scheme": scheme},
454
638
  severity="high",
455
639
  message=f"Security scheme '{scheme}' removed"
456
640
  ))
641
+
642
+ # Security scheme addition is non-breaking
643
+ for scheme in set(new_security.keys()) - set(old_security.keys()):
644
+ self.changes.append(Change(
645
+ type=ChangeType.SECURITY_ADDED,
646
+ path=f"#/components/securitySchemes/{scheme}",
647
+ details={"scheme": scheme},
648
+ severity="low",
649
+ message=f"Security scheme '{scheme}' added"
650
+ ))
457
651
 
458
652
  def _param_key(self, param: Dict) -> str:
459
653
  """Generate unique key for parameter."""
@@ -3,7 +3,7 @@ Delimit Event Backbone
3
3
  Constructs ledger events, generates SHA-256 hashes, links hash chains,
4
4
  and appends to the append-only JSONL ledger.
5
5
 
6
- Per Delimit architecture:
6
+ Per Jamsons Doctrine:
7
7
  - Deterministic outputs
8
8
  - Append-only artifacts
9
9
  - Fail-closed CI behavior (ledger failures never affect CI)
@@ -199,7 +199,7 @@ class EventBackbone:
199
199
  This is the primary API for event generation. It is best-effort:
200
200
  if the ledger write fails, the event is still returned but not persisted.
201
201
 
202
- CRITICAL: This method NEVER raises exceptions. Per Delimit architecture,
202
+ CRITICAL: This method NEVER raises exceptions. Per Jamsons Doctrine,
203
203
  ledger failures must not affect CI pass/fail outcome.
204
204
 
205
205
  Returns:
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Delimit Event Schema
3
3
  Canonical event schema for API contract evolution tracking.
4
- Deterministic validation and serialization per Delimit architecture.
4
+ Deterministic validation and serialization per Jamsons Doctrine.
5
5
  """
6
6
 
7
7
  import hashlib
@@ -3,7 +3,7 @@ Delimit Impact Analyzer
3
3
  Determines downstream consumers affected by an API change
4
4
  and produces informational impact summaries for CI output.
5
5
 
6
- Per Delimit architecture:
6
+ Per Jamsons Doctrine:
7
7
  - Impact analysis is INFORMATIONAL ONLY
8
8
  - NEVER affects CI pass/fail outcome
9
9
  - Deterministic outputs
@@ -11,12 +11,16 @@ from pathlib import Path
11
11
 
12
12
  from core.diff_engine_v2 import OpenAPIDiffEngine, Change, ChangeType
13
13
 
14
+
14
15
  class RuleSeverity(Enum):
16
+ """Severity levels for governance policy rules."""
15
17
  ERROR = "error" # Fails CI
16
18
  WARNING = "warning" # Shows warning but passes
17
19
  INFO = "info" # Informational only
18
20
 
21
+
19
22
  class RuleAction(Enum):
23
+ """Actions to take when a policy rule is triggered."""
20
24
  FORBID = "forbid" # Forbids the change
21
25
  ALLOW = "allow" # Explicitly allows
22
26
  WARN = "warn" # Warns but allows
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
3
  "mcpName": "io.github.delimit-ai/delimit-mcp-server",
4
- "version": "3.14.28",
4
+ "version": "3.14.29",
5
5
  "description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
6
6
  "main": "index.js",
7
7
  "files": [