delimit-cli 2.3.2 → 3.0.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.
Files changed (113) hide show
  1. package/.dockerignore +7 -0
  2. package/.github/workflows/ci.yml +22 -0
  3. package/CHANGELOG.md +33 -0
  4. package/CODE_OF_CONDUCT.md +48 -0
  5. package/CONTRIBUTING.md +67 -0
  6. package/Dockerfile +9 -0
  7. package/LICENSE +21 -0
  8. package/README.md +51 -130
  9. package/SECURITY.md +42 -0
  10. package/adapters/codex-forge.js +107 -0
  11. package/adapters/codex-jamsons.js +142 -0
  12. package/adapters/codex-security.js +94 -0
  13. package/adapters/gemini-forge.js +120 -0
  14. package/adapters/gemini-jamsons.js +152 -0
  15. package/bin/delimit-cli.js +52 -2
  16. package/bin/delimit-setup.js +258 -0
  17. package/gateway/ai/backends/__init__.py +0 -0
  18. package/gateway/ai/backends/async_utils.py +21 -0
  19. package/gateway/ai/backends/deploy_bridge.py +150 -0
  20. package/gateway/ai/backends/gateway_core.py +261 -0
  21. package/gateway/ai/backends/generate_bridge.py +38 -0
  22. package/gateway/ai/backends/governance_bridge.py +196 -0
  23. package/gateway/ai/backends/intel_bridge.py +59 -0
  24. package/gateway/ai/backends/memory_bridge.py +93 -0
  25. package/gateway/ai/backends/ops_bridge.py +137 -0
  26. package/gateway/ai/backends/os_bridge.py +82 -0
  27. package/gateway/ai/backends/repo_bridge.py +117 -0
  28. package/gateway/ai/backends/ui_bridge.py +118 -0
  29. package/gateway/ai/backends/vault_bridge.py +129 -0
  30. package/gateway/ai/server.py +1182 -0
  31. package/gateway/core/__init__.py +3 -0
  32. package/gateway/core/__pycache__/__init__.cpython-310.pyc +0 -0
  33. package/gateway/core/__pycache__/auto_baseline.cpython-310.pyc +0 -0
  34. package/gateway/core/__pycache__/ci_formatter.cpython-310.pyc +0 -0
  35. package/gateway/core/__pycache__/contract_ledger.cpython-310.pyc +0 -0
  36. package/gateway/core/__pycache__/dependency_graph.cpython-310.pyc +0 -0
  37. package/gateway/core/__pycache__/dependency_manifest.cpython-310.pyc +0 -0
  38. package/gateway/core/__pycache__/diff_engine_v2.cpython-310.pyc +0 -0
  39. package/gateway/core/__pycache__/event_backbone.cpython-310.pyc +0 -0
  40. package/gateway/core/__pycache__/event_schema.cpython-310.pyc +0 -0
  41. package/gateway/core/__pycache__/explainer.cpython-310.pyc +0 -0
  42. package/gateway/core/__pycache__/gateway.cpython-310.pyc +0 -0
  43. package/gateway/core/__pycache__/gateway_v2.cpython-310.pyc +0 -0
  44. package/gateway/core/__pycache__/gateway_v3.cpython-310.pyc +0 -0
  45. package/gateway/core/__pycache__/impact_analyzer.cpython-310.pyc +0 -0
  46. package/gateway/core/__pycache__/policy_engine.cpython-310.pyc +0 -0
  47. package/gateway/core/__pycache__/registry.cpython-310.pyc +0 -0
  48. package/gateway/core/__pycache__/registry_v2.cpython-310.pyc +0 -0
  49. package/gateway/core/__pycache__/registry_v3.cpython-310.pyc +0 -0
  50. package/gateway/core/__pycache__/semver_classifier.cpython-310.pyc +0 -0
  51. package/gateway/core/__pycache__/spec_detector.cpython-310.pyc +0 -0
  52. package/gateway/core/__pycache__/surface_bridge.cpython-310.pyc +0 -0
  53. package/gateway/core/auto_baseline.py +304 -0
  54. package/gateway/core/ci_formatter.py +283 -0
  55. package/gateway/core/complexity_analyzer.py +386 -0
  56. package/gateway/core/contract_ledger.py +345 -0
  57. package/gateway/core/dependency_graph.py +218 -0
  58. package/gateway/core/dependency_manifest.py +223 -0
  59. package/gateway/core/diff_engine_v2.py +477 -0
  60. package/gateway/core/diff_engine_v2.py.bak +426 -0
  61. package/gateway/core/event_backbone.py +268 -0
  62. package/gateway/core/event_schema.py +258 -0
  63. package/gateway/core/explainer.py +438 -0
  64. package/gateway/core/gateway.py +128 -0
  65. package/gateway/core/gateway_v2.py +154 -0
  66. package/gateway/core/gateway_v3.py +224 -0
  67. package/gateway/core/impact_analyzer.py +163 -0
  68. package/gateway/core/policies/default.yml +13 -0
  69. package/gateway/core/policies/relaxed.yml +48 -0
  70. package/gateway/core/policies/strict.yml +55 -0
  71. package/gateway/core/policy_engine.py +464 -0
  72. package/gateway/core/registry.py +52 -0
  73. package/gateway/core/registry_v2.py +132 -0
  74. package/gateway/core/registry_v3.py +134 -0
  75. package/gateway/core/semver_classifier.py +152 -0
  76. package/gateway/core/spec_detector.py +130 -0
  77. package/gateway/core/surface_bridge.py +307 -0
  78. package/gateway/core/zero_spec/__init__.py +4 -0
  79. package/gateway/core/zero_spec/__pycache__/__init__.cpython-310.pyc +0 -0
  80. package/gateway/core/zero_spec/__pycache__/detector.cpython-310.pyc +0 -0
  81. package/gateway/core/zero_spec/__pycache__/express_extractor.cpython-310.pyc +0 -0
  82. package/gateway/core/zero_spec/__pycache__/fastapi_extractor.cpython-310.pyc +0 -0
  83. package/gateway/core/zero_spec/__pycache__/nestjs_extractor.cpython-310.pyc +0 -0
  84. package/gateway/core/zero_spec/detector.py +353 -0
  85. package/gateway/core/zero_spec/express_extractor.py +483 -0
  86. package/gateway/core/zero_spec/fastapi_extractor.py +254 -0
  87. package/gateway/core/zero_spec/nestjs_extractor.py +369 -0
  88. package/gateway/tasks/__init__.py +1 -0
  89. package/gateway/tasks/__pycache__/__init__.cpython-310.pyc +0 -0
  90. package/gateway/tasks/__pycache__/check_policy.cpython-310.pyc +0 -0
  91. package/gateway/tasks/__pycache__/check_policy_v2.cpython-310.pyc +0 -0
  92. package/gateway/tasks/__pycache__/check_policy_v3.cpython-310.pyc +0 -0
  93. package/gateway/tasks/__pycache__/explain_diff.cpython-310.pyc +0 -0
  94. package/gateway/tasks/__pycache__/explain_diff_v2.cpython-310.pyc +0 -0
  95. package/gateway/tasks/__pycache__/validate_api.cpython-310.pyc +0 -0
  96. package/gateway/tasks/__pycache__/validate_api_v2.cpython-310.pyc +0 -0
  97. package/gateway/tasks/__pycache__/validate_api_v3.cpython-310.pyc +0 -0
  98. package/gateway/tasks/check_policy.py +177 -0
  99. package/gateway/tasks/check_policy_v2.py +255 -0
  100. package/gateway/tasks/check_policy_v3.py +255 -0
  101. package/gateway/tasks/explain_diff.py +305 -0
  102. package/gateway/tasks/explain_diff_v2.py +267 -0
  103. package/gateway/tasks/validate_api.py +131 -0
  104. package/gateway/tasks/validate_api_v2.py +208 -0
  105. package/gateway/tasks/validate_api_v3.py +163 -0
  106. package/package.json +3 -3
  107. package/adapters/codex-skill.js +0 -87
  108. package/adapters/cursor-extension.js +0 -190
  109. package/adapters/gemini-action.js +0 -93
  110. package/adapters/openai-function.js +0 -112
  111. package/adapters/xai-plugin.js +0 -151
  112. package/test-decision-engine.js +0 -181
  113. package/test-hook.js +0 -27
@@ -0,0 +1,177 @@
1
+ import yaml
2
+ from typing import Dict, List, Any
3
+ from core.registry import task_registry
4
+ from schemas.base import TaskRequest
5
+
6
+ register_task = task_registry.register
7
+
8
+ @register_task("check-policy", version="v1", description="Check API against policy rules")
9
+ def check_policy_handler(request: TaskRequest) -> Dict[str, Any]:
10
+ """Validate API specification against organizational policies"""
11
+
12
+ files = request.files
13
+ if not files:
14
+ raise ValueError("check-policy requires at least one API spec file")
15
+
16
+ # Load policy from config or use defaults
17
+ policy = request.config.get("policy", get_default_policy())
18
+ if isinstance(policy, str):
19
+ # If policy is a file path, load it
20
+ policy = load_policy(policy)
21
+
22
+ violations = []
23
+ warnings = []
24
+ passed_checks = []
25
+
26
+ for file_path in files:
27
+ spec = load_spec(file_path)
28
+
29
+ # Check various policy rules
30
+ violations_found, warnings_found, passed = check_spec_against_policy(spec, policy)
31
+ violations.extend(violations_found)
32
+ warnings.extend(warnings_found)
33
+ passed_checks.extend(passed)
34
+
35
+ compliance_score = calculate_compliance_score(violations, warnings, passed_checks)
36
+
37
+ return {
38
+ "compliant": len(violations) == 0,
39
+ "violations": violations,
40
+ "warnings": warnings,
41
+ "passed_checks": passed_checks,
42
+ "compliance_score": compliance_score,
43
+ "summary": {
44
+ "total_violations": len(violations),
45
+ "total_warnings": len(warnings),
46
+ "total_passed": len(passed_checks)
47
+ }
48
+ }
49
+
50
+ def load_spec(file_path: str) -> Dict:
51
+ """Load API specification"""
52
+ with open(file_path, 'r') as f:
53
+ if file_path.endswith('.yaml') or file_path.endswith('.yml'):
54
+ return yaml.safe_load(f)
55
+ else:
56
+ import json
57
+ return json.load(f)
58
+
59
+ def load_policy(policy_path: str) -> Dict:
60
+ """Load policy rules from file"""
61
+ with open(policy_path, 'r') as f:
62
+ return yaml.safe_load(f)
63
+
64
+ def get_default_policy() -> Dict:
65
+ """Get default policy rules"""
66
+ return {
67
+ "rules": {
68
+ "require_version": True,
69
+ "require_description": True,
70
+ "require_auth": True,
71
+ "require_https": True,
72
+ "max_path_depth": 5,
73
+ "naming_convention": "kebab-case",
74
+ "require_response_codes": ["200", "400", "500"],
75
+ "require_request_validation": True
76
+ },
77
+ "severity": {
78
+ "require_version": "high",
79
+ "require_auth": "high",
80
+ "require_https": "high",
81
+ "require_description": "low",
82
+ "max_path_depth": "medium",
83
+ "naming_convention": "low"
84
+ }
85
+ }
86
+
87
+ def check_spec_against_policy(spec: Dict, policy: Dict) -> tuple:
88
+ """Check specification against policy rules"""
89
+ violations = []
90
+ warnings = []
91
+ passed = []
92
+
93
+ rules = policy.get("rules", {})
94
+ severity = policy.get("severity", {})
95
+
96
+ # Check version requirement
97
+ if rules.get("require_version"):
98
+ if "openapi" in spec or "swagger" in spec:
99
+ passed.append("API version specified")
100
+ else:
101
+ violation = {
102
+ "rule": "require_version",
103
+ "message": "API specification must include version",
104
+ "severity": severity.get("require_version", "medium")
105
+ }
106
+ if violation["severity"] == "high":
107
+ violations.append(violation)
108
+ else:
109
+ warnings.append(violation["message"])
110
+
111
+ # Check description
112
+ if rules.get("require_description"):
113
+ if spec.get("info", {}).get("description"):
114
+ passed.append("API description present")
115
+ else:
116
+ violation = {
117
+ "rule": "require_description",
118
+ "message": "API must have a description",
119
+ "severity": severity.get("require_description", "low")
120
+ }
121
+ warnings.append(violation["message"])
122
+
123
+ # Check security/auth
124
+ if rules.get("require_auth"):
125
+ if spec.get("security") or spec.get("securityDefinitions") or spec.get("components", {}).get("securitySchemes"):
126
+ passed.append("Security definitions present")
127
+ else:
128
+ violations.append({
129
+ "rule": "require_auth",
130
+ "message": "API must define security schemes",
131
+ "severity": severity.get("require_auth", "high")
132
+ })
133
+
134
+ # Check HTTPS requirement
135
+ if rules.get("require_https"):
136
+ servers = spec.get("servers", [])
137
+ if servers:
138
+ non_https = [s for s in servers if not s.get("url", "").startswith("https://")]
139
+ if non_https:
140
+ violations.append({
141
+ "rule": "require_https",
142
+ "message": f"All servers must use HTTPS. Found non-HTTPS: {len(non_https)}",
143
+ "severity": severity.get("require_https", "high")
144
+ })
145
+ else:
146
+ passed.append("All servers use HTTPS")
147
+ else:
148
+ warnings.append("No servers defined to check HTTPS requirement")
149
+
150
+ # Check path depth
151
+ max_depth = rules.get("max_path_depth", 5)
152
+ if "paths" in spec:
153
+ for path in spec["paths"]:
154
+ depth = len([p for p in path.split("/") if p])
155
+ if depth > max_depth:
156
+ warnings.append(f"Path exceeds max depth ({max_depth}): {path}")
157
+
158
+ return violations, warnings, passed
159
+
160
+ def calculate_compliance_score(violations: List, warnings: List, passed: List) -> int:
161
+ """Calculate compliance score (0-100)"""
162
+ total_checks = len(violations) + len(warnings) + len(passed)
163
+ if total_checks == 0:
164
+ return 100
165
+
166
+ # High severity violations heavily impact score
167
+ high_violations = len([v for v in violations if v.get("severity") == "high"])
168
+ med_violations = len([v for v in violations if v.get("severity") == "medium"])
169
+ low_violations = len([v for v in violations if v.get("severity") == "low"])
170
+
171
+ score = 100
172
+ score -= high_violations * 20
173
+ score -= med_violations * 10
174
+ score -= low_violations * 5
175
+ score -= len(warnings) * 2
176
+
177
+ return max(0, score)
@@ -0,0 +1,255 @@
1
+ """
2
+ Check Policy task with Evidence Contract - ONLY enforces implemented rules
3
+ V12 Core Hardening
4
+ """
5
+
6
+ import yaml
7
+ import json
8
+ from typing import Dict, List
9
+ from pathlib import Path
10
+
11
+ from core.registry_v2 import task_registry
12
+ from schemas.requests import CheckPolicyRequest
13
+ from schemas.evidence import (
14
+ PolicyComplianceEvidence, Decision, Violation, ViolationSeverity,
15
+ Evidence, Remediation
16
+ )
17
+
18
+
19
+ # ONLY rules we actually implement
20
+ IMPLEMENTED_RULES = {
21
+ "require_openapi_version",
22
+ "require_info_description",
23
+ "require_security_definition",
24
+ "require_https_only",
25
+ "max_path_depth"
26
+ }
27
+
28
+
29
+ @task_registry.register("check-policy", version="1.0", description="Check API against implemented policy rules")
30
+ def check_policy_handler(request: CheckPolicyRequest) -> PolicyComplianceEvidence:
31
+ """Validate API spec against ACTUALLY IMPLEMENTED policy rules"""
32
+
33
+ # Get policy - either from file, inline, or defaults
34
+ if request.policy_file:
35
+ policy = load_policy(request.policy_file)
36
+ elif request.policy_inline:
37
+ policy = request.policy_inline
38
+ else:
39
+ policy = get_default_policy()
40
+
41
+ violations = []
42
+ evidence_list = []
43
+ checks_performed = 0
44
+ checks_passed = 0
45
+
46
+ # Process each spec file
47
+ for spec_file in request.spec_files:
48
+ spec = load_spec(spec_file)
49
+
50
+ # ONLY check rules we actually implement
51
+ for rule_name in IMPLEMENTED_RULES:
52
+ if not policy.get("rules", {}).get(rule_name, False):
53
+ continue # Rule not enabled in policy
54
+
55
+ checks_performed += 1
56
+ rule_passed, violation = check_rule(rule_name, spec, policy)
57
+
58
+ if rule_passed:
59
+ checks_passed += 1
60
+ evidence_list.append(Evidence(
61
+ rule=rule_name,
62
+ passed=True,
63
+ details={"file": spec_file, "status": "passed"}
64
+ ))
65
+ else:
66
+ violations.append(violation)
67
+ evidence_list.append(Evidence(
68
+ rule=rule_name,
69
+ passed=False,
70
+ details={"file": spec_file, "reason": violation.message}
71
+ ))
72
+
73
+ # Calculate compliance score
74
+ compliance_score = int((checks_passed / checks_performed * 100)) if checks_performed > 0 else 100
75
+
76
+ # Determine decision
77
+ if violations:
78
+ high_severity = any(v.severity == ViolationSeverity.HIGH for v in violations)
79
+ if high_severity:
80
+ decision = Decision.FAIL
81
+ exit_code = 1
82
+ else:
83
+ decision = Decision.WARN
84
+ exit_code = 0
85
+ else:
86
+ decision = Decision.PASS
87
+ exit_code = 0
88
+
89
+ # Build summary
90
+ if decision == Decision.PASS:
91
+ summary = f"Policy check passed: All {checks_performed} checks passed"
92
+ elif decision == Decision.WARN:
93
+ summary = f"Policy check passed with warnings: {len(violations)} low-severity issues"
94
+ else:
95
+ summary = f"Policy check failed: {len(violations)} violations found"
96
+
97
+ # Build remediation
98
+ remediation = None
99
+ if violations:
100
+ steps = []
101
+ for v in violations[:3]: # Show top 3 violations
102
+ if v.rule == "require_openapi_version":
103
+ steps.append("Add 'openapi' field with version (e.g., openapi: 3.0.0)")
104
+ elif v.rule == "require_info_description":
105
+ steps.append("Add description field under info section")
106
+ elif v.rule == "require_security_definition":
107
+ steps.append("Define security schemes in components.securitySchemes")
108
+ elif v.rule == "require_https_only":
109
+ steps.append("Update all server URLs to use HTTPS")
110
+ elif v.rule == "max_path_depth":
111
+ steps.append("Simplify API paths to reduce nesting depth")
112
+
113
+ remediation = Remediation(
114
+ summary="Fix policy violations to ensure API compliance",
115
+ steps=steps,
116
+ documentation="https://docs.delimit.ai/policy-rules"
117
+ )
118
+
119
+ return PolicyComplianceEvidence(
120
+ task="check-policy",
121
+ task_version="1.0",
122
+ decision=decision,
123
+ exit_code=exit_code,
124
+ violations=violations,
125
+ evidence=evidence_list,
126
+ remediation=remediation,
127
+ summary=summary,
128
+ correlation_id=request.correlation_id,
129
+ metrics={
130
+ "files_checked": len(request.spec_files),
131
+ "rules_checked": checks_performed,
132
+ "rules_passed": checks_passed,
133
+ "violations": len(violations)
134
+ },
135
+ compliance_score=compliance_score,
136
+ policy_version="1.0",
137
+ checks_performed=checks_performed,
138
+ checks_passed=checks_passed
139
+ )
140
+
141
+
142
+ def check_rule(rule_name: str, spec: Dict, policy: Dict) -> tuple[bool, Violation]:
143
+ """Check a specific rule - ONLY for implemented rules"""
144
+
145
+ severity_map = policy.get("severity", {})
146
+
147
+ if rule_name == "require_openapi_version":
148
+ if "openapi" in spec or "swagger" in spec:
149
+ return True, None
150
+ return False, Violation(
151
+ rule=rule_name,
152
+ severity=ViolationSeverity(severity_map.get(rule_name, "high")),
153
+ message="API specification must include OpenAPI version",
154
+ details={"missing": "openapi field"}
155
+ )
156
+
157
+ elif rule_name == "require_info_description":
158
+ if spec.get("info", {}).get("description"):
159
+ return True, None
160
+ return False, Violation(
161
+ rule=rule_name,
162
+ severity=ViolationSeverity(severity_map.get(rule_name, "low")),
163
+ message="API must have a description in info section",
164
+ details={"missing": "info.description"}
165
+ )
166
+
167
+ elif rule_name == "require_security_definition":
168
+ has_security = (
169
+ spec.get("security") or
170
+ spec.get("securityDefinitions") or
171
+ spec.get("components", {}).get("securitySchemes")
172
+ )
173
+ if has_security:
174
+ return True, None
175
+ return False, Violation(
176
+ rule=rule_name,
177
+ severity=ViolationSeverity(severity_map.get(rule_name, "high")),
178
+ message="API must define security schemes",
179
+ details={"missing": "security definitions"}
180
+ )
181
+
182
+ elif rule_name == "require_https_only":
183
+ servers = spec.get("servers", [])
184
+ if not servers:
185
+ return True, None # No servers defined, pass by default
186
+
187
+ non_https = [s for s in servers if not s.get("url", "").startswith("https://")]
188
+ if non_https:
189
+ return False, Violation(
190
+ rule=rule_name,
191
+ severity=ViolationSeverity(severity_map.get(rule_name, "high")),
192
+ message=f"All servers must use HTTPS ({len(non_https)} use HTTP)",
193
+ details={"non_https_count": str(len(non_https))}
194
+ )
195
+ return True, None
196
+
197
+ elif rule_name == "max_path_depth":
198
+ max_depth = policy.get("rules", {}).get("max_path_depth", 5)
199
+ violations_found = []
200
+
201
+ if "paths" in spec:
202
+ for path in spec["paths"]:
203
+ depth = len([p for p in path.split("/") if p])
204
+ if depth > max_depth:
205
+ violations_found.append(path)
206
+
207
+ if violations_found:
208
+ return False, Violation(
209
+ rule=rule_name,
210
+ severity=ViolationSeverity(severity_map.get(rule_name, "medium")),
211
+ message=f"Paths exceed max depth of {max_depth}",
212
+ path=violations_found[0],
213
+ details={"paths_over_limit": str(len(violations_found))}
214
+ )
215
+ return True, None
216
+
217
+ # Unknown rule - should never happen with IMPLEMENTED_RULES filter
218
+ return True, None
219
+
220
+
221
+ def get_default_policy() -> Dict:
222
+ """Get default policy with ONLY implemented rules"""
223
+ return {
224
+ "rules": {
225
+ "require_openapi_version": True,
226
+ "require_info_description": True,
227
+ "require_security_definition": True,
228
+ "require_https_only": True,
229
+ "max_path_depth": 5
230
+ },
231
+ "severity": {
232
+ "require_openapi_version": "high",
233
+ "require_info_description": "low",
234
+ "require_security_definition": "high",
235
+ "require_https_only": "high",
236
+ "max_path_depth": "medium"
237
+ }
238
+ }
239
+
240
+
241
+ def load_spec(file_path: str) -> Dict:
242
+ """Load API specification"""
243
+ path = Path(file_path)
244
+ with path.open('r') as f:
245
+ if path.suffix in ['.yaml', '.yml']:
246
+ return yaml.safe_load(f)
247
+ else:
248
+ return json.load(f)
249
+
250
+
251
+ def load_policy(policy_path: str) -> Dict:
252
+ """Load policy rules from file"""
253
+ path = Path(policy_path)
254
+ with path.open('r') as f:
255
+ return yaml.safe_load(f)
@@ -0,0 +1,255 @@
1
+ """
2
+ Check Policy task with Evidence Contract - V12 Final
3
+ ONLY enforces implemented rules with Pydantic v2
4
+ """
5
+
6
+ import yaml
7
+ import json
8
+ from typing import Dict, List
9
+ from pathlib import Path
10
+
11
+ from core.registry_v3 import task_registry
12
+ from schemas.requests_v2 import CheckPolicyRequest
13
+ from schemas.evidence import (
14
+ PolicyComplianceEvidence, Decision, Violation, ViolationSeverity,
15
+ Evidence, Remediation
16
+ )
17
+
18
+
19
+ # ONLY rules we actually implement
20
+ IMPLEMENTED_RULES = {
21
+ "require_openapi_version",
22
+ "require_info_description",
23
+ "require_security_definition",
24
+ "require_https_only",
25
+ "max_path_depth"
26
+ }
27
+
28
+
29
+ @task_registry.register("check-policy", task_version="1.0", description="Check API against implemented policy rules")
30
+ def check_policy_handler(request: CheckPolicyRequest) -> PolicyComplianceEvidence:
31
+ """Validate API spec against ACTUALLY IMPLEMENTED policy rules"""
32
+
33
+ # Get policy - either from file, inline, or defaults
34
+ if request.policy_file:
35
+ policy = load_policy(request.policy_file)
36
+ elif request.policy_inline:
37
+ policy = request.policy_inline
38
+ else:
39
+ policy = get_default_policy()
40
+
41
+ violations = []
42
+ evidence_list = []
43
+ checks_performed = 0
44
+ checks_passed = 0
45
+
46
+ # Process each spec file
47
+ for spec_file in request.spec_files:
48
+ spec = load_spec(spec_file)
49
+
50
+ # ONLY check rules we actually implement
51
+ for rule_name in IMPLEMENTED_RULES:
52
+ if not policy.get("rules", {}).get(rule_name, False):
53
+ continue # Rule not enabled in policy
54
+
55
+ checks_performed += 1
56
+ rule_passed, violation = check_rule(rule_name, spec, policy)
57
+
58
+ if rule_passed:
59
+ checks_passed += 1
60
+ evidence_list.append(Evidence(
61
+ rule=rule_name,
62
+ passed=True,
63
+ details={"file": spec_file, "status": "passed"}
64
+ ))
65
+ else:
66
+ violations.append(violation)
67
+ evidence_list.append(Evidence(
68
+ rule=rule_name,
69
+ passed=False,
70
+ details={"file": spec_file, "reason": violation.message}
71
+ ))
72
+
73
+ # Calculate compliance score
74
+ compliance_score = int((checks_passed / checks_performed * 100)) if checks_performed > 0 else 100
75
+
76
+ # Determine decision
77
+ if violations:
78
+ high_severity = any(v.severity == ViolationSeverity.HIGH for v in violations)
79
+ if high_severity:
80
+ decision = Decision.FAIL
81
+ exit_code = 1
82
+ else:
83
+ decision = Decision.WARN
84
+ exit_code = 0
85
+ else:
86
+ decision = Decision.PASS
87
+ exit_code = 0
88
+
89
+ # Build summary
90
+ if decision == Decision.PASS:
91
+ summary = f"Policy check passed: All {checks_performed} checks passed"
92
+ elif decision == Decision.WARN:
93
+ summary = f"Policy check passed with warnings: {len(violations)} low-severity issues"
94
+ else:
95
+ summary = f"Policy check failed: {len(violations)} violations found"
96
+
97
+ # Build remediation
98
+ remediation = None
99
+ if violations:
100
+ steps = []
101
+ for v in violations[:3]: # Show top 3 violations
102
+ if v.rule == "require_openapi_version":
103
+ steps.append("Add 'openapi' field with version (e.g., openapi: 3.0.0)")
104
+ elif v.rule == "require_info_description":
105
+ steps.append("Add description field under info section")
106
+ elif v.rule == "require_security_definition":
107
+ steps.append("Define security schemes in components.securitySchemes")
108
+ elif v.rule == "require_https_only":
109
+ steps.append("Update all server URLs to use HTTPS")
110
+ elif v.rule == "max_path_depth":
111
+ steps.append("Simplify API paths to reduce nesting depth")
112
+
113
+ remediation = Remediation(
114
+ summary="Fix policy violations to ensure API compliance",
115
+ steps=steps,
116
+ documentation="https://docs.delimit.ai/policy-rules"
117
+ )
118
+
119
+ return PolicyComplianceEvidence(
120
+ task="check-policy",
121
+ task_version="1.0",
122
+ decision=decision,
123
+ exit_code=exit_code,
124
+ violations=violations,
125
+ evidence=evidence_list,
126
+ remediation=remediation,
127
+ summary=summary,
128
+ correlation_id=request.correlation_id,
129
+ metrics={
130
+ "files_checked": len(request.spec_files),
131
+ "rules_checked": checks_performed,
132
+ "rules_passed": checks_passed,
133
+ "violations": len(violations)
134
+ },
135
+ compliance_score=compliance_score,
136
+ policy_version="1.0",
137
+ checks_performed=checks_performed,
138
+ checks_passed=checks_passed
139
+ )
140
+
141
+
142
+ def check_rule(rule_name: str, spec: Dict, policy: Dict) -> tuple[bool, Violation]:
143
+ """Check a specific rule - ONLY for implemented rules"""
144
+
145
+ severity_map = policy.get("severity", {})
146
+
147
+ if rule_name == "require_openapi_version":
148
+ if "openapi" in spec or "swagger" in spec:
149
+ return True, None
150
+ return False, Violation(
151
+ rule=rule_name,
152
+ severity=ViolationSeverity(severity_map.get(rule_name, "high")),
153
+ message="API specification must include OpenAPI version",
154
+ details={"missing": "openapi field"}
155
+ )
156
+
157
+ elif rule_name == "require_info_description":
158
+ if spec.get("info", {}).get("description"):
159
+ return True, None
160
+ return False, Violation(
161
+ rule=rule_name,
162
+ severity=ViolationSeverity(severity_map.get(rule_name, "low")),
163
+ message="API must have a description in info section",
164
+ details={"missing": "info.description"}
165
+ )
166
+
167
+ elif rule_name == "require_security_definition":
168
+ has_security = (
169
+ spec.get("security") or
170
+ spec.get("securityDefinitions") or
171
+ spec.get("components", {}).get("securitySchemes")
172
+ )
173
+ if has_security:
174
+ return True, None
175
+ return False, Violation(
176
+ rule=rule_name,
177
+ severity=ViolationSeverity(severity_map.get(rule_name, "high")),
178
+ message="API must define security schemes",
179
+ details={"missing": "security definitions"}
180
+ )
181
+
182
+ elif rule_name == "require_https_only":
183
+ servers = spec.get("servers", [])
184
+ if not servers:
185
+ return True, None # No servers defined, pass by default
186
+
187
+ non_https = [s for s in servers if not s.get("url", "").startswith("https://")]
188
+ if non_https:
189
+ return False, Violation(
190
+ rule=rule_name,
191
+ severity=ViolationSeverity(severity_map.get(rule_name, "high")),
192
+ message=f"All servers must use HTTPS ({len(non_https)} use HTTP)",
193
+ details={"non_https_count": str(len(non_https))}
194
+ )
195
+ return True, None
196
+
197
+ elif rule_name == "max_path_depth":
198
+ max_depth = policy.get("rules", {}).get("max_path_depth", 5)
199
+ violations_found = []
200
+
201
+ if "paths" in spec:
202
+ for path in spec["paths"]:
203
+ depth = len([p for p in path.split("/") if p])
204
+ if depth > max_depth:
205
+ violations_found.append(path)
206
+
207
+ if violations_found:
208
+ return False, Violation(
209
+ rule=rule_name,
210
+ severity=ViolationSeverity(severity_map.get(rule_name, "medium")),
211
+ message=f"Paths exceed max depth of {max_depth}",
212
+ path=violations_found[0],
213
+ details={"paths_over_limit": str(len(violations_found))}
214
+ )
215
+ return True, None
216
+
217
+ # Unknown rule - should never happen with IMPLEMENTED_RULES filter
218
+ return True, None
219
+
220
+
221
+ def get_default_policy() -> Dict:
222
+ """Get default policy with ONLY implemented rules"""
223
+ return {
224
+ "rules": {
225
+ "require_openapi_version": True,
226
+ "require_info_description": True,
227
+ "require_security_definition": True,
228
+ "require_https_only": True,
229
+ "max_path_depth": 5
230
+ },
231
+ "severity": {
232
+ "require_openapi_version": "high",
233
+ "require_info_description": "low",
234
+ "require_security_definition": "high",
235
+ "require_https_only": "high",
236
+ "max_path_depth": "medium"
237
+ }
238
+ }
239
+
240
+
241
+ def load_spec(file_path: str) -> Dict:
242
+ """Load API specification"""
243
+ path = Path(file_path)
244
+ with path.open('r') as f:
245
+ if path.suffix in ['.yaml', '.yml']:
246
+ return yaml.safe_load(f)
247
+ else:
248
+ return json.load(f)
249
+
250
+
251
+ def load_policy(policy_path: str) -> Dict:
252
+ """Load policy rules from file"""
253
+ path = Path(policy_path)
254
+ with path.open('r') as f:
255
+ return yaml.safe_load(f)