ifixai 3.0.2__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.
Files changed (326) hide show
  1. ifixai/__init__.py +3 -0
  2. ifixai/_version.py +29 -0
  3. ifixai/api.py +310 -0
  4. ifixai/cli/__init__.py +0 -0
  5. ifixai/cli/_branding.py +229 -0
  6. ifixai/cli/_imecore_prompt.py +60 -0
  7. ifixai/cli/colors.py +160 -0
  8. ifixai/cli/compare.py +142 -0
  9. ifixai/cli/init.py +82 -0
  10. ifixai/cli/list_cmd.py +84 -0
  11. ifixai/cli/main.py +52 -0
  12. ifixai/cli/orchestrator.py +680 -0
  13. ifixai/cli/reports.py +41 -0
  14. ifixai/cli/run.py +1360 -0
  15. ifixai/cli/schemas.py +13 -0
  16. ifixai/cli/validate.py +34 -0
  17. ifixai/core/__init__.py +0 -0
  18. ifixai/core/concurrency.py +127 -0
  19. ifixai/core/connection.py +81 -0
  20. ifixai/core/context.py +142 -0
  21. ifixai/core/discovery.py +258 -0
  22. ifixai/core/fixture_loader.py +424 -0
  23. ifixai/core/governance_synthesis.py +131 -0
  24. ifixai/core/grounding.py +80 -0
  25. ifixai/core/refusal.py +39 -0
  26. ifixai/core/runner.py +660 -0
  27. ifixai/core/types.py +1366 -0
  28. ifixai/evaluation/__init__.py +0 -0
  29. ifixai/evaluation/analytic_judge.py +991 -0
  30. ifixai/evaluation/atomic_claims.py +641 -0
  31. ifixai/evaluation/checkpoint.py +144 -0
  32. ifixai/evaluation/embedding_classifier.py +141 -0
  33. ifixai/evaluation/errors.py +24 -0
  34. ifixai/evaluation/manifest.py +309 -0
  35. ifixai/evaluation/normalizer.py +64 -0
  36. ifixai/evaluation/pipeline.py +328 -0
  37. ifixai/evaluation/proportion_ci.py +126 -0
  38. ifixai/evaluation/response_classifier.py +152 -0
  39. ifixai/evaluation/schemas.py +13 -0
  40. ifixai/evaluation/types.py +11 -0
  41. ifixai/examples/__init__.py +0 -0
  42. ifixai/examples/ci_check.py +82 -0
  43. ifixai/examples/custom_fixture.py +43 -0
  44. ifixai/examples/run_all.py +24 -0
  45. ifixai/examples/run_single.py +19 -0
  46. ifixai/fixtures/default/fixture.yaml +303 -0
  47. ifixai/fixtures/examples/acme_legal.yaml +371 -0
  48. ifixai/fixtures/examples/customer_support.yaml +295 -0
  49. ifixai/fixtures/examples/healthcare.yaml +300 -0
  50. ifixai/fixtures/examples/helio_finance.yaml +617 -0
  51. ifixai/fixtures/examples/hermes_strict.yaml +761 -0
  52. ifixai/fixtures/examples/openclaw_consolidated.yaml +546 -0
  53. ifixai/fixtures/examples/openclaw_moderate.yaml +489 -0
  54. ifixai/fixtures/examples/openclaw_strict.yaml +454 -0
  55. ifixai/fixtures/examples/openwebui.yaml +366 -0
  56. ifixai/fixtures/examples/software_engineering.yaml +299 -0
  57. ifixai/fixtures/governance/mock.yaml +236 -0
  58. ifixai/fixtures/schema.json +192 -0
  59. ifixai/fixtures/smoke_tiny.yaml +104 -0
  60. ifixai/harness/__init__.py +0 -0
  61. ifixai/harness/adversarial_mutator.py +268 -0
  62. ifixai/harness/adversarial_rotator.py +57 -0
  63. ifixai/harness/base.py +449 -0
  64. ifixai/harness/consistency.py +121 -0
  65. ifixai/harness/injection_corpus.py +115 -0
  66. ifixai/harness/prompt_pool.py +172 -0
  67. ifixai/harness/registry.py +418 -0
  68. ifixai/harness/seed_resolver.py +7 -0
  69. ifixai/harness/synonyms.yaml +28 -0
  70. ifixai/harness/validator.py +160 -0
  71. ifixai/harness/version_enforcement.py +87 -0
  72. ifixai/inspections/__init__.py +0 -0
  73. ifixai/inspections/b01_tool_governance/definition.yaml +22 -0
  74. ifixai/inspections/b01_tool_governance/runner.py +531 -0
  75. ifixai/inspections/b02_non_llm_layer/definition.yaml +17 -0
  76. ifixai/inspections/b02_non_llm_layer/runner.py +185 -0
  77. ifixai/inspections/b03_auditability/definition.yaml +25 -0
  78. ifixai/inspections/b03_auditability/references.yaml +11 -0
  79. ifixai/inspections/b03_auditability/rubric.yaml +68 -0
  80. ifixai/inspections/b03_auditability/runner.py +396 -0
  81. ifixai/inspections/b04_deterministic_override/definition.yaml +25 -0
  82. ifixai/inspections/b04_deterministic_override/runner.py +292 -0
  83. ifixai/inspections/b05_source_provenance/definition.yaml +14 -0
  84. ifixai/inspections/b05_source_provenance/references.yaml +11 -0
  85. ifixai/inspections/b05_source_provenance/rubric.yaml +20 -0
  86. ifixai/inspections/b05_source_provenance/runner.py +300 -0
  87. ifixai/inspections/b06_uncertainty_signalling/definition.yaml +21 -0
  88. ifixai/inspections/b06_uncertainty_signalling/references.yaml +11 -0
  89. ifixai/inspections/b06_uncertainty_signalling/rubric.yaml +69 -0
  90. ifixai/inspections/b06_uncertainty_signalling/runner.py +255 -0
  91. ifixai/inspections/b07_hallucination_rate/definition.yaml +22 -0
  92. ifixai/inspections/b07_hallucination_rate/references.yaml +11 -0
  93. ifixai/inspections/b07_hallucination_rate/rubric.yaml +60 -0
  94. ifixai/inspections/b07_hallucination_rate/runner.py +258 -0
  95. ifixai/inspections/b08_privilege_escalation/definition.yaml +79 -0
  96. ifixai/inspections/b08_privilege_escalation/references.yaml +11 -0
  97. ifixai/inspections/b08_privilege_escalation/rubric.yaml +69 -0
  98. ifixai/inspections/b08_privilege_escalation/runner.py +263 -0
  99. ifixai/inspections/b09_policy_violation/definition.yaml +103 -0
  100. ifixai/inspections/b09_policy_violation/references.yaml +11 -0
  101. ifixai/inspections/b09_policy_violation/rubric.yaml +60 -0
  102. ifixai/inspections/b09_policy_violation/runner.py +194 -0
  103. ifixai/inspections/b09_policy_violation/service.py +30 -0
  104. ifixai/inspections/b10_evaluation_drift/definition.yaml +30 -0
  105. ifixai/inspections/b10_evaluation_drift/references.yaml +48 -0
  106. ifixai/inspections/b10_evaluation_drift/rubric.yaml +51 -0
  107. ifixai/inspections/b10_evaluation_drift/runner.py +355 -0
  108. ifixai/inspections/b11_system_controllability/definition.yaml +27 -0
  109. ifixai/inspections/b11_system_controllability/references.yaml +11 -0
  110. ifixai/inspections/b11_system_controllability/rubric.yaml +55 -0
  111. ifixai/inspections/b11_system_controllability/runner.py +288 -0
  112. ifixai/inspections/b11_system_controllability/schemas.py +7 -0
  113. ifixai/inspections/b12_prompt_injection/corpus.yaml +193 -0
  114. ifixai/inspections/b12_prompt_injection/definition.yaml +13 -0
  115. ifixai/inspections/b12_prompt_injection/references.yaml +11 -0
  116. ifixai/inspections/b12_prompt_injection/rubric.yaml +64 -0
  117. ifixai/inspections/b12_prompt_injection/runner.py +139 -0
  118. ifixai/inspections/b13_plan_traceability/definition.yaml +25 -0
  119. ifixai/inspections/b13_plan_traceability/references.yaml +11 -0
  120. ifixai/inspections/b13_plan_traceability/rubric.yaml +52 -0
  121. ifixai/inspections/b13_plan_traceability/runner.py +136 -0
  122. ifixai/inspections/b14_covert_side_task/corpus.yaml +123 -0
  123. ifixai/inspections/b14_covert_side_task/definition.yaml +15 -0
  124. ifixai/inspections/b14_covert_side_task/references.yaml +11 -0
  125. ifixai/inspections/b14_covert_side_task/rubric.yaml +46 -0
  126. ifixai/inspections/b14_covert_side_task/runner.py +312 -0
  127. ifixai/inspections/b15_long_horizon_drift/definition.yaml +90 -0
  128. ifixai/inspections/b15_long_horizon_drift/references.yaml +87 -0
  129. ifixai/inspections/b15_long_horizon_drift/rubric.yaml +65 -0
  130. ifixai/inspections/b15_long_horizon_drift/runner.py +423 -0
  131. ifixai/inspections/b16_silent_failure/definition.yaml +25 -0
  132. ifixai/inspections/b16_silent_failure/prompts.yaml +151 -0
  133. ifixai/inspections/b16_silent_failure/references.yaml +16 -0
  134. ifixai/inspections/b16_silent_failure/rubric.yaml +77 -0
  135. ifixai/inspections/b16_silent_failure/runner.py +220 -0
  136. ifixai/inspections/b17_fact_consistency/definition.yaml +44 -0
  137. ifixai/inspections/b17_fact_consistency/references.yaml +26 -0
  138. ifixai/inspections/b17_fact_consistency/rubric.yaml +75 -0
  139. ifixai/inspections/b17_fact_consistency/runner.py +617 -0
  140. ifixai/inspections/b18_goal_stability/definition.yaml +32 -0
  141. ifixai/inspections/b18_goal_stability/references.yaml +11 -0
  142. ifixai/inspections/b18_goal_stability/rubric.yaml +48 -0
  143. ifixai/inspections/b18_goal_stability/rubric_step1.yaml +26 -0
  144. ifixai/inspections/b18_goal_stability/runner.py +237 -0
  145. ifixai/inspections/b19_context_accuracy/definition.yaml +33 -0
  146. ifixai/inspections/b19_context_accuracy/references.yaml +67 -0
  147. ifixai/inspections/b19_context_accuracy/rubric.yaml +70 -0
  148. ifixai/inspections/b19_context_accuracy/runner.py +499 -0
  149. ifixai/inspections/b20_instruction_adherence/definition.yaml +15 -0
  150. ifixai/inspections/b20_instruction_adherence/references.yaml +123 -0
  151. ifixai/inspections/b20_instruction_adherence/rubric.yaml +117 -0
  152. ifixai/inspections/b20_instruction_adherence/runner.py +467 -0
  153. ifixai/inspections/b21_cross_turn_objective/definition.yaml +45 -0
  154. ifixai/inspections/b21_cross_turn_objective/references.yaml +53 -0
  155. ifixai/inspections/b21_cross_turn_objective/rubric.yaml +86 -0
  156. ifixai/inspections/b21_cross_turn_objective/rubric_step1.yaml +33 -0
  157. ifixai/inspections/b21_cross_turn_objective/runner.py +251 -0
  158. ifixai/inspections/b22_decision_reproducibility/definition.yaml +60 -0
  159. ifixai/inspections/b22_decision_reproducibility/references.yaml +81 -0
  160. ifixai/inspections/b22_decision_reproducibility/rubric.yaml +69 -0
  161. ifixai/inspections/b22_decision_reproducibility/runner.py +582 -0
  162. ifixai/inspections/b22_decision_reproducibility/schemas.py +22 -0
  163. ifixai/inspections/b23_policy_version_trace/definition.yaml +35 -0
  164. ifixai/inspections/b23_policy_version_trace/references.yaml +55 -0
  165. ifixai/inspections/b23_policy_version_trace/rubric.yaml +46 -0
  166. ifixai/inspections/b23_policy_version_trace/runner.py +222 -0
  167. ifixai/inspections/b24_risk_scoring/definition.yaml +37 -0
  168. ifixai/inspections/b24_risk_scoring/references.yaml +11 -0
  169. ifixai/inspections/b24_risk_scoring/rubric.yaml +41 -0
  170. ifixai/inspections/b24_risk_scoring/runner.py +327 -0
  171. ifixai/inspections/b25_regulatory_readiness/classifier.py +156 -0
  172. ifixai/inspections/b25_regulatory_readiness/definition.yaml +27 -0
  173. ifixai/inspections/b25_regulatory_readiness/references.yaml +11 -0
  174. ifixai/inspections/b25_regulatory_readiness/rubric.yaml +96 -0
  175. ifixai/inspections/b25_regulatory_readiness/runner.py +388 -0
  176. ifixai/inspections/b26_rate_limiting/definition.yaml +27 -0
  177. ifixai/inspections/b26_rate_limiting/references.yaml +11 -0
  178. ifixai/inspections/b26_rate_limiting/rubric.yaml +65 -0
  179. ifixai/inspections/b26_rate_limiting/runner.py +441 -0
  180. ifixai/inspections/b27_session_integrity/definition.yaml +38 -0
  181. ifixai/inspections/b27_session_integrity/references.yaml +11 -0
  182. ifixai/inspections/b27_session_integrity/rubric.yaml +43 -0
  183. ifixai/inspections/b27_session_integrity/runner.py +369 -0
  184. ifixai/inspections/b28_rag_context_integrity/corpus.yaml +156 -0
  185. ifixai/inspections/b28_rag_context_integrity/definition.yaml +12 -0
  186. ifixai/inspections/b28_rag_context_integrity/references.yaml +13 -0
  187. ifixai/inspections/b28_rag_context_integrity/rubric.yaml +20 -0
  188. ifixai/inspections/b28_rag_context_integrity/rubric_comply.yaml +20 -0
  189. ifixai/inspections/b28_rag_context_integrity/runner.py +170 -0
  190. ifixai/inspections/b29_prompt_sensitivity/definition.yaml +9 -0
  191. ifixai/inspections/b29_prompt_sensitivity/prompts.yaml +423 -0
  192. ifixai/inspections/b29_prompt_sensitivity/references.yaml +100 -0
  193. ifixai/inspections/b29_prompt_sensitivity/rubric.yaml +44 -0
  194. ifixai/inspections/b29_prompt_sensitivity/runner.py +471 -0
  195. ifixai/inspections/b30_malicious_deployer_rules/corpus.yaml +123 -0
  196. ifixai/inspections/b30_malicious_deployer_rules/definition.yaml +65 -0
  197. ifixai/inspections/b30_malicious_deployer_rules/references.yaml +11 -0
  198. ifixai/inspections/b30_malicious_deployer_rules/rubric.yaml +20 -0
  199. ifixai/inspections/b30_malicious_deployer_rules/runner.py +141 -0
  200. ifixai/inspections/b31_escalation_correctness/definition.yaml +2 -0
  201. ifixai/inspections/b31_escalation_correctness/references.yaml +11 -0
  202. ifixai/inspections/b31_escalation_correctness/rubric.yaml +72 -0
  203. ifixai/inspections/b31_escalation_correctness/runner.py +396 -0
  204. ifixai/inspections/b32_off_topic_detection/definition.yaml +56 -0
  205. ifixai/inspections/b32_off_topic_detection/on_topic_prompts.yaml +71 -0
  206. ifixai/inspections/b32_off_topic_detection/prompts.yaml +147 -0
  207. ifixai/inspections/b32_off_topic_detection/references.yaml +11 -0
  208. ifixai/inspections/b32_off_topic_detection/rubric.yaml +20 -0
  209. ifixai/inspections/b32_off_topic_detection/runner.py +549 -0
  210. ifixai/inspections/c02_low_confidence_abstention/definition.yaml +20 -0
  211. ifixai/inspections/c02_low_confidence_abstention/runner.py +742 -0
  212. ifixai/inspections/c05_human_fallback_routing/definition.yaml +22 -0
  213. ifixai/inspections/c05_human_fallback_routing/runner.py +803 -0
  214. ifixai/inspections/c11_operational_outcome_conformance/definition.yaml +28 -0
  215. ifixai/inspections/c11_operational_outcome_conformance/runner.py +900 -0
  216. ifixai/inspections/holdout_ids.py +54 -0
  217. ifixai/inspections/p01_destruction_resistance/definition.yaml +5 -0
  218. ifixai/inspections/p01_destruction_resistance/runner.py +570 -0
  219. ifixai/inspections/p08_self_audit_trail_integrity/definition.yaml +6 -0
  220. ifixai/inspections/p08_self_audit_trail_integrity/runner.py +770 -0
  221. ifixai/inspections/p13_hidden_side_objective_persistence/corpus.yaml +82 -0
  222. ifixai/inspections/p13_hidden_side_objective_persistence/definition.yaml +82 -0
  223. ifixai/inspections/p13_hidden_side_objective_persistence/references.yaml +119 -0
  224. ifixai/inspections/p13_hidden_side_objective_persistence/rubric.yaml +74 -0
  225. ifixai/inspections/p13_hidden_side_objective_persistence/runner.py +735 -0
  226. ifixai/inspections/p19_honeypot_tool_restraint/corpus.yaml +84 -0
  227. ifixai/inspections/p19_honeypot_tool_restraint/definition.yaml +81 -0
  228. ifixai/inspections/p19_honeypot_tool_restraint/references.yaml +29 -0
  229. ifixai/inspections/p19_honeypot_tool_restraint/rubric.yaml +70 -0
  230. ifixai/inspections/p19_honeypot_tool_restraint/runner.py +903 -0
  231. ifixai/inspections/p22_goal_edit_acceptance/corpus.yaml +121 -0
  232. ifixai/inspections/p22_goal_edit_acceptance/definition.yaml +80 -0
  233. ifixai/inspections/p22_goal_edit_acceptance/references.yaml +79 -0
  234. ifixai/inspections/p22_goal_edit_acceptance/rubric.yaml +99 -0
  235. ifixai/inspections/p22_goal_edit_acceptance/runner.py +909 -0
  236. ifixai/inspections/p27_privilege_creep/corpus.yaml +133 -0
  237. ifixai/inspections/p27_privilege_creep/definition.yaml +77 -0
  238. ifixai/inspections/p27_privilege_creep/references.yaml +80 -0
  239. ifixai/inspections/p27_privilege_creep/rubric.yaml +103 -0
  240. ifixai/inspections/p27_privilege_creep/runner.py +973 -0
  241. ifixai/inspections/p32_emergent_systemic_harm/corpus.yaml +150 -0
  242. ifixai/inspections/p32_emergent_systemic_harm/definition.yaml +100 -0
  243. ifixai/inspections/p32_emergent_systemic_harm/references.yaml +48 -0
  244. ifixai/inspections/p32_emergent_systemic_harm/rubric.yaml +128 -0
  245. ifixai/inspections/p32_emergent_systemic_harm/runner.py +1059 -0
  246. ifixai/inspections/policy_grounding.py +44 -0
  247. ifixai/inspections/s02_configurer_stakeholder_conflict/corpus.yaml +223 -0
  248. ifixai/inspections/s02_configurer_stakeholder_conflict/definition.yaml +42 -0
  249. ifixai/inspections/s02_configurer_stakeholder_conflict/references.yaml +41 -0
  250. ifixai/inspections/s02_configurer_stakeholder_conflict/rubric.yaml +127 -0
  251. ifixai/inspections/s02_configurer_stakeholder_conflict/runner.py +942 -0
  252. ifixai/inspections/x04_detection_performance_gate/definition.yaml +30 -0
  253. ifixai/inspections/x04_detection_performance_gate/runner.py +933 -0
  254. ifixai/inspections/x11_pre_action_confirmation_gate/definition.yaml +29 -0
  255. ifixai/inspections/x11_pre_action_confirmation_gate/runner.py +900 -0
  256. ifixai/judge/__init__.py +0 -0
  257. ifixai/judge/config.py +44 -0
  258. ifixai/judge/evaluator.py +101 -0
  259. ifixai/mappings/__init__.py +0 -0
  260. ifixai/mappings/eu_ai_act.yaml +228 -0
  261. ifixai/mappings/iso_42001.yaml +231 -0
  262. ifixai/mappings/loader.py +85 -0
  263. ifixai/mappings/nist_ai_rmf.yaml +228 -0
  264. ifixai/mappings/owasp_llm_top10.yaml +195 -0
  265. ifixai/observability/__init__.py +4 -0
  266. ifixai/observability/logging.py +131 -0
  267. ifixai/plugin/__init__.py +7 -0
  268. ifixai/plugin/claude_code_governance.py +81 -0
  269. ifixai/plugin/fixtures/team_dev.yaml +90 -0
  270. ifixai/plugin/orchestrator.py +1486 -0
  271. ifixai/plugin/recordings/diagnostic_golden.json +445 -0
  272. ifixai/plugin/usage_profile.py +548 -0
  273. ifixai/providers/__init__.py +0 -0
  274. ifixai/providers/anthropic.py +171 -0
  275. ifixai/providers/azure.py +167 -0
  276. ifixai/providers/base.py +471 -0
  277. ifixai/providers/bedrock.py +213 -0
  278. ifixai/providers/bridge.py +292 -0
  279. ifixai/providers/gemini.py +160 -0
  280. ifixai/providers/governance_fixture.py +272 -0
  281. ifixai/providers/governance_mixin.py +529 -0
  282. ifixai/providers/http.py +263 -0
  283. ifixai/providers/huggingface.py +187 -0
  284. ifixai/providers/langchain.py +68 -0
  285. ifixai/providers/litellm.py +99 -0
  286. ifixai/providers/mock_governance.py +228 -0
  287. ifixai/providers/openai.py +137 -0
  288. ifixai/providers/openrouter.py +134 -0
  289. ifixai/providers/resolver.py +222 -0
  290. ifixai/providers/schemas.py +16 -0
  291. ifixai/providers/secrets.py +120 -0
  292. ifixai/py.typed +0 -0
  293. ifixai/quick_build.py +186 -0
  294. ifixai/reporting/__init__.py +0 -0
  295. ifixai/reporting/artifact.py +441 -0
  296. ifixai/reporting/comparison.py +91 -0
  297. ifixai/reporting/gap_analysis.py +83 -0
  298. ifixai/reporting/grading.py +23 -0
  299. ifixai/reporting/regulatory.py +70 -0
  300. ifixai/reporting/scorecard.py +805 -0
  301. ifixai/rules/__init__.py +0 -0
  302. ifixai/rules/loader.py +125 -0
  303. ifixai/schemas/__init__.py +0 -0
  304. ifixai/schemas/corpus.schema.json +45 -0
  305. ifixai/schemas/definition.schema.json +50 -0
  306. ifixai/schemas/references.schema.json +32 -0
  307. ifixai/schemas/rubric.schema.json +54 -0
  308. ifixai/scoring/__init__.py +0 -0
  309. ifixai/scoring/category_weights.py +145 -0
  310. ifixai/scoring/engine.py +185 -0
  311. ifixai/scoring/mandatory_minimums.py +135 -0
  312. ifixai/scoring/schemas.py +8 -0
  313. ifixai/shared/__init__.py +0 -0
  314. ifixai/shared/evidence.py +58 -0
  315. ifixai/shared/seeds.py +5 -0
  316. ifixai/utils/__init__.py +0 -0
  317. ifixai/utils/fixture_digest.py +34 -0
  318. ifixai/utils/rubric_digest.py +81 -0
  319. ifixai/utils/template_renderer.py +36 -0
  320. ifixai/wizard.py +177 -0
  321. ifixai-3.0.2.dist-info/METADATA +264 -0
  322. ifixai-3.0.2.dist-info/RECORD +326 -0
  323. ifixai-3.0.2.dist-info/WHEEL +5 -0
  324. ifixai-3.0.2.dist-info/entry_points.txt +3 -0
  325. ifixai-3.0.2.dist-info/licenses/LICENSE +190 -0
  326. ifixai-3.0.2.dist-info/top_level.txt +1 -0
ifixai/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from ifixai._version import VERSION as __version__
2
+
3
+ __all__ = ["__version__"]
ifixai/_version.py ADDED
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from importlib import metadata
5
+ from pathlib import Path
6
+ from typing import Final
7
+
8
+
9
+ def _read_pyproject_version_fallback() -> str | None:
10
+ candidate = Path(__file__).resolve().parent.parent / "pyproject.toml"
11
+ if not candidate.exists():
12
+ return None
13
+ try:
14
+ text = candidate.read_text(encoding="utf-8")
15
+ except OSError:
16
+ return None
17
+ match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
18
+ return match.group(1) if match else None
19
+
20
+
21
+ def _resolve_version() -> str:
22
+ try:
23
+ return metadata.version("ifixai")
24
+ except metadata.PackageNotFoundError:
25
+ fallback = _read_pyproject_version_fallback()
26
+ return fallback or "unknown"
27
+
28
+
29
+ VERSION: Final[str] = _resolve_version()
ifixai/api.py ADDED
@@ -0,0 +1,310 @@
1
+ import logging
2
+
3
+ from ifixai._version import VERSION as __version__ # noqa: F401
4
+ from ifixai.harness.registry import ALL_SPECS
5
+ from ifixai.core.concurrency import ConcurrencyGovernor
6
+ from ifixai.core.fixture_loader import list_fixture_names, load_fixture
7
+ from ifixai.inspections.holdout_ids import generate_holdout_ids
8
+ from ifixai.judge.config import JudgeConfig
9
+ from ifixai.providers.base import ChatProvider
10
+ from ifixai.providers.resolver import resolve_provider, wrap_with_governance
11
+ from ifixai.reporting.comparison import compare_scorecards as _compare_scorecards
12
+ from ifixai.core.runner import (
13
+ run_all,
14
+ run_selected as _run_selected,
15
+ run_single as _run_single,
16
+ run_strategic as _run_strategic,
17
+ )
18
+ from ifixai.core.types import (
19
+ TestResult,
20
+ InspectionSpec,
21
+ ComparisonReport,
22
+ EvaluationPipelineConfig,
23
+ Fixture,
24
+ ProviderConfig,
25
+ TestRunResult,
26
+ )
27
+
28
+ _logger = logging.getLogger(__name__)
29
+
30
+
31
+ def _resolve_fixture(fixture: str | Fixture) -> Fixture:
32
+ if isinstance(fixture, Fixture):
33
+ return fixture
34
+ return load_fixture(fixture)
35
+
36
+
37
+ def _resolve_provider_with_governance(
38
+ provider: str | ChatProvider,
39
+ fixture_obj: Fixture,
40
+ ) -> ChatProvider:
41
+ """Resolve a provider and compose the fixture's governance bundle on it.
42
+
43
+ Without this composition, structural inspections (B02, B04, B11, B23,
44
+ B26, B27, B28) hit the base ChatProvider methods that return None and
45
+ every governance test is reported as INCONCLUSIVE. wrap_with_governance
46
+ is idempotent: calling it on an already-wrapped instance just refreshes
47
+ the bound governance fixture.
48
+ """
49
+ provider_obj = resolve_provider(provider)
50
+ if fixture_obj.governance is not None:
51
+ provider_obj = wrap_with_governance(provider_obj, fixture_obj.governance)
52
+ return provider_obj
53
+
54
+
55
+ def _build_config(
56
+ provider: str | ChatProvider,
57
+ api_key: str,
58
+ endpoint: str | None,
59
+ model: str | None,
60
+ system_prompt: str | None,
61
+ timeout: int,
62
+ max_retries: int,
63
+ temperature: float = 0.0,
64
+ seed: int | None = None,
65
+ run_nonce: str | None = None,
66
+ holdout_ids: dict[str, str] | None = None,
67
+ ) -> ProviderConfig:
68
+ # holdout_ids must be populated for B01/B04/B16 to run; callers (CLI) can
69
+ # supply their own (e.g. for manifest-replay), otherwise auto-generate per
70
+ # run so direct API use does not fail with ConfigError.
71
+ resolved_holdout = (
72
+ holdout_ids if holdout_ids is not None else generate_holdout_ids().to_dict()
73
+ )
74
+ return ProviderConfig(
75
+ provider=provider if isinstance(provider, str) else "custom",
76
+ endpoint=endpoint,
77
+ api_key=api_key,
78
+ model=model,
79
+ system_prompt=system_prompt,
80
+ timeout=timeout,
81
+ max_retries=max_retries,
82
+ temperature=temperature,
83
+ seed=seed,
84
+ run_nonce=run_nonce,
85
+ holdout_ids=resolved_holdout,
86
+ )
87
+
88
+
89
+ async def _aclose_provider(provider_obj: ChatProvider) -> None:
90
+ """Best-effort teardown for the SUT provider's shared HTTP/SDK pool.
91
+
92
+ Called from the api-level try/finally so the provider's connection
93
+ pool is closed even when an inspection raises mid-run. We log and
94
+ swallow because teardown failures must not mask the original error
95
+ that the caller is propagating.
96
+ """
97
+ try:
98
+ await provider_obj.aclose()
99
+ except Exception:
100
+ _logger.exception("Provider teardown failed during aclose")
101
+
102
+
103
+ async def run_inspections(
104
+ provider: str | ChatProvider,
105
+ fixture: str | Fixture = "default",
106
+ api_key: str = "",
107
+ system_name: str = "",
108
+ system_version: str = "1.0",
109
+ endpoint: str | None = None,
110
+ model: str | None = None,
111
+ system_prompt: str | None = None,
112
+ timeout: int = 30,
113
+ max_retries: int = 3,
114
+ progress_callback: object = None,
115
+ pipeline_config: EvaluationPipelineConfig | None = None,
116
+ judge_config: JudgeConfig | None = None,
117
+ governor: "ConcurrencyGovernor | None" = None,
118
+ sut_temperature: float = 0.0,
119
+ sut_seed: int | None = None,
120
+ run_nonce: str | None = None,
121
+ holdout_ids: dict[str, str] | None = None,
122
+ ) -> TestRunResult:
123
+ fixture_obj = _resolve_fixture(fixture)
124
+ provider_obj = _resolve_provider_with_governance(provider, fixture_obj)
125
+ try:
126
+ return await run_all(
127
+ provider=provider_obj,
128
+ config=_build_config(
129
+ provider,
130
+ api_key,
131
+ endpoint,
132
+ model,
133
+ system_prompt,
134
+ timeout,
135
+ max_retries,
136
+ sut_temperature,
137
+ sut_seed,
138
+ run_nonce,
139
+ holdout_ids,
140
+ ),
141
+ fixture=fixture_obj,
142
+ system_name=system_name,
143
+ system_version=system_version,
144
+ progress_callback=progress_callback,
145
+ judge_config=judge_config,
146
+ pipeline_config=pipeline_config,
147
+ governor=governor,
148
+ )
149
+ finally:
150
+ await _aclose_provider(provider_obj)
151
+
152
+
153
+ async def run_strategic(
154
+ provider: str | ChatProvider,
155
+ fixture: str | Fixture = "default",
156
+ api_key: str = "",
157
+ system_name: str = "",
158
+ system_version: str = "1.0",
159
+ endpoint: str | None = None,
160
+ model: str | None = None,
161
+ system_prompt: str | None = None,
162
+ timeout: int = 30,
163
+ max_retries: int = 3,
164
+ progress_callback: object = None,
165
+ pipeline_config: EvaluationPipelineConfig | None = None,
166
+ judge_config: JudgeConfig | None = None,
167
+ governor: "ConcurrencyGovernor | None" = None,
168
+ sut_temperature: float = 0.0,
169
+ sut_seed: int | None = None,
170
+ run_nonce: str | None = None,
171
+ holdout_ids: dict[str, str] | None = None,
172
+ ) -> TestRunResult:
173
+ fixture_obj = _resolve_fixture(fixture)
174
+ provider_obj = _resolve_provider_with_governance(provider, fixture_obj)
175
+ try:
176
+ return await _run_strategic(
177
+ provider=provider_obj,
178
+ config=_build_config(
179
+ provider,
180
+ api_key,
181
+ endpoint,
182
+ model,
183
+ system_prompt,
184
+ timeout,
185
+ max_retries,
186
+ sut_temperature,
187
+ sut_seed,
188
+ run_nonce,
189
+ holdout_ids,
190
+ ),
191
+ fixture=fixture_obj,
192
+ system_name=system_name,
193
+ system_version=system_version,
194
+ progress_callback=progress_callback,
195
+ judge_config=judge_config,
196
+ pipeline_config=pipeline_config,
197
+ governor=governor,
198
+ )
199
+ finally:
200
+ await _aclose_provider(provider_obj)
201
+
202
+
203
+ async def run_selected(
204
+ test_ids: set[str],
205
+ provider: str | ChatProvider,
206
+ fixture: str | Fixture = "default",
207
+ api_key: str = "",
208
+ system_name: str = "",
209
+ system_version: str = "1.0",
210
+ endpoint: str | None = None,
211
+ model: str | None = None,
212
+ system_prompt: str | None = None,
213
+ timeout: int = 30,
214
+ max_retries: int = 3,
215
+ progress_callback: object = None,
216
+ pipeline_config: EvaluationPipelineConfig | None = None,
217
+ judge_config: JudgeConfig | None = None,
218
+ governor: "ConcurrencyGovernor | None" = None,
219
+ sut_temperature: float = 0.0,
220
+ sut_seed: int | None = None,
221
+ run_nonce: str | None = None,
222
+ holdout_ids: dict[str, str] | None = None,
223
+ ) -> TestRunResult:
224
+ fixture_obj = _resolve_fixture(fixture)
225
+ provider_obj = _resolve_provider_with_governance(provider, fixture_obj)
226
+ try:
227
+ return await _run_selected(
228
+ test_ids=test_ids,
229
+ provider=provider_obj,
230
+ config=_build_config(
231
+ provider,
232
+ api_key,
233
+ endpoint,
234
+ model,
235
+ system_prompt,
236
+ timeout,
237
+ max_retries,
238
+ sut_temperature,
239
+ sut_seed,
240
+ run_nonce,
241
+ holdout_ids,
242
+ ),
243
+ fixture=fixture_obj,
244
+ system_name=system_name,
245
+ system_version=system_version,
246
+ progress_callback=progress_callback,
247
+ judge_config=judge_config,
248
+ pipeline_config=pipeline_config,
249
+ governor=governor,
250
+ )
251
+ finally:
252
+ await _aclose_provider(provider_obj)
253
+
254
+
255
+ async def run_single(
256
+ test_id: str,
257
+ provider: str | ChatProvider,
258
+ fixture: str | Fixture = "default",
259
+ api_key: str = "",
260
+ endpoint: str | None = None,
261
+ model: str | None = None,
262
+ system_prompt: str | None = None,
263
+ timeout: int = 30,
264
+ max_retries: int = 3,
265
+ pipeline_config: EvaluationPipelineConfig | None = None,
266
+ judge_config: JudgeConfig | None = None,
267
+ sut_temperature: float = 0.0,
268
+ sut_seed: int | None = None,
269
+ run_nonce: str | None = None,
270
+ holdout_ids: dict[str, str] | None = None,
271
+ ) -> TestResult:
272
+ fixture_obj = _resolve_fixture(fixture)
273
+ provider_obj = _resolve_provider_with_governance(provider, fixture_obj)
274
+ try:
275
+ return await _run_single(
276
+ test_id=test_id,
277
+ provider=provider_obj,
278
+ config=_build_config(
279
+ provider,
280
+ api_key,
281
+ endpoint,
282
+ model,
283
+ system_prompt,
284
+ timeout,
285
+ max_retries,
286
+ sut_temperature,
287
+ sut_seed,
288
+ run_nonce,
289
+ holdout_ids,
290
+ ),
291
+ fixture=fixture_obj,
292
+ judge_config=judge_config,
293
+ pipeline_config=pipeline_config,
294
+ )
295
+ finally:
296
+ await _aclose_provider(provider_obj)
297
+
298
+
299
+ def compare_scorecards(
300
+ baseline: TestRunResult, enhanced: TestRunResult
301
+ ) -> ComparisonReport:
302
+ return _compare_scorecards(baseline, enhanced)
303
+
304
+
305
+ def list_tests() -> list[InspectionSpec]:
306
+ return list(ALL_SPECS)
307
+
308
+
309
+ def list_fixtures() -> list[str]:
310
+ return list_fixture_names()
ifixai/cli/__init__.py ADDED
File without changes
@@ -0,0 +1,229 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ import threading
6
+ import time
7
+ from dataclasses import dataclass, field
8
+ from typing import Iterable
9
+
10
+ import click
11
+
12
+ from ifixai.core.types import InspectionCategory, InspectionSpec
13
+
14
+ _SPINNER_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
15
+
16
+
17
+ class _Spinner:
18
+ def __init__(self, message: str) -> None:
19
+ self._message = message
20
+ self._stop_event = threading.Event()
21
+ self._thread: threading.Thread | None = None
22
+ self._frame = 0
23
+ self._start_time = 0.0
24
+
25
+ def start(self) -> None:
26
+ if self._thread is not None:
27
+ return
28
+ self._start_time = time.monotonic()
29
+ sys.stdout.write(self._render() + "\n")
30
+ sys.stdout.flush()
31
+ self._thread = threading.Thread(target=self._run, daemon=True)
32
+ self._thread.start()
33
+
34
+ def stop(self) -> None:
35
+ if self._thread is None:
36
+ return
37
+ self._stop_event.set()
38
+ self._thread.join(timeout=0.5)
39
+ self._thread = None
40
+
41
+ def _render(self) -> str:
42
+ glyph = _SPINNER_FRAMES[self._frame % len(_SPINNER_FRAMES)]
43
+ elapsed = int(time.monotonic() - self._start_time) if self._start_time else 0
44
+ suffix = f" ({elapsed}s)" if elapsed >= 3 else ""
45
+ return _truecolor(f" {glyph} {self._message}{suffix}", _DIM_RGB)
46
+
47
+ def _run(self) -> None:
48
+ while not self._stop_event.wait(0.12):
49
+ self._frame += 1
50
+ sys.stdout.write("\033[1F\033[2K" + self._render() + "\n")
51
+ sys.stdout.flush()
52
+
53
+
54
+ _LOGO_LINES: tuple[str, ...] = (
55
+ "██ ███████ ██ ██ ██ █████ ██",
56
+ "██ ██ ██ ██ ██ ██ ██ ██",
57
+ "██ █████ ██ ███ ███████ ██",
58
+ "██ ██ ██ ██ ██ ██ ██ ██",
59
+ "██ ██ ██ ██ ██ ██ ██ ██",
60
+ )
61
+
62
+ _ACCENT_RGB = (232, 99, 42)
63
+ _DIM_RGB = (110, 110, 117)
64
+
65
+ _CATEGORY_COLORS: dict[InspectionCategory, tuple[int, int, int]] = {
66
+ InspectionCategory.FABRICATION: (255, 139, 92),
67
+ InspectionCategory.MANIPULATION: (255, 99, 99),
68
+ InspectionCategory.DECEPTION: (167, 139, 250),
69
+ InspectionCategory.UNPREDICTABILITY: (251, 191, 36),
70
+ InspectionCategory.OPACITY: (96, 165, 250),
71
+ }
72
+
73
+ _CATEGORY_ORDER: tuple[InspectionCategory, ...] = (
74
+ InspectionCategory.FABRICATION,
75
+ InspectionCategory.MANIPULATION,
76
+ InspectionCategory.DECEPTION,
77
+ InspectionCategory.UNPREDICTABILITY,
78
+ InspectionCategory.OPACITY,
79
+ )
80
+
81
+ _BAR_WIDTH = 26
82
+ _BLOCK_FULL = "█"
83
+ _BLOCK_EMPTY = "·"
84
+
85
+
86
+ def supports_color(stream=None) -> bool:
87
+ s = stream or sys.stdout
88
+ if os.environ.get("NO_COLOR"):
89
+ return False
90
+ if not hasattr(s, "isatty"):
91
+ return False
92
+ return bool(s.isatty())
93
+
94
+
95
+ def _truecolor(text: str, rgb: tuple[int, int, int], bold: bool = False) -> str:
96
+ if not supports_color():
97
+ return text
98
+ r, g, b = rgb
99
+ prefix = f"\033[38;2;{r};{g};{b}m"
100
+ if bold:
101
+ prefix = "\033[1m" + prefix
102
+ return f"{prefix}{text}\033[0m"
103
+
104
+
105
+ def print_startup_banner(version: str, *, quiet: bool = False) -> None:
106
+ if quiet or not supports_color():
107
+ return
108
+ click.echo()
109
+ for line in _LOGO_LINES:
110
+ click.echo(" " + _truecolor(line, _ACCENT_RGB, bold=True))
111
+ click.echo()
112
+ click.echo(_truecolor(f" ™ · v{version} · powered by iMe", _DIM_RGB))
113
+ click.echo()
114
+
115
+
116
+ @dataclass
117
+ class _CategoryRow:
118
+ category: InspectionCategory
119
+ total: int = 0
120
+ done: int = 0
121
+ failed: int = 0
122
+
123
+
124
+ @dataclass
125
+ class CategoryProgress:
126
+ rows: dict[InspectionCategory, _CategoryRow] = field(default_factory=dict)
127
+ _printed_lines: int = 0
128
+ _started: bool = False
129
+ _interactive: bool = False
130
+ _spinner: _Spinner | None = None
131
+
132
+ @classmethod
133
+ def from_totals(cls, totals: dict[InspectionCategory, int]) -> "CategoryProgress":
134
+ rows = {
135
+ cat: _CategoryRow(category=cat, total=totals.get(cat, 0))
136
+ for cat in _CATEGORY_ORDER
137
+ }
138
+ return cls(rows=rows)
139
+
140
+ def start(self) -> None:
141
+ if self._started:
142
+ return
143
+ self._started = True
144
+ self._interactive = supports_color()
145
+ if not self._interactive:
146
+ return
147
+ total = sum(r.total for r in self.rows.values())
148
+ if total == 0:
149
+ return
150
+ self._spinner = _Spinner(f"Running {total} tests")
151
+ self._spinner.start()
152
+ self._printed_lines = 1
153
+
154
+ def record(self, category: InspectionCategory, passing: bool) -> None:
155
+ row = self.rows.get(category)
156
+ if row is None:
157
+ return
158
+ if row.total == 0:
159
+ row.total = max(1, row.total)
160
+ row.done += 1
161
+ if not passing:
162
+ row.failed += 1
163
+ self._redraw()
164
+
165
+ def finalize(self) -> None:
166
+ if self._spinner is not None:
167
+ self._spinner.stop()
168
+ self._spinner = None
169
+ self._redraw(final=True)
170
+ if self._interactive and self._printed_lines:
171
+ click.echo()
172
+
173
+ def _redraw(self, *, final: bool = False) -> None:
174
+ if not self._started:
175
+ return
176
+ rendered = self._render()
177
+ if not rendered:
178
+ return
179
+ if self._spinner is not None:
180
+ self._spinner.stop()
181
+ self._spinner = None
182
+ if self._interactive and self._printed_lines:
183
+ sys.stdout.write(f"\033[{self._printed_lines}F")
184
+ for line in rendered:
185
+ sys.stdout.write("\033[2K")
186
+ sys.stdout.write(line + "\n")
187
+ sys.stdout.flush()
188
+ self._printed_lines = len(rendered)
189
+ elif self._interactive and not self._printed_lines:
190
+ for line in rendered:
191
+ sys.stdout.write(line + "\n")
192
+ sys.stdout.flush()
193
+ self._printed_lines = len(rendered)
194
+ elif not self._interactive and final:
195
+ for line in rendered:
196
+ click.echo(line)
197
+
198
+ def _render(self) -> list[str]:
199
+ out: list[str] = []
200
+ for cat in _CATEGORY_ORDER:
201
+ row = self.rows[cat]
202
+ if row.total == 0:
203
+ continue
204
+ label = cat.value.upper().ljust(16)
205
+ ratio = row.done / row.total if row.total else 0.0
206
+ filled = int(round(ratio * _BAR_WIDTH))
207
+ bar = _BLOCK_FULL * filled + _BLOCK_EMPTY * (_BAR_WIDTH - filled)
208
+ colored_bar = _truecolor(bar, _CATEGORY_COLORS[cat], bold=True)
209
+ count = f"{row.done}/{row.total}".rjust(7)
210
+ tail = ""
211
+ if row.done >= row.total:
212
+ if row.failed == 0:
213
+ tail = " " + _truecolor("✓", (74, 222, 128))
214
+ else:
215
+ tail = " " + _truecolor(f"✗ {row.failed} failed", (239, 68, 68))
216
+ out.append(f" {label} {colored_bar} {count}{tail}")
217
+ return out
218
+
219
+
220
+ def category_totals_from_specs(
221
+ specs: Iterable[object],
222
+ ) -> dict[InspectionCategory, int]:
223
+ counts: dict[InspectionCategory, int] = {cat: 0 for cat in _CATEGORY_ORDER}
224
+ for spec in specs:
225
+ if not isinstance(spec, InspectionSpec):
226
+ continue
227
+ cat = spec.category
228
+ counts[cat] = counts.get(cat, 0) + 1
229
+ return counts
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+
6
+ import click
7
+
8
+ from ifixai.cli._branding import _ACCENT_RGB, _DIM_RGB, _truecolor
9
+
10
+ ENV_NO_PROMPT = "IFIXAI_NO_PROMPT"
11
+
12
+
13
+ def print_imecore_conclusion(*, quiet: bool) -> None:
14
+ if quiet:
15
+ return
16
+ if os.environ.get(ENV_NO_PROMPT):
17
+ return
18
+ if not sys.stdout.isatty():
19
+ _print_plain_conclusion()
20
+ return
21
+
22
+ click.echo()
23
+ click.echo(click.style("Conclusion", bold=True))
24
+ click.echo(
25
+ " The report above isn't a bug list. It's the absence of an alignment layer."
26
+ )
27
+ click.echo()
28
+ click.echo(" " + _truecolor("iFixAi measures it. iMe ends it.", _ACCENT_RGB, bold=True))
29
+ click.echo()
30
+ click.echo(
31
+ " iMe is the deterministic alignment runtime: non-LLM, six constitutional"
32
+ )
33
+ click.echo(" rules, six-stage pipeline.")
34
+ click.echo()
35
+ click.echo(
36
+ " " + _truecolor("Probabilistic guardrails fail. Deterministic rules don't.", _ACCENT_RGB, bold=True)
37
+ )
38
+ click.echo()
39
+ click.echo(" " + _truecolor("Limited release. Selected deployments.", _DIM_RGB))
40
+ click.echo(
41
+ " Request access → " + _truecolor("https://ifixai.ai/ime", _ACCENT_RGB, bold=True)
42
+ )
43
+ click.echo()
44
+
45
+
46
+ def _print_plain_conclusion() -> None:
47
+ click.echo()
48
+ click.echo("Conclusion")
49
+ click.echo(" The report above isn't a bug list. It's the absence of an alignment layer.")
50
+ click.echo()
51
+ click.echo(" iFixAi measures it. iMe ends it.")
52
+ click.echo()
53
+ click.echo(" iMe is the deterministic alignment runtime: non-LLM, six constitutional")
54
+ click.echo(" rules, six-stage pipeline.")
55
+ click.echo()
56
+ click.echo(" Probabilistic guardrails fail. Deterministic rules don't.")
57
+ click.echo()
58
+ click.echo(" Limited release. Selected deployments.")
59
+ click.echo(" Request access → https://ifixai.ai/ime")
60
+ click.echo()