patchi 0.7.5__py3-none-any.whl
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.
- patchi/__init__.py +1 -0
- patchi/__main__.py +13 -0
- patchi/cli/__init__.py +0 -0
- patchi/cli/command_families.py +235 -0
- patchi/cli/commands/__init__.py +0 -0
- patchi/cli/commands/access_cmd.py +65 -0
- patchi/cli/commands/agent_stats_cmd.py +273 -0
- patchi/cli/commands/agents_cmd.py +187 -0
- patchi/cli/commands/ai_cmd.py +308 -0
- patchi/cli/commands/assure_cmd.py +538 -0
- patchi/cli/commands/audit_cmd.py +286 -0
- patchi/cli/commands/auto_cmd.py +110 -0
- patchi/cli/commands/blame_cmd.py +53 -0
- patchi/cli/commands/chains_cmd.py +327 -0
- patchi/cli/commands/charter_cmd.py +173 -0
- patchi/cli/commands/chat_cmd.py +597 -0
- patchi/cli/commands/check_cmd.py +232 -0
- patchi/cli/commands/cleanup_cmd.py +328 -0
- patchi/cli/commands/cockpit_cmd.py +208 -0
- patchi/cli/commands/commands_cmd.py +160 -0
- patchi/cli/commands/cross_repo_cmd.py +113 -0
- patchi/cli/commands/deps_cmd.py +110 -0
- patchi/cli/commands/dev_check_cmd.py +342 -0
- patchi/cli/commands/dev_cmd.py +445 -0
- patchi/cli/commands/doctor_cmd.py +457 -0
- patchi/cli/commands/findings_cmd.py +187 -0
- patchi/cli/commands/fix_cmd.py +648 -0
- patchi/cli/commands/fix_review_cmd.py +273 -0
- patchi/cli/commands/heatmap_cmd.py +62 -0
- patchi/cli/commands/help_cmd.py +159 -0
- patchi/cli/commands/hosted_cmd.py +980 -0
- patchi/cli/commands/init.py +456 -0
- patchi/cli/commands/key_cmd.py +571 -0
- patchi/cli/commands/learn_cmd.py +140 -0
- patchi/cli/commands/link_cmd.py +104 -0
- patchi/cli/commands/log_cmd.py +57 -0
- patchi/cli/commands/memory_cmd.py +215 -0
- patchi/cli/commands/model_cmd.py +207 -0
- patchi/cli/commands/notify_cmd.py +213 -0
- patchi/cli/commands/notify_prompts.py +29 -0
- patchi/cli/commands/patch_cmd.py +231 -0
- patchi/cli/commands/plan_cmd.py +128 -0
- patchi/cli/commands/plugins_cmd.py +307 -0
- patchi/cli/commands/queue_cmd.py +152 -0
- patchi/cli/commands/quick_cmd.py +159 -0
- patchi/cli/commands/ready_cmd.py +292 -0
- patchi/cli/commands/report_cmd.py +416 -0
- patchi/cli/commands/restrict_cmd.py +127 -0
- patchi/cli/commands/review_cmd.py +225 -0
- patchi/cli/commands/rules_cmd.py +233 -0
- patchi/cli/commands/scan_cmd.py +1908 -0
- patchi/cli/commands/settings_cmd.py +45 -0
- patchi/cli/commands/status_cmd.py +679 -0
- patchi/cli/commands/test_cmd.py +1052 -0
- patchi/cli/commands/trend_cmd.py +296 -0
- patchi/cli/commands/undo_cmd.py +235 -0
- patchi/cli/commands/update_cmd.py +345 -0
- patchi/cli/commands/verify_cmd.py +56 -0
- patchi/cli/commands/vr_cmd.py +440 -0
- patchi/cli/commands/watch_cmd.py +304 -0
- patchi/cli/commands/web_cmd.py +114 -0
- patchi/cli/console.py +101 -0
- patchi/cli/display/__init__.py +1 -0
- patchi/cli/display/live_audit.py +269 -0
- patchi/cli/display/live_progress.py +169 -0
- patchi/cli/framework.py +241 -0
- patchi/cli/logo.py +128 -0
- patchi/cli/main.py +222 -0
- patchi/cli/registry.py +1171 -0
- patchi/cli/style.py +68 -0
- patchi/cli/themes.py +187 -0
- patchi/cli/ux.py +444 -0
- patchi/core/__init__.py +0 -0
- patchi/core/agents/__init__.py +0 -0
- patchi/core/agents/api_fuzzer_agent.py +141 -0
- patchi/core/agents/attack_agent.py +242 -0
- patchi/core/agents/base.py +1012 -0
- patchi/core/agents/bug_predictor_agent.py +58 -0
- patchi/core/agents/build_tool_validator.py +150 -0
- patchi/core/agents/cache.py +192 -0
- patchi/core/agents/chaos_agent.py +133 -0
- patchi/core/agents/cicd_generator.py +240 -0
- patchi/core/agents/comment_scanner.py +280 -0
- patchi/core/agents/console_logging_agent.py +156 -0
- patchi/core/agents/coordinator.py +858 -0
- patchi/core/agents/core_scanner.py +269 -0
- patchi/core/agents/coverage_prioritizer.py +181 -0
- patchi/core/agents/dead_code_hygiene.py +296 -0
- patchi/core/agents/dead_code_scanner.py +692 -0
- patchi/core/agents/dependency_scanner.py +725 -0
- patchi/core/agents/doc_claim_agent.py +573 -0
- patchi/core/agents/duplicate_scanner.py +568 -0
- patchi/core/agents/env_scanner.py +410 -0
- patchi/core/agents/feature_flag_agent.py +89 -0
- patchi/core/agents/frontend_framework_agent.py +133 -0
- patchi/core/agents/goroutine_agent.py +82 -0
- patchi/core/agents/governor.py +1972 -0
- patchi/core/agents/i18n_agent.py +95 -0
- patchi/core/agents/license_compliance.py +247 -0
- patchi/core/agents/linking_agent.py +251 -0
- patchi/core/agents/memory_profiler_agent.py +102 -0
- patchi/core/agents/modernization_agent.py +84 -0
- patchi/core/agents/mutation_agent.py +174 -0
- patchi/core/agents/nl_query_agent.py +74 -0
- patchi/core/agents/promise_rejection_agent.py +100 -0
- patchi/core/agents/refactoring_agent.py +286 -0
- patchi/core/agents/resource_leak_agent.py +91 -0
- patchi/core/agents/route_graph_scanner.py +951 -0
- patchi/core/agents/run_agent.py +106 -0
- patchi/core/agents/rust_unwrap_agent.py +100 -0
- patchi/core/agents/sbom_generator.py +284 -0
- patchi/core/agents/scanners.py +67 -0
- patchi/core/agents/side/__init__.py +1 -0
- patchi/core/agents/side/build_agent.py +65 -0
- patchi/core/agents/side/format_agent.py +50 -0
- patchi/core/agents/side/install_agent.py +63 -0
- patchi/core/agents/side_file_scanner.py +549 -0
- patchi/core/agents/snapshot_drift_detector.py +146 -0
- patchi/core/agents/spa_route_inventory.py +203 -0
- patchi/core/agents/test_scanner.py +442 -0
- patchi/core/agents/tool_health.py +109 -0
- patchi/core/agents/tool_runner.py +162 -0
- patchi/core/agents/type_scanner.py +168 -0
- patchi/core/agents/ui_scanner.py +558 -0
- patchi/core/ai/__init__.py +20 -0
- patchi/core/ai/agent_audit.py +153 -0
- patchi/core/ai/agent_profiler.py +277 -0
- patchi/core/ai/client.py +499 -0
- patchi/core/ai/cost_tracker.py +107 -0
- patchi/core/ai/model_router.py +318 -0
- patchi/core/ai/orchestrator.py +625 -0
- patchi/core/ai/prompts.py +681 -0
- patchi/core/ai/smart.py +302 -0
- patchi/core/ai/tool_executor.py +24 -0
- patchi/core/ai/tools/__init__.py +10 -0
- patchi/core/ai/tools/executor.py +621 -0
- patchi/core/ai/tools/realize.py +1160 -0
- patchi/core/ai/tools/registry.py +1577 -0
- patchi/core/assurance/__init__.py +33 -0
- patchi/core/assurance/builder.py +211 -0
- patchi/core/assurance/graph.py +342 -0
- patchi/core/assurance/invariants.py +168 -0
- patchi/core/atomic.py +61 -0
- patchi/core/attackers/__init__.py +17 -0
- patchi/core/attackers/api_abuse.py +89 -0
- patchi/core/attackers/auth_bypass.py +73 -0
- patchi/core/attackers/base.py +91 -0
- patchi/core/attackers/business_logic.py +88 -0
- patchi/core/attackers/planner.py +108 -0
- patchi/core/attackers/priv_escalation.py +88 -0
- patchi/core/attackers/recon.py +87 -0
- patchi/core/brain/__init__.py +0 -0
- patchi/core/brain/ast_utils/__init__.py +81 -0
- patchi/core/brain/ast_utils/assignments.py +219 -0
- patchi/core/brain/ast_utils/calls.py +139 -0
- patchi/core/brain/ast_utils/config.py +132 -0
- patchi/core/brain/ast_utils/control_flow.py +74 -0
- patchi/core/brain/ast_utils/dead_symbols.py +205 -0
- patchi/core/brain/ast_utils/decorators.py +108 -0
- patchi/core/brain/ast_utils/helpers.py +144 -0
- patchi/core/brain/ast_utils/imports.py +169 -0
- patchi/core/brain/ast_utils/scan.py +143 -0
- patchi/core/brain/ast_utils/taint.py +310 -0
- patchi/core/brain/audit.py +379 -0
- patchi/core/brain/baseline.py +195 -0
- patchi/core/brain/blast_radius.py +312 -0
- patchi/core/brain/body_tags.py +144 -0
- patchi/core/brain/brain.py +1161 -0
- patchi/core/brain/brain_context.py +393 -0
- patchi/core/brain/brain_watcher.py +340 -0
- patchi/core/brain/charter.py +473 -0
- patchi/core/brain/classifier.py +214 -0
- patchi/core/brain/cockpit.py +350 -0
- patchi/core/brain/code_query.py +710 -0
- patchi/core/brain/contract.py +889 -0
- patchi/core/brain/contract_diff.py +155 -0
- patchi/core/brain/council.py +800 -0
- patchi/core/brain/cross_repo.py +104 -0
- patchi/core/brain/dead_code_tools.py +157 -0
- patchi/core/brain/doc_validator.py +197 -0
- patchi/core/brain/domain_activator.py +2183 -0
- patchi/core/brain/enriched_context.py +220 -0
- patchi/core/brain/file_corpus.py +220 -0
- patchi/core/brain/framework.py +953 -0
- patchi/core/brain/freshness.py +273 -0
- patchi/core/brain/git_aware.py +206 -0
- patchi/core/brain/heatmap.py +104 -0
- patchi/core/brain/ignore_parser.py +153 -0
- patchi/core/brain/import_graph.py +486 -0
- patchi/core/brain/languages.py +397 -0
- patchi/core/brain/layered_brain.py +419 -0
- patchi/core/brain/learning.py +229 -0
- patchi/core/brain/orphaned_endpoints.py +313 -0
- patchi/core/brain/personas/__init__.py +648 -0
- patchi/core/brain/personas/bad_user.py +165 -0
- patchi/core/brain/personas/base.py +349 -0
- patchi/core/brain/proactive.py +807 -0
- patchi/core/brain/project_context.py +121 -0
- patchi/core/brain/project_reader.py +521 -0
- patchi/core/brain/reasoning.py +269 -0
- patchi/core/brain/route_detector/__init__.py +15 -0
- patchi/core/brain/route_detector/base.py +31 -0
- patchi/core/brain/route_detector/csharp.py +209 -0
- patchi/core/brain/route_detector/file_based.py +81 -0
- patchi/core/brain/route_detector/go.py +152 -0
- patchi/core/brain/route_detector/java.py +195 -0
- patchi/core/brain/route_detector/javascript.py +197 -0
- patchi/core/brain/route_detector/php.py +158 -0
- patchi/core/brain/route_detector/python.py +193 -0
- patchi/core/brain/route_detector/registry.py +25 -0
- patchi/core/brain/route_detector/ruby.py +134 -0
- patchi/core/brain/route_detector/rust.py +216 -0
- patchi/core/brain/route_detector/swift.py +118 -0
- patchi/core/brain/route_mapper.py +265 -0
- patchi/core/brain/scanner.py +2136 -0
- patchi/core/brain/secrets.py +280 -0
- patchi/core/brain/symbol_graph.py +1955 -0
- patchi/core/brain/team.py +28 -0
- patchi/core/brain/trace_log.py +132 -0
- patchi/core/brain/type_checker/__init__.py +65 -0
- patchi/core/brain/type_checker/base.py +60 -0
- patchi/core/brain/type_checker/csharp.py +59 -0
- patchi/core/brain/type_checker/dart.py +59 -0
- patchi/core/brain/type_checker/go.py +81 -0
- patchi/core/brain/type_checker/java.py +75 -0
- patchi/core/brain/type_checker/kotlin.py +75 -0
- patchi/core/brain/type_checker/php.py +88 -0
- patchi/core/brain/type_checker/python.py +87 -0
- patchi/core/brain/type_checker/rust.py +81 -0
- patchi/core/brain/type_checker/swift.py +59 -0
- patchi/core/brain/type_checker/typescript.py +298 -0
- patchi/core/brain/understander.py +130 -0
- patchi/core/brain/verify.py +379 -0
- patchi/core/campaigns/__init__.py +14 -0
- patchi/core/campaigns/base.py +62 -0
- patchi/core/campaigns/data_flow_abuse.py +153 -0
- patchi/core/campaigns/orchestrator.py +140 -0
- patchi/core/campaigns/state_transitions.py +178 -0
- patchi/core/ci_bundle.py +104 -0
- patchi/core/config.py +311 -0
- patchi/core/constants.py +245 -0
- patchi/core/debug/__init__.py +25 -0
- patchi/core/debug/adapters/__init__.py +11 -0
- patchi/core/debug/adapters/codelldb.py +379 -0
- patchi/core/debug/adapters/node.py +222 -0
- patchi/core/debug/adapters/powershell.py +210 -0
- patchi/core/debug/adapters/python.py +244 -0
- patchi/core/debug/capture.py +210 -0
- patchi/core/debug/dap_client.py +268 -0
- patchi/core/detector/__init__.py +0 -0
- patchi/core/detector/audit.py +194 -0
- patchi/core/detector/bus.py +226 -0
- patchi/core/detector/dispatcher.py +275 -0
- patchi/core/detector/event.py +254 -0
- patchi/core/detector/sigma_engine.py +378 -0
- patchi/core/detector/triage.py +289 -0
- patchi/core/export/sarif.py +113 -0
- patchi/core/fix/__init__.py +17 -0
- patchi/core/fix/applier.py +574 -0
- patchi/core/fix/base.py +510 -0
- patchi/core/fix/code_fixer.py +169 -0
- patchi/core/fix/dead_code_remover.py +131 -0
- patchi/core/fix/fix_agents.py +775 -0
- patchi/core/fix/patch.py +444 -0
- patchi/core/fix/risk_gate.py +383 -0
- patchi/core/fix/security_fixer.py +93 -0
- patchi/core/fix/verify_loop.py +204 -0
- patchi/core/fuzz/__init__.py +8 -0
- patchi/core/fuzz/corpus.py +137 -0
- patchi/core/fuzz/input_fuzzer.py +256 -0
- patchi/core/fuzz/sequence_fuzzer.py +162 -0
- patchi/core/fuzz/state_fuzzer.py +236 -0
- patchi/core/health.py +407 -0
- patchi/core/hosted/__init__.py +1 -0
- patchi/core/hosted/anomaly.py +298 -0
- patchi/core/hosted/audit_log.py +102 -0
- patchi/core/hosted/compliance_report.py +148 -0
- patchi/core/hosted/ip_reputation.py +169 -0
- patchi/core/hosted/log_parsers.py +251 -0
- patchi/core/hosted/tokens.py +127 -0
- patchi/core/hosted/watchlist.py +164 -0
- patchi/core/hosted/webhooks.py +156 -0
- patchi/core/memory.py +437 -0
- patchi/core/notifications/__init__.py +18 -0
- patchi/core/notifications/channels.py +192 -0
- patchi/core/notifications/digest.py +141 -0
- patchi/core/notifications/escalation.py +158 -0
- patchi/core/notifications/notifier.py +182 -0
- patchi/core/notifications/quiet_hours.py +69 -0
- patchi/core/plugins/__init__.py +48 -0
- patchi/core/plugins/analyzer.py +326 -0
- patchi/core/plugins/analyzers/__init__.py +6 -0
- patchi/core/plugins/analyzers/dead_code_analyzer.py +120 -0
- patchi/core/plugins/analyzers/secret_scanner.py +158 -0
- patchi/core/plugins/analyzers/todo_scanner.py +115 -0
- patchi/core/plugins/registry.py +335 -0
- patchi/core/queue.py +375 -0
- patchi/core/reliability/__init__.py +13 -0
- patchi/core/reliability/chaos.py +163 -0
- patchi/core/reliability/fault_injector.py +172 -0
- patchi/core/reliability/idempotency.py +128 -0
- patchi/core/reliability/recovery.py +147 -0
- patchi/core/runtime/__init__.py +5 -0
- patchi/core/runtime/tracer.py +230 -0
- patchi/core/scan_bus.py +156 -0
- patchi/core/security/__init__.py +1 -0
- patchi/core/security/ai_validator.py +462 -0
- patchi/core/security/app_mapper.py +216 -0
- patchi/core/security/app_profile.py +464 -0
- patchi/core/security/attack_feedback.py +141 -0
- patchi/core/security/attack_scenarios/__init__.py +1 -0
- patchi/core/security/attack_tree.py +176 -0
- patchi/core/security/auth_audit_agent.py +344 -0
- patchi/core/security/authz_agent.py +602 -0
- patchi/core/security/auto_fix_proactive.py +620 -0
- patchi/core/security/auto_fixer.py +660 -0
- patchi/core/security/auto_ticket.py +53 -0
- patchi/core/security/bandit_agent.py +152 -0
- patchi/core/security/blast_radius.py +226 -0
- patchi/core/security/browser_tester.py +333 -0
- patchi/core/security/business_logic_agent.py +263 -0
- patchi/core/security/catch_block_auditor.py +395 -0
- patchi/core/security/cdn_cache_agent.py +357 -0
- patchi/core/security/chain_analyzer.py +321 -0
- patchi/core/security/chain_to_assurance.py +168 -0
- patchi/core/security/charter.py +510 -0
- patchi/core/security/cloud_waf_detector.py +295 -0
- patchi/core/security/codeql_agent.py +233 -0
- patchi/core/security/compliance_agent.py +348 -0
- patchi/core/security/confidence_gate.py +423 -0
- patchi/core/security/container_scanner.py +282 -0
- patchi/core/security/crypto_agent.py +378 -0
- patchi/core/security/cve_monitor.py +262 -0
- patchi/core/security/dast_agent.py +865 -0
- patchi/core/security/defectdojo.py +238 -0
- patchi/core/security/defenders/__init__.py +75 -0
- patchi/core/security/defenders/auth_middleware.py +115 -0
- patchi/core/security/defenders/base.py +97 -0
- patchi/core/security/defenders/block_ip.py +71 -0
- patchi/core/security/defenders/block_ws_origin.py +36 -0
- patchi/core/security/defenders/crypto_fix.py +118 -0
- patchi/core/security/defenders/enforce_rate_limit.py +108 -0
- patchi/core/security/defenders/fix_code.py +93 -0
- patchi/core/security/defenders/invalidate_session.py +40 -0
- patchi/core/security/defenders/patch_config.py +70 -0
- patchi/core/security/defenders/rotate_secret.py +54 -0
- patchi/core/security/defenders/suspend_account.py +41 -0
- patchi/core/security/defenders/update_dependency.py +160 -0
- patchi/core/security/defense_layer.py +217 -0
- patchi/core/security/dependency_vulnerability_agent.py +616 -0
- patchi/core/security/detection_pipeline.py +289 -0
- patchi/core/security/dns_security_agent.py +497 -0
- patchi/core/security/domain_activator_v2.py +913 -0
- patchi/core/security/domain_loader.py +1004 -0
- patchi/core/security/domains/__init__.py +0 -0
- patchi/core/security/email_authentication_agent.py +417 -0
- patchi/core/security/env_var_validator.py +304 -0
- patchi/core/security/evidence.py +100 -0
- patchi/core/security/falco_runtime_agent.py +387 -0
- patchi/core/security/fix-playbooks/__init__.py +0 -0
- patchi/core/security/gated_finding.py +85 -0
- patchi/core/security/git_diff_activator.py +367 -0
- patchi/core/security/governance.py +208 -0
- patchi/core/security/gradual_gate.py +34 -0
- patchi/core/security/history.py +315 -0
- patchi/core/security/iac_scanner.py +250 -0
- patchi/core/security/ignore_expiry.py +111 -0
- patchi/core/security/ignore_learner.py +416 -0
- patchi/core/security/injection_agent.py +319 -0
- patchi/core/security/insecure_randomness_agent.py +288 -0
- patchi/core/security/intent_analyzer.py +326 -0
- patchi/core/security/jwt_agent.py +453 -0
- patchi/core/security/kubernetes_agent.py +638 -0
- patchi/core/security/layer2_orchestrator.py +341 -0
- patchi/core/security/llm_security_agent.py +252 -0
- patchi/core/security/misconfig_agent.py +600 -0
- patchi/core/security/mobile_security_agent.py +474 -0
- patchi/core/security/network_agent.py +376 -0
- patchi/core/security/noise_filter.py +274 -0
- patchi/core/security/orchestrator.py +313 -0
- patchi/core/security/pattern_context.py +125 -0
- patchi/core/security/pattern_loader.py +101 -0
- patchi/core/security/pentest/__init__.py +4 -0
- patchi/core/security/pentest/base.py +58 -0
- patchi/core/security/pentest/dalfox_adapter.py +40 -0
- patchi/core/security/pentest/ffuf_adapter.py +48 -0
- patchi/core/security/pentest/nuclei_adapter.py +50 -0
- patchi/core/security/pentest/registry.py +80 -0
- patchi/core/security/pentest/shannon_adapter.py +107 -0
- patchi/core/security/pentest/sqlmap_adapter.py +56 -0
- patchi/core/security/pentest/zap_adapter.py +52 -0
- patchi/core/security/plan_auditor.py +276 -0
- patchi/core/security/policy_engine.py +272 -0
- patchi/core/security/prechecks.py +172 -0
- patchi/core/security/precommit_hook.py +247 -0
- patchi/core/security/privacy_agent.py +361 -0
- patchi/core/security/push_notification_agent.py +417 -0
- patchi/core/security/pysa_agent.py +143 -0
- patchi/core/security/reasoning.py +442 -0
- patchi/core/security/red_team_agent.py +184 -0
- patchi/core/security/red_team_engine.py +1142 -0
- patchi/core/security/remediation.py +360 -0
- patchi/core/security/request_interceptor.py +294 -0
- patchi/core/security/runtime_validator.py +245 -0
- patchi/core/security/saml_sso_agent.py +401 -0
- patchi/core/security/sast_agent.py +213 -0
- patchi/core/security/sast_gate.py +95 -0
- patchi/core/security/scheduler.py +287 -0
- patchi/core/security/secrets_runtime_agent.py +476 -0
- patchi/core/security/security_agents.py +175 -0
- patchi/core/security/security_config.py +551 -0
- patchi/core/security/security_probe.py +762 -0
- patchi/core/security/security_taint.py +1005 -0
- patchi/core/security/sensitive_data_agent.py +271 -0
- patchi/core/security/service_mesh_agent.py +412 -0
- patchi/core/security/session_management_agent.py +245 -0
- patchi/core/security/ssrf_agent.py +422 -0
- patchi/core/security/supply_chain.py +449 -0
- patchi/core/security/threat_model_generator.py +378 -0
- patchi/core/security/threat_model_updater.py +196 -0
- patchi/core/security/tool_adapters.py +135 -0
- patchi/core/security/tool_verify.py +173 -0
- patchi/core/security/websocket_security_agent.py +188 -0
- patchi/core/snapshot.py +242 -0
- patchi/core/tenant.py +386 -0
- patchi/core/testing/__init__.py +17 -0
- patchi/core/testing/_browser.py +147 -0
- patchi/core/testing/ads_agent.py +141 -0
- patchi/core/testing/api_contract_agent.py +450 -0
- patchi/core/testing/app_discovery_agent.py +641 -0
- patchi/core/testing/app_launcher.py +184 -0
- patchi/core/testing/browser_test_agent.py +516 -0
- patchi/core/testing/e2e_flow_agent.py +354 -0
- patchi/core/testing/flake_detector_agent.py +292 -0
- patchi/core/testing/gate.py +31 -0
- patchi/core/testing/live_test_runner.py +304 -0
- patchi/core/testing/live_v2/__init__.py +1 -0
- patchi/core/testing/live_v2/browser_pool.py +349 -0
- patchi/core/testing/live_v2/browser_test_runner.py +313 -0
- patchi/core/testing/live_v2/runner.py +626 -0
- patchi/core/testing/live_v2/screenshot_manager.py +474 -0
- patchi/core/testing/live_v2/stress_orchestrator.py +523 -0
- patchi/core/testing/live_v2/video_recorder.py +303 -0
- patchi/core/testing/regression_agent.py +302 -0
- patchi/core/testing/security_test_agent.py +253 -0
- patchi/core/testing/stress_test_agent.py +203 -0
- patchi/core/testing/test_agents.py +50 -0
- patchi/core/testing/test_config.py +194 -0
- patchi/core/testing/ui_accessibility_agent.py +324 -0
- patchi/core/testing/ui_button_agent.py +294 -0
- patchi/core/testing/ui_layout_agent.py +330 -0
- patchi/core/testing/unit_test_agent.py +1149 -0
- patchi/core/testing/visual_regression_agent.py +260 -0
- patchi/web/__init__.py +1 -0
- patchi/web/api/__init__.py +1 -0
- patchi/web/api/brain_map.py +114 -0
- patchi/web/api/charts.py +47 -0
- patchi/web/api/chat.py +224 -0
- patchi/web/api/cicd.py +576 -0
- patchi/web/api/dast_evidence.py +49 -0
- patchi/web/api/dev_check.py +320 -0
- patchi/web/api/fix.py +134 -0
- patchi/web/api/guard.py +65 -0
- patchi/web/api/hosted.py +128 -0
- patchi/web/api/hosted_v2.py +234 -0
- patchi/web/api/live_testing.py +699 -0
- patchi/web/api/scan.py +582 -0
- patchi/web/api/smart.py +111 -0
- patchi/web/api/tenant.py +117 -0
- patchi/web/api/tools.py +85 -0
- patchi/web/api_legacy.py +2401 -0
- patchi/web/app.py +310 -0
- patchi/web/events.py +483 -0
- patchi/web/routes/__init__.py +1 -0
- patchi/web/routes/assurance.py +541 -0
- patchi/web/routes/brain.py +36 -0
- patchi/web/routes/charter.py +227 -0
- patchi/web/routes/chat.py +23 -0
- patchi/web/routes/dashboard.py +558 -0
- patchi/web/routes/dashboard_v2.py +402 -0
- patchi/web/routes/findings.py +330 -0
- patchi/web/routes/guard.py +35 -0
- patchi/web/routes/history.py +126 -0
- patchi/web/routes/landing.py +52 -0
- patchi/web/routes/live_testing.py +24 -0
- patchi/web/routes/review.py +57 -0
- patchi/web/routes/self_improvement.py +110 -0
- patchi/web/routes/settings.py +49 -0
- patchi/web/routes/tokens.py +29 -0
- patchi/web/spawn.py +139 -0
- patchi/web/ws.py +141 -0
- patchi-0.7.5.dist-info/METADATA +315 -0
- patchi-0.7.5.dist-info/RECORD +496 -0
- patchi-0.7.5.dist-info/WHEEL +5 -0
- patchi-0.7.5.dist-info/licenses/LICENSE +60 -0
- patchi-0.7.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"""
|
|
2
|
+
APIContractAgent — OpenAPI/JSON Schema compliance tests.
|
|
3
|
+
|
|
4
|
+
Validates API responses against:
|
|
5
|
+
- OpenAPI specifications
|
|
6
|
+
- JSON Schema definitions
|
|
7
|
+
- Contract-first API designs
|
|
8
|
+
- Generated API contracts from app contract
|
|
9
|
+
|
|
10
|
+
Tests for:
|
|
11
|
+
- Response structure compliance
|
|
12
|
+
- Type mismatches
|
|
13
|
+
- Missing required fields
|
|
14
|
+
- Extra unexpected fields
|
|
15
|
+
- Status code validation
|
|
16
|
+
- Parameter validation
|
|
17
|
+
|
|
18
|
+
Does NOT write to disk.
|
|
19
|
+
Does NOT call AI.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import time
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from ..agents.base import (
|
|
31
|
+
AgentDomain,
|
|
32
|
+
AgentGroup,
|
|
33
|
+
AgentInput,
|
|
34
|
+
AgentResult,
|
|
35
|
+
AgentStatus,
|
|
36
|
+
BaseAgent,
|
|
37
|
+
Severity,
|
|
38
|
+
make_finding,
|
|
39
|
+
register,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
_log = logging.getLogger("patchi.testing.api_contract_agent")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@register
|
|
46
|
+
class APIContractAgent(BaseAgent):
|
|
47
|
+
"""Agent for running API contract tests."""
|
|
48
|
+
|
|
49
|
+
group = AgentGroup.TEST
|
|
50
|
+
domain = AgentDomain.TESTING
|
|
51
|
+
name = "APIContractAgent"
|
|
52
|
+
description = "OpenAPI/JSON Schema compliance: response structure, types, status codes"
|
|
53
|
+
|
|
54
|
+
def _run(self, inp: AgentInput, result: AgentResult) -> None:
|
|
55
|
+
"""Run API contract compliance tests."""
|
|
56
|
+
start_time = time.time()
|
|
57
|
+
findings = []
|
|
58
|
+
|
|
59
|
+
# Find API contract files
|
|
60
|
+
contract_files = self._find_api_contracts(inp.root)
|
|
61
|
+
|
|
62
|
+
if not contract_files:
|
|
63
|
+
findings.append(
|
|
64
|
+
make_finding(
|
|
65
|
+
severity=Severity.INFO,
|
|
66
|
+
file="__api_contract__",
|
|
67
|
+
line_start=0,
|
|
68
|
+
title="No API contracts found",
|
|
69
|
+
description="No OpenAPI or JSON Schema files found in project",
|
|
70
|
+
evidence="No .json/.yaml files with API contract indicators found",
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
result.status = AgentStatus.SKIPPED
|
|
74
|
+
result.findings = findings
|
|
75
|
+
result.data.update({"contracts_found": 0, "needs_ai": False})
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Validate contracts against running API
|
|
79
|
+
validation_results = self._validate_contracts(contract_files, inp)
|
|
80
|
+
|
|
81
|
+
# Process validation results
|
|
82
|
+
# Note: loop targets use `r`, never `result` — `result` is the
|
|
83
|
+
# AgentResult this _run mutates (it is NOT a local here).
|
|
84
|
+
total_errors = sum(len(r.get("errors", [])) for r in validation_results.values())
|
|
85
|
+
|
|
86
|
+
if total_errors == 0:
|
|
87
|
+
findings.append(
|
|
88
|
+
make_finding(
|
|
89
|
+
severity=Severity.INFO,
|
|
90
|
+
file="__api_contract_summary__",
|
|
91
|
+
line_start=0,
|
|
92
|
+
title="API Contract Validation Passed",
|
|
93
|
+
description="All API endpoints comply with their contracts",
|
|
94
|
+
evidence="No contract violations found",
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
findings.append(
|
|
99
|
+
make_finding(
|
|
100
|
+
severity=Severity.HIGH,
|
|
101
|
+
file="__api_contract_summary__",
|
|
102
|
+
line_start=0,
|
|
103
|
+
title=f"API Contract Violations: {total_errors} errors",
|
|
104
|
+
description=f"Found {total_errors} contract violations across endpoints",
|
|
105
|
+
evidence=f"Violations found in {len([r for r in validation_results.values() if r.get('errors')])} endpoints",
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Add individual violation findings
|
|
110
|
+
for endpoint, r in validation_results.items():
|
|
111
|
+
for error in r.get("errors", []):
|
|
112
|
+
severity = self._map_contract_error_severity(error.get("type", "validation"))
|
|
113
|
+
|
|
114
|
+
findings.append(
|
|
115
|
+
make_finding(
|
|
116
|
+
severity=severity,
|
|
117
|
+
file=endpoint,
|
|
118
|
+
line_start=error.get("line", 0),
|
|
119
|
+
title=f"API Contract Violation: {error.get('type', 'unknown')}",
|
|
120
|
+
description=error.get("message", "Unknown contract violation"),
|
|
121
|
+
evidence=f"Expected: {error.get('expected', 'N/A')}\nActual: {error.get('actual', 'N/A')}\nPath: {error.get('path', 'N/A')}",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
duration = time.time() - start_time
|
|
126
|
+
|
|
127
|
+
# NOTE: FAILED here is a *findings* signal (violations found), not a
|
|
128
|
+
# crash — errors stays empty by design. The per-agent smoke sweep and
|
|
129
|
+
# the --pipeline orchestration gate both treat FAILED as a crash, so
|
|
130
|
+
# an agent that legitimately finds violations must be careful to only
|
|
131
|
+
# do so when the project actually ships contracts (and never against
|
|
132
|
+
# .patchi state — see _find_api_contracts).
|
|
133
|
+
result.status = AgentStatus.SUCCEEDED if total_errors == 0 else AgentStatus.FAILED
|
|
134
|
+
result.findings = findings
|
|
135
|
+
result.data.update(
|
|
136
|
+
{
|
|
137
|
+
"contracts_found": len(contract_files),
|
|
138
|
+
"endpoints_tested": len(validation_results),
|
|
139
|
+
"total_violations": total_errors,
|
|
140
|
+
"duration": round(duration, 2),
|
|
141
|
+
"needs_ai": False, # Contract validation doesn't require AI
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
def _find_api_contracts(self, root: Path) -> list[Path]:
|
|
147
|
+
"""Find API contract files (OpenAPI/Swagger/JSON Schema)."""
|
|
148
|
+
contract_files = []
|
|
149
|
+
|
|
150
|
+
# Look for common API contract file patterns
|
|
151
|
+
contract_patterns = [
|
|
152
|
+
"**/openapi*.json",
|
|
153
|
+
"**/openapi*.yaml",
|
|
154
|
+
"**/openapi*.yml",
|
|
155
|
+
"**/swagger*.json",
|
|
156
|
+
"**/swagger*.yaml",
|
|
157
|
+
"**/swagger*.yml",
|
|
158
|
+
"**/api*.json", # Might contain OpenAPI spec
|
|
159
|
+
"**/*.schema.json", # JSON Schema files
|
|
160
|
+
"**/schemas/**/*.json", # Schema directory
|
|
161
|
+
"**/specs/**/*.json", # Spec directory
|
|
162
|
+
"**/specs/**/*.yaml",
|
|
163
|
+
"**/specs/**/*.yml",
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
for pattern in contract_patterns:
|
|
167
|
+
for contract_file in root.rglob(pattern):
|
|
168
|
+
if not contract_file.is_file():
|
|
169
|
+
continue
|
|
170
|
+
# Never treat the tool's own state directory as project source:
|
|
171
|
+
# on case-insensitive filesystems (Windows) the agent's own
|
|
172
|
+
# cache file .patchi/cache/agent_cache/APIContractAgent.json
|
|
173
|
+
# matches the `**/api*.json` pattern, so a re-run would
|
|
174
|
+
# validate its own cache and report spurious violations (found
|
|
175
|
+
# by the smoke-sweep --pipeline orchestration gate).
|
|
176
|
+
if ".patchi" in contract_file.parts:
|
|
177
|
+
continue
|
|
178
|
+
# Verify it's actually an API contract by checking content
|
|
179
|
+
if self._is_api_contract(contract_file):
|
|
180
|
+
contract_files.append(contract_file)
|
|
181
|
+
|
|
182
|
+
return contract_files
|
|
183
|
+
|
|
184
|
+
def _is_api_contract(self, file_path: Path) -> bool:
|
|
185
|
+
"""Check if a file is actually an API contract."""
|
|
186
|
+
try:
|
|
187
|
+
content = file_path.read_text(encoding="utf-8")
|
|
188
|
+
|
|
189
|
+
# Check for OpenAPI/Swagger indicators
|
|
190
|
+
if "openapi" in content.lower() or "swagger" in content.lower():
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
# Check for JSON Schema indicators
|
|
194
|
+
if '"$schema"' in content and (
|
|
195
|
+
"json-schema" in content.lower() or "jsonschema" in content.lower()
|
|
196
|
+
):
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
# For YAML files, check for OpenAPI structure
|
|
200
|
+
if file_path.suffix.lower() in [".yaml", ".yml"]:
|
|
201
|
+
# Look for common OpenAPI fields
|
|
202
|
+
if any(
|
|
203
|
+
field in content for field in ["openapi:", "swagger:", "paths:", "components:"]
|
|
204
|
+
):
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
# For JSON files, check for API contract structure
|
|
208
|
+
if file_path.suffix.lower() == ".json":
|
|
209
|
+
try:
|
|
210
|
+
data = json.loads(content)
|
|
211
|
+
# Check for OpenAPI fields
|
|
212
|
+
if isinstance(data, dict) and any(
|
|
213
|
+
field in data for field in ["openapi", "swagger", "paths", "components"]
|
|
214
|
+
):
|
|
215
|
+
return True
|
|
216
|
+
# Check for JSON Schema fields
|
|
217
|
+
if isinstance(data, dict) and any(
|
|
218
|
+
field in data for field in ["$schema", "type", "properties", "required"]
|
|
219
|
+
):
|
|
220
|
+
return True
|
|
221
|
+
except json.JSONDecodeError:
|
|
222
|
+
pass # Not a valid JSON file
|
|
223
|
+
|
|
224
|
+
return False
|
|
225
|
+
except Exception as e:
|
|
226
|
+
_log.debug("APIContractAgent._is_api_contract failed: %s", e)
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
def _validate_contracts(self, contract_files: list[Path], inp: AgentInput) -> dict:
|
|
230
|
+
"""Validate contracts against the running API."""
|
|
231
|
+
results = {}
|
|
232
|
+
|
|
233
|
+
for contract_file in contract_files:
|
|
234
|
+
try:
|
|
235
|
+
# Read the contract
|
|
236
|
+
contract_content = contract_file.read_text(encoding="utf-8")
|
|
237
|
+
|
|
238
|
+
# Parse the contract based on type
|
|
239
|
+
if contract_file.suffix.lower() in [".json"]:
|
|
240
|
+
contract = json.loads(contract_content)
|
|
241
|
+
validation_result = self._validate_openapi_contract_json(
|
|
242
|
+
contract, contract_file, inp
|
|
243
|
+
)
|
|
244
|
+
else:
|
|
245
|
+
# For YAML, we'd need a YAML parser
|
|
246
|
+
# For now, we'll treat it as a string
|
|
247
|
+
validation_result = self._validate_openapi_contract_yaml(
|
|
248
|
+
contract_content, contract_file, inp
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
results[str(contract_file)] = validation_result
|
|
252
|
+
except Exception as e:
|
|
253
|
+
results[str(contract_file)] = {
|
|
254
|
+
"error": f"Could not parse contract file: {str(e)}",
|
|
255
|
+
"errors": [
|
|
256
|
+
{
|
|
257
|
+
"type": "parse_error",
|
|
258
|
+
"message": f"Failed to parse contract file: {str(e)}",
|
|
259
|
+
"path": "",
|
|
260
|
+
"expected": "valid OpenAPI/JSON Schema",
|
|
261
|
+
"actual": "parse error",
|
|
262
|
+
}
|
|
263
|
+
],
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return results
|
|
267
|
+
|
|
268
|
+
def _resolve_server_url(self, contract: dict, inp: AgentInput) -> str:
|
|
269
|
+
"""Extract server URL from contract or brain config."""
|
|
270
|
+
extra_base = (inp.extra or {}).get("base_url")
|
|
271
|
+
if extra_base:
|
|
272
|
+
return extra_base.rstrip("/")
|
|
273
|
+
test_config = inp.config.get("test_config", {})
|
|
274
|
+
if test_config.get("base_url"):
|
|
275
|
+
return test_config["base_url"].rstrip("/")
|
|
276
|
+
servers = contract.get("servers", [])
|
|
277
|
+
if servers:
|
|
278
|
+
return servers[0].get("url", "").rstrip("/")
|
|
279
|
+
brain = inp.brain or {}
|
|
280
|
+
return brain.get("base_url", "http://localhost:3000").rstrip("/")
|
|
281
|
+
|
|
282
|
+
def _validate_openapi_contract_json(
|
|
283
|
+
self, contract: dict, contract_file: Path, inp: AgentInput
|
|
284
|
+
) -> dict:
|
|
285
|
+
"""Validate OpenAPI contract by probing the running API."""
|
|
286
|
+
errors = []
|
|
287
|
+
if not isinstance(contract, dict) or (
|
|
288
|
+
"openapi" not in contract and "swagger" not in contract
|
|
289
|
+
):
|
|
290
|
+
errors.append(
|
|
291
|
+
{
|
|
292
|
+
"type": "invalid_spec",
|
|
293
|
+
"message": "File is not a valid OpenAPI specification",
|
|
294
|
+
"path": "$",
|
|
295
|
+
"expected": "OpenAPI object with 'openapi' or 'swagger' field",
|
|
296
|
+
"actual": f"{type(contract).__name__} object",
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
return {"errors": errors}
|
|
300
|
+
|
|
301
|
+
base_url = self._resolve_server_url(contract, inp)
|
|
302
|
+
paths = contract.get("paths", {})
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
import httpx
|
|
306
|
+
except ImportError:
|
|
307
|
+
errors.append({"type": "missing_dep", "message": "httpx required for API validation"})
|
|
308
|
+
return {"errors": errors}
|
|
309
|
+
|
|
310
|
+
for path, path_item in paths.items():
|
|
311
|
+
for method, operation in path_item.items():
|
|
312
|
+
if not isinstance(operation, dict):
|
|
313
|
+
continue
|
|
314
|
+
responses_spec = operation.get("responses", {})
|
|
315
|
+
if not responses_spec:
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
url = f"{base_url}{path}"
|
|
319
|
+
try:
|
|
320
|
+
resp = httpx.request(method.upper(), url, timeout=10)
|
|
321
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
322
|
+
errors.append(
|
|
323
|
+
{
|
|
324
|
+
"type": "connection_error",
|
|
325
|
+
"severity": "high",
|
|
326
|
+
"path": f"{method.upper()} {path}",
|
|
327
|
+
"message": f"Cannot reach {url}: {e}",
|
|
328
|
+
"expected": "200 range response",
|
|
329
|
+
"actual": "connection failed",
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
continue
|
|
333
|
+
except Exception as e:
|
|
334
|
+
errors.append(
|
|
335
|
+
{
|
|
336
|
+
"type": "request_error",
|
|
337
|
+
"severity": "medium",
|
|
338
|
+
"path": f"{method.upper()} {path}",
|
|
339
|
+
"message": f"Request failed: {e}",
|
|
340
|
+
"expected": "valid response",
|
|
341
|
+
"actual": str(e),
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
expected_statuses = [int(s) for s in responses_spec if s != "default"]
|
|
347
|
+
if expected_statuses and resp.status_code not in expected_statuses:
|
|
348
|
+
errors.append(
|
|
349
|
+
{
|
|
350
|
+
"type": "status_code_mismatch",
|
|
351
|
+
"severity": "high" if resp.status_code >= 400 else "medium",
|
|
352
|
+
"path": f"{method.upper()} {path}",
|
|
353
|
+
"message": f"Expected {expected_statuses}, got {resp.status_code}",
|
|
354
|
+
"expected": str(expected_statuses),
|
|
355
|
+
"actual": str(resp.status_code),
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Validate response body against first successful response schema
|
|
360
|
+
for status_code in ("200", "201", "204"):
|
|
361
|
+
response_schema = responses_spec.get(status_code, {})
|
|
362
|
+
content = response_schema.get("content", {})
|
|
363
|
+
for media_type, media_type_obj in content.items():
|
|
364
|
+
schema = media_type_obj.get("schema", {})
|
|
365
|
+
if not schema or not resp.content:
|
|
366
|
+
continue
|
|
367
|
+
if not self._validate_json_schema(resp.json(), schema):
|
|
368
|
+
errors.append(
|
|
369
|
+
{
|
|
370
|
+
"type": "type_mismatch",
|
|
371
|
+
"severity": "high",
|
|
372
|
+
"path": f"{method.upper()} {path} -> {status_code}",
|
|
373
|
+
"message": f"Response body does not match {media_type} schema",
|
|
374
|
+
"expected": str(schema),
|
|
375
|
+
"actual": resp.text[:500],
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
break # only validate first success schema
|
|
379
|
+
|
|
380
|
+
return {"errors": errors}
|
|
381
|
+
|
|
382
|
+
def _validate_openapi_contract_yaml(
|
|
383
|
+
self, content: str, contract_file: Path, inp: AgentInput
|
|
384
|
+
) -> dict:
|
|
385
|
+
"""Validate OpenAPI contract from YAML."""
|
|
386
|
+
try:
|
|
387
|
+
import yaml
|
|
388
|
+
|
|
389
|
+
contract = yaml.safe_load(content)
|
|
390
|
+
if isinstance(contract, dict):
|
|
391
|
+
return self._validate_openapi_contract_json(contract, contract_file, inp)
|
|
392
|
+
except ImportError:
|
|
393
|
+
pass
|
|
394
|
+
return {
|
|
395
|
+
"errors": [
|
|
396
|
+
{
|
|
397
|
+
"type": "yaml_not_implemented",
|
|
398
|
+
"message": "Install PyYAML to validate YAML contracts",
|
|
399
|
+
"path": "$",
|
|
400
|
+
"expected": "YAML parsing",
|
|
401
|
+
"actual": "PyYAML not installed",
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
def _validate_json_schema(self, instance: Any, schema: dict) -> bool:
|
|
407
|
+
"""Basic JSON Schema validation (subset — type and required fields)."""
|
|
408
|
+
if not isinstance(schema, dict):
|
|
409
|
+
return True
|
|
410
|
+
schema_type = schema.get("type")
|
|
411
|
+
if schema_type == "object" and isinstance(instance, dict):
|
|
412
|
+
required = schema.get("required", [])
|
|
413
|
+
for field in required:
|
|
414
|
+
if field not in instance:
|
|
415
|
+
return False
|
|
416
|
+
props = schema.get("properties", {})
|
|
417
|
+
for key, val in instance.items():
|
|
418
|
+
prop_schema = props.get(key, {})
|
|
419
|
+
if not self._validate_json_schema(val, prop_schema):
|
|
420
|
+
return False
|
|
421
|
+
elif schema_type == "array" and isinstance(instance, list):
|
|
422
|
+
items_schema = schema.get("items", {})
|
|
423
|
+
if items_schema:
|
|
424
|
+
for item in instance:
|
|
425
|
+
if not self._validate_json_schema(item, items_schema):
|
|
426
|
+
return False
|
|
427
|
+
elif schema_type == "string" and not isinstance(instance, str):
|
|
428
|
+
return False
|
|
429
|
+
elif schema_type == "integer" and not isinstance(instance, int):
|
|
430
|
+
return False
|
|
431
|
+
elif schema_type == "number" and not isinstance(instance, (int, float)):
|
|
432
|
+
return False
|
|
433
|
+
elif schema_type == "boolean" and not isinstance(instance, bool):
|
|
434
|
+
return False
|
|
435
|
+
return True
|
|
436
|
+
|
|
437
|
+
def _map_contract_error_severity(self, error_type: str) -> Severity:
|
|
438
|
+
"""Map contract error types to severity levels."""
|
|
439
|
+
error_severity = {
|
|
440
|
+
"required_field_missing": Severity.HIGH,
|
|
441
|
+
"type_mismatch": Severity.HIGH,
|
|
442
|
+
"invalid_format": Severity.MEDIUM,
|
|
443
|
+
"extra_field": Severity.LOW,
|
|
444
|
+
"status_code_mismatch": Severity.HIGH,
|
|
445
|
+
"parse_error": Severity.CRITICAL,
|
|
446
|
+
"runtime_validation_needed": Severity.INFO,
|
|
447
|
+
"yaml_not_implemented": Severity.INFO,
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return error_severity.get(error_type, Severity.MEDIUM)
|