quality-harness 0.5.0__tar.gz

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 (462) hide show
  1. quality_harness-0.5.0/.gitignore +45 -0
  2. quality_harness-0.5.0/AGENTS.md +45 -0
  3. quality_harness-0.5.0/Makefile +31 -0
  4. quality_harness-0.5.0/PKG-INFO +21 -0
  5. quality_harness-0.5.0/build.sh +5 -0
  6. quality_harness-0.5.0/common/AGENTS.md +20 -0
  7. quality_harness-0.5.0/common/README.md +34 -0
  8. quality_harness-0.5.0/common/harness_common/__init__.py +75 -0
  9. quality_harness-0.5.0/common/harness_common/access/__init__.py +1 -0
  10. quality_harness-0.5.0/common/harness_common/access/client.py +127 -0
  11. quality_harness-0.5.0/common/harness_common/access/context.py +29 -0
  12. quality_harness-0.5.0/common/harness_common/access/datasource.py +25 -0
  13. quality_harness-0.5.0/common/harness_common/access/fixture.py +198 -0
  14. quality_harness-0.5.0/common/harness_common/execution/__init__.py +3 -0
  15. quality_harness-0.5.0/common/harness_common/execution/artifact.py +17 -0
  16. quality_harness-0.5.0/common/harness_common/execution/execution.py +25 -0
  17. quality_harness-0.5.0/common/harness_common/execution/experiment.py +60 -0
  18. quality_harness-0.5.0/common/harness_common/execution/operation.py +37 -0
  19. quality_harness-0.5.0/common/harness_common/execution/outcome.py +15 -0
  20. quality_harness-0.5.0/common/harness_common/execution/overlay.py +82 -0
  21. quality_harness-0.5.0/common/harness_common/execution/reducer.py +26 -0
  22. quality_harness-0.5.0/common/harness_common/execution/run.py +34 -0
  23. quality_harness-0.5.0/common/harness_common/execution/verdict.py +252 -0
  24. quality_harness-0.5.0/common/harness_common/identity.py +13 -0
  25. quality_harness-0.5.0/common/harness_common/report_kit/__init__.py +19 -0
  26. quality_harness-0.5.0/common/harness_common/report_kit/doc.py +134 -0
  27. quality_harness-0.5.0/common/harness_common/report_kit/html.py +304 -0
  28. quality_harness-0.5.0/common/harness_common/system/__init__.py +1 -0
  29. quality_harness-0.5.0/common/harness_common/system/component.py +30 -0
  30. quality_harness-0.5.0/common/harness_common/system/deployment.py +31 -0
  31. quality_harness-0.5.0/common/harness_common/system/environment.py +55 -0
  32. quality_harness-0.5.0/common/harness_common/system/forge.py +12 -0
  33. quality_harness-0.5.0/common/harness_common/system/host.py +45 -0
  34. quality_harness-0.5.0/common/harness_common/system/product.py +21 -0
  35. quality_harness-0.5.0/common/harness_common/system/repository.py +26 -0
  36. quality_harness-0.5.0/common/harness_common/system/resource.py +92 -0
  37. quality_harness-0.5.0/common/harness_common/system/service.py +32 -0
  38. quality_harness-0.5.0/common/harness_common/system/workload.py +95 -0
  39. quality_harness-0.5.0/common/harness_common/tests/__init__.py +0 -0
  40. quality_harness-0.5.0/common/harness_common/tests/test_client.py +155 -0
  41. quality_harness-0.5.0/common/harness_common/tests/test_context.py +93 -0
  42. quality_harness-0.5.0/common/harness_common/tests/test_datasource.py +29 -0
  43. quality_harness-0.5.0/common/harness_common/tests/test_environment_service.py +213 -0
  44. quality_harness-0.5.0/common/harness_common/tests/test_fixture.py +212 -0
  45. quality_harness-0.5.0/common/harness_common/tests/test_import_boundary.py +45 -0
  46. quality_harness-0.5.0/common/harness_common/tests/test_overlay.py +38 -0
  47. quality_harness-0.5.0/common/harness_common/tests/test_report_kit.py +33 -0
  48. quality_harness-0.5.0/common/harness_common/tests/test_resource.py +62 -0
  49. quality_harness-0.5.0/common/harness_common/tests/test_source_identity_conformance.py +101 -0
  50. quality_harness-0.5.0/common/harness_common/tests/test_verdict_evalperf.py +291 -0
  51. quality_harness-0.5.0/common/harness_common/tests/test_workload.py +100 -0
  52. quality_harness-0.5.0/common/pyproject.toml +15 -0
  53. quality_harness-0.5.0/e2e_harness/AGENTS.md +57 -0
  54. quality_harness-0.5.0/e2e_harness/__init__.py +80 -0
  55. quality_harness-0.5.0/e2e_harness/__main__.py +10 -0
  56. quality_harness-0.5.0/e2e_harness/assertion.py +144 -0
  57. quality_harness-0.5.0/e2e_harness/casegen/__init__.py +41 -0
  58. quality_harness-0.5.0/e2e_harness/casegen/compiler.py +288 -0
  59. quality_harness-0.5.0/e2e_harness/casegen/contract.py +344 -0
  60. quality_harness-0.5.0/e2e_harness/casegen/discover.py +390 -0
  61. quality_harness-0.5.0/e2e_harness/caserun.py +241 -0
  62. quality_harness-0.5.0/e2e_harness/cli.py +167 -0
  63. quality_harness-0.5.0/e2e_harness/command.py +71 -0
  64. quality_harness-0.5.0/e2e_harness/core/__init__.py +0 -0
  65. quality_harness-0.5.0/e2e_harness/core/config.py +274 -0
  66. quality_harness-0.5.0/e2e_harness/core/profile.py +31 -0
  67. quality_harness-0.5.0/e2e_harness/engine.py +227 -0
  68. quality_harness-0.5.0/e2e_harness/environment.py +99 -0
  69. quality_harness-0.5.0/e2e_harness/examples/chat_cases.yaml +17 -0
  70. quality_harness-0.5.0/e2e_harness/examples/note_cases.yaml +27 -0
  71. quality_harness-0.5.0/e2e_harness/judge/__init__.py +0 -0
  72. quality_harness-0.5.0/e2e_harness/judge/metric/__init__.py +18 -0
  73. quality_harness-0.5.0/e2e_harness/judge/metric/base.py +89 -0
  74. quality_harness-0.5.0/e2e_harness/judge/metric/outcome.py +151 -0
  75. quality_harness-0.5.0/e2e_harness/judge/metric/registry.py +71 -0
  76. quality_harness-0.5.0/e2e_harness/matrix.py +32 -0
  77. quality_harness-0.5.0/e2e_harness/reducer.py +41 -0
  78. quality_harness-0.5.0/e2e_harness/runner/__init__.py +0 -0
  79. quality_harness-0.5.0/e2e_harness/runner/async_base.py +24 -0
  80. quality_harness-0.5.0/e2e_harness/runner/async_json_runner.py +74 -0
  81. quality_harness-0.5.0/e2e_harness/runner/async_sse_runner.py +122 -0
  82. quality_harness-0.5.0/e2e_harness/runner/base.py +102 -0
  83. quality_harness-0.5.0/e2e_harness/runner/events.py +103 -0
  84. quality_harness-0.5.0/e2e_harness/runner/headers.py +30 -0
  85. quality_harness-0.5.0/e2e_harness/runner/json_runner.py +71 -0
  86. quality_harness-0.5.0/e2e_harness/runner/sse_runner.py +141 -0
  87. quality_harness-0.5.0/e2e_harness/temporal.py +71 -0
  88. quality_harness-0.5.0/e2e_harness/tests/__init__.py +0 -0
  89. quality_harness-0.5.0/e2e_harness/tests/test_assertion.py +75 -0
  90. quality_harness-0.5.0/e2e_harness/tests/test_async_runners.py +305 -0
  91. quality_harness-0.5.0/e2e_harness/tests/test_casegen.py +115 -0
  92. quality_harness-0.5.0/e2e_harness/tests/test_casegen_draft.py +98 -0
  93. quality_harness-0.5.0/e2e_harness/tests/test_caserun.py +140 -0
  94. quality_harness-0.5.0/e2e_harness/tests/test_caserun_conformance.py +79 -0
  95. quality_harness-0.5.0/e2e_harness/tests/test_command.py +84 -0
  96. quality_harness-0.5.0/e2e_harness/tests/test_contract.py +264 -0
  97. quality_harness-0.5.0/e2e_harness/tests/test_core.py +247 -0
  98. quality_harness-0.5.0/e2e_harness/tests/test_discover_scripts.py +214 -0
  99. quality_harness-0.5.0/e2e_harness/tests/test_e2e_cli.py +112 -0
  100. quality_harness-0.5.0/e2e_harness/tests/test_e2e_engine.py +105 -0
  101. quality_harness-0.5.0/e2e_harness/tests/test_e2e_engine_integration.py +82 -0
  102. quality_harness-0.5.0/e2e_harness/tests/test_e2e_sse.py +86 -0
  103. quality_harness-0.5.0/e2e_harness/tests/test_environment.py +161 -0
  104. quality_harness-0.5.0/e2e_harness/tests/test_events.py +110 -0
  105. quality_harness-0.5.0/e2e_harness/tests/test_module_layout.py +123 -0
  106. quality_harness-0.5.0/e2e_harness/tests/test_outcome_metric.py +139 -0
  107. quality_harness-0.5.0/e2e_harness/tests/test_runner_integration.py +162 -0
  108. quality_harness-0.5.0/e2e_harness/tests/test_sse_parser.py +102 -0
  109. quality_harness-0.5.0/e2e_harness/tests/test_sse_runner.py +213 -0
  110. quality_harness-0.5.0/e2e_harness/tests/test_tech_debt.py +36 -0
  111. quality_harness-0.5.0/e2e_harness/tests/test_temporal.py +54 -0
  112. quality_harness-0.5.0/eval_harness/AGENTS.md +50 -0
  113. quality_harness-0.5.0/eval_harness/README.md +96 -0
  114. quality_harness-0.5.0/eval_harness/__init__.py +53 -0
  115. quality_harness-0.5.0/eval_harness/cli.py +69 -0
  116. quality_harness-0.5.0/eval_harness/config.py +128 -0
  117. quality_harness-0.5.0/eval_harness/engine.py +174 -0
  118. quality_harness-0.5.0/eval_harness/ingest.py +107 -0
  119. quality_harness-0.5.0/eval_harness/materials/cases/smoke.yaml +21 -0
  120. quality_harness-0.5.0/eval_harness/materials/experiments/smoke.yaml +17 -0
  121. quality_harness-0.5.0/eval_harness/metric/__init__.py +9 -0
  122. quality_harness-0.5.0/eval_harness/metric/aggregate.py +67 -0
  123. quality_harness-0.5.0/eval_harness/metric/base.py +71 -0
  124. quality_harness-0.5.0/eval_harness/metric/builtins.py +98 -0
  125. quality_harness-0.5.0/eval_harness/metric/llm_judge.py +81 -0
  126. quality_harness-0.5.0/eval_harness/metric/registry.py +40 -0
  127. quality_harness-0.5.0/eval_harness/model/__init__.py +8 -0
  128. quality_harness-0.5.0/eval_harness/model/evalset.py +145 -0
  129. quality_harness-0.5.0/eval_harness/model/experiment.py +213 -0
  130. quality_harness-0.5.0/eval_harness/model/sample.py +81 -0
  131. quality_harness-0.5.0/eval_harness/produce/__init__.py +7 -0
  132. quality_harness-0.5.0/eval_harness/produce/mock.py +37 -0
  133. quality_harness-0.5.0/eval_harness/report/__init__.py +11 -0
  134. quality_harness-0.5.0/eval_harness/report/html.py +240 -0
  135. quality_harness-0.5.0/eval_harness/report/pivot.py +211 -0
  136. quality_harness-0.5.0/eval_harness/report/render.py +290 -0
  137. quality_harness-0.5.0/eval_harness/ruff.toml +11 -0
  138. quality_harness-0.5.0/eval_harness/schedule/__init__.py +7 -0
  139. quality_harness-0.5.0/eval_harness/schedule/ratelimit.py +84 -0
  140. quality_harness-0.5.0/eval_harness/schedule/reconcile.py +242 -0
  141. quality_harness-0.5.0/eval_harness/tests/__init__.py +0 -0
  142. quality_harness-0.5.0/eval_harness/tests/eval_cases.py +39 -0
  143. quality_harness-0.5.0/eval_harness/tests/test_aggregate.py +40 -0
  144. quality_harness-0.5.0/eval_harness/tests/test_config_engine.py +193 -0
  145. quality_harness-0.5.0/eval_harness/tests/test_importer_evalset.py +102 -0
  146. quality_harness-0.5.0/eval_harness/tests/test_ingest.py +56 -0
  147. quality_harness-0.5.0/eval_harness/tests/test_llmspec.py +97 -0
  148. quality_harness-0.5.0/eval_harness/tests/test_metric_extras.py +97 -0
  149. quality_harness-0.5.0/eval_harness/tests/test_model.py +130 -0
  150. quality_harness-0.5.0/eval_harness/tests/test_multicorpus.py +99 -0
  151. quality_harness-0.5.0/eval_harness/tests/test_reconcile.py +272 -0
  152. quality_harness-0.5.0/eval_harness/tests/test_report.py +149 -0
  153. quality_harness-0.5.0/eval_harness/tests/test_worksheet.py +175 -0
  154. quality_harness-0.5.0/eval_harness/verdict.py +84 -0
  155. quality_harness-0.5.0/eval_harness/worksheet/__init__.py +8 -0
  156. quality_harness-0.5.0/eval_harness/worksheet/checkpoint.py +192 -0
  157. quality_harness-0.5.0/eval_harness/worksheet/worksheet.py +230 -0
  158. quality_harness-0.5.0/perf_harness/AGENTS.md +98 -0
  159. quality_harness-0.5.0/perf_harness/README.md +62 -0
  160. quality_harness-0.5.0/perf_harness/__init__.py +88 -0
  161. quality_harness-0.5.0/perf_harness/analysis/__init__.py +61 -0
  162. quality_harness-0.5.0/perf_harness/analysis/base.py +48 -0
  163. quality_harness-0.5.0/perf_harness/analysis/capacity.py +137 -0
  164. quality_harness-0.5.0/perf_harness/analysis/latency.py +153 -0
  165. quality_harness-0.5.0/perf_harness/analysis/resource.py +180 -0
  166. quality_harness-0.5.0/perf_harness/analysis/validity.py +131 -0
  167. quality_harness-0.5.0/perf_harness/cli.py +123 -0
  168. quality_harness-0.5.0/perf_harness/comparison.py +83 -0
  169. quality_harness-0.5.0/perf_harness/config.py +753 -0
  170. quality_harness-0.5.0/perf_harness/deploy.py +116 -0
  171. quality_harness-0.5.0/perf_harness/docs/extensions.md +209 -0
  172. quality_harness-0.5.0/perf_harness/docs/load-model-redesign.md +70 -0
  173. quality_harness-0.5.0/perf_harness/docs/metric-model.md +334 -0
  174. quality_harness-0.5.0/perf_harness/docs/result-semantics.md +42 -0
  175. quality_harness-0.5.0/perf_harness/docs/service-metrics.md +88 -0
  176. quality_harness-0.5.0/perf_harness/drive/__init__.py +1 -0
  177. quality_harness-0.5.0/perf_harness/drive/controller.py +117 -0
  178. quality_harness-0.5.0/perf_harness/drive/load.py +153 -0
  179. quality_harness-0.5.0/perf_harness/drive/runner.py +219 -0
  180. quality_harness-0.5.0/perf_harness/drive/scheduler.py +243 -0
  181. quality_harness-0.5.0/perf_harness/engine.py +605 -0
  182. quality_harness-0.5.0/perf_harness/examples/example.yaml +92 -0
  183. quality_harness-0.5.0/perf_harness/examples/mock.yaml +37 -0
  184. quality_harness-0.5.0/perf_harness/examples/mock_cases.yaml +7 -0
  185. quality_harness-0.5.0/perf_harness/judge.py +33 -0
  186. quality_harness-0.5.0/perf_harness/metric/__init__.py +79 -0
  187. quality_harness-0.5.0/perf_harness/metric/family.py +270 -0
  188. quality_harness-0.5.0/perf_harness/metric/reduce.py +187 -0
  189. quality_harness-0.5.0/perf_harness/metric/store.py +227 -0
  190. quality_harness-0.5.0/perf_harness/model.py +488 -0
  191. quality_harness-0.5.0/perf_harness/observe/__init__.py +29 -0
  192. quality_harness-0.5.0/perf_harness/observe/base.py +257 -0
  193. quality_harness-0.5.0/perf_harness/observe/configuration.py +170 -0
  194. quality_harness-0.5.0/perf_harness/observe/k8s.py +478 -0
  195. quality_harness-0.5.0/perf_harness/observe/metric.py +172 -0
  196. quality_harness-0.5.0/perf_harness/observe/prometheus.py +99 -0
  197. quality_harness-0.5.0/perf_harness/observe/prometheus_query.py +59 -0
  198. quality_harness-0.5.0/perf_harness/observe/registry.py +42 -0
  199. quality_harness-0.5.0/perf_harness/records.py +26 -0
  200. quality_harness-0.5.0/perf_harness/report/__init__.py +13 -0
  201. quality_harness-0.5.0/perf_harness/report/columns.py +25 -0
  202. quality_harness-0.5.0/perf_harness/report/lifecycle.py +82 -0
  203. quality_harness-0.5.0/perf_harness/report/palette.py +20 -0
  204. quality_harness-0.5.0/perf_harness/report/render.py +1268 -0
  205. quality_harness-0.5.0/perf_harness/ruff.toml +11 -0
  206. quality_harness-0.5.0/perf_harness/runio.py +584 -0
  207. quality_harness-0.5.0/perf_harness/sh.py +16 -0
  208. quality_harness-0.5.0/perf_harness/slo.py +90 -0
  209. quality_harness-0.5.0/perf_harness/tests/__init__.py +0 -0
  210. quality_harness-0.5.0/perf_harness/tests/test_metric_windows.py +279 -0
  211. quality_harness-0.5.0/perf_harness/tests/test_perf_analysis.py +341 -0
  212. quality_harness-0.5.0/perf_harness/tests/test_perf_config.py +404 -0
  213. quality_harness-0.5.0/perf_harness/tests/test_perf_engine.py +566 -0
  214. quality_harness-0.5.0/perf_harness/tests/test_perf_environment.py +164 -0
  215. quality_harness-0.5.0/perf_harness/tests/test_perf_facets.py +111 -0
  216. quality_harness-0.5.0/perf_harness/tests/test_perf_judge.py +77 -0
  217. quality_harness-0.5.0/perf_harness/tests/test_perf_lifecycle.py +275 -0
  218. quality_harness-0.5.0/perf_harness/tests/test_perf_metrics.py +264 -0
  219. quality_harness-0.5.0/perf_harness/tests/test_perf_native.py +132 -0
  220. quality_harness-0.5.0/perf_harness/tests/test_perf_observe.py +769 -0
  221. quality_harness-0.5.0/perf_harness/tests/test_perf_parsers.py +148 -0
  222. quality_harness-0.5.0/perf_harness/tests/test_perf_phases.py +165 -0
  223. quality_harness-0.5.0/perf_harness/tests/test_perf_probe.py +73 -0
  224. quality_harness-0.5.0/perf_harness/tests/test_perf_report.py +348 -0
  225. quality_harness-0.5.0/perf_harness/tests/test_perf_runio.py +334 -0
  226. quality_harness-0.5.0/perf_harness/tests/test_perf_runs.py +105 -0
  227. quality_harness-0.5.0/perf_harness/tests/test_perf_slo.py +444 -0
  228. quality_harness-0.5.0/perf_harness/tests/test_perf_stages.py +93 -0
  229. quality_harness-0.5.0/perf_harness/tests/test_prometheus_query.py +200 -0
  230. quality_harness-0.5.0/perf_harness/verdict.py +119 -0
  231. quality_harness-0.5.0/pyproject.toml +79 -0
  232. quality_harness-0.5.0/setup.py +4 -0
  233. quality_harness-0.5.0/toolbox/Makefile +16 -0
  234. quality_harness-0.5.0/toolbox/README.md +128 -0
  235. quality_harness-0.5.0/toolbox/docs/lifecycle.md +207 -0
  236. quality_harness-0.5.0/toolbox/harness_toolbox/AGENTS.md +50 -0
  237. quality_harness-0.5.0/toolbox/harness_toolbox/__init__.py +18 -0
  238. quality_harness-0.5.0/toolbox/harness_toolbox/_kube_options.py +15 -0
  239. quality_harness-0.5.0/toolbox/harness_toolbox/_mysql_values.py +46 -0
  240. quality_harness-0.5.0/toolbox/harness_toolbox/address.py +171 -0
  241. quality_harness-0.5.0/toolbox/harness_toolbox/client.py +9 -0
  242. quality_harness-0.5.0/toolbox/harness_toolbox/data_loader.py +61 -0
  243. quality_harness-0.5.0/toolbox/harness_toolbox/diagnostics.py +73 -0
  244. quality_harness-0.5.0/toolbox/harness_toolbox/environment.py +115 -0
  245. quality_harness-0.5.0/toolbox/harness_toolbox/errors.py +66 -0
  246. quality_harness-0.5.0/toolbox/harness_toolbox/host.py +44 -0
  247. quality_harness-0.5.0/toolbox/harness_toolbox/http.py +42 -0
  248. quality_harness-0.5.0/toolbox/harness_toolbox/kube/__init__.py +13 -0
  249. quality_harness-0.5.0/toolbox/harness_toolbox/kube/client.py +803 -0
  250. quality_harness-0.5.0/toolbox/harness_toolbox/kube/environment_resources.py +133 -0
  251. quality_harness-0.5.0/toolbox/harness_toolbox/kube/model.py +72 -0
  252. quality_harness-0.5.0/toolbox/harness_toolbox/kube/resource_list.py +71 -0
  253. quality_harness-0.5.0/toolbox/harness_toolbox/kube/resources.py +142 -0
  254. quality_harness-0.5.0/toolbox/harness_toolbox/kube/selector.py +22 -0
  255. quality_harness-0.5.0/toolbox/harness_toolbox/kube/worker.py +223 -0
  256. quality_harness-0.5.0/toolbox/harness_toolbox/kube/workload.py +122 -0
  257. quality_harness-0.5.0/toolbox/harness_toolbox/kube_portforward.py +136 -0
  258. quality_harness-0.5.0/toolbox/harness_toolbox/line_buffer.py +52 -0
  259. quality_harness-0.5.0/toolbox/harness_toolbox/llm/__init__.py +9 -0
  260. quality_harness-0.5.0/toolbox/harness_toolbox/llm/client.py +157 -0
  261. quality_harness-0.5.0/toolbox/harness_toolbox/mysql.py +432 -0
  262. quality_harness-0.5.0/toolbox/harness_toolbox/opensearch.py +334 -0
  263. quality_harness-0.5.0/toolbox/harness_toolbox/pod_log.py +218 -0
  264. quality_harness-0.5.0/toolbox/harness_toolbox/process.py +116 -0
  265. quality_harness-0.5.0/toolbox/harness_toolbox/prometheus.py +259 -0
  266. quality_harness-0.5.0/toolbox/harness_toolbox/prometheus_discovery.py +64 -0
  267. quality_harness-0.5.0/toolbox/harness_toolbox/prometheus_query.py +226 -0
  268. quality_harness-0.5.0/toolbox/harness_toolbox/socks.py +133 -0
  269. quality_harness-0.5.0/toolbox/harness_toolbox/sse.py +84 -0
  270. quality_harness-0.5.0/toolbox/harness_toolbox/sse_parser.py +101 -0
  271. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_address.py +265 -0
  272. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_data_loader.py +96 -0
  273. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_diagnostics.py +279 -0
  274. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_environment.py +79 -0
  275. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_environment_provider.py +52 -0
  276. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_host.py +33 -0
  277. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_http_sse.py +82 -0
  278. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_kube.py +361 -0
  279. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_kube_manifests.py +196 -0
  280. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_kube_portforward.py +157 -0
  281. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_kube_resources.py +153 -0
  282. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_llm_client.py +175 -0
  283. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_mysql.py +81 -0
  284. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_mysql_queries.py +282 -0
  285. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_opensearch.py +111 -0
  286. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_pod_log.py +99 -0
  287. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_process.py +25 -0
  288. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_prometheus.py +221 -0
  289. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_prometheus_discovery.py +98 -0
  290. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_prometheus_query.py +199 -0
  291. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_resource_list.py +222 -0
  292. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_socks.py +98 -0
  293. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_transport.py +50 -0
  294. quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_workload.py +262 -0
  295. quality_harness-0.5.0/toolbox/harness_toolbox/transport.py +184 -0
  296. quality_harness-0.5.0/toolbox/pyproject.toml +26 -0
  297. quality_harness-0.5.0/toolbox/ruff.toml +4 -0
  298. quality_harness-0.5.0/trace_harness/AGENTS.md +130 -0
  299. quality_harness-0.5.0/trace_harness/__init__.py +68 -0
  300. quality_harness-0.5.0/trace_harness/analyze/__init__.py +14 -0
  301. quality_harness-0.5.0/trace_harness/analyze/budget.py +190 -0
  302. quality_harness-0.5.0/trace_harness/analyze/call_breakdown.py +164 -0
  303. quality_harness-0.5.0/trace_harness/analyze/context.py +49 -0
  304. quality_harness-0.5.0/trace_harness/analyze/diagnose/__init__.py +177 -0
  305. quality_harness-0.5.0/trace_harness/analyze/diagnose/detectors.py +100 -0
  306. quality_harness-0.5.0/trace_harness/analyze/diagnose/http.py +132 -0
  307. quality_harness-0.5.0/trace_harness/analyze/diagnose/outliers.py +91 -0
  308. quality_harness-0.5.0/trace_harness/analyze/diagnose/patterns.py +65 -0
  309. quality_harness-0.5.0/trace_harness/analyze/diagnose/probes.py +41 -0
  310. quality_harness-0.5.0/trace_harness/analyze/diagnose/registry.py +43 -0
  311. quality_harness-0.5.0/trace_harness/analyze/diagnose/series.py +55 -0
  312. quality_harness-0.5.0/trace_harness/analyze/measure.py +200 -0
  313. quality_harness-0.5.0/trace_harness/analyze/verdict.py +130 -0
  314. quality_harness-0.5.0/trace_harness/batch.py +423 -0
  315. quality_harness-0.5.0/trace_harness/cli.py +262 -0
  316. quality_harness-0.5.0/trace_harness/corpus/__init__.py +23 -0
  317. quality_harness-0.5.0/trace_harness/corpus/experiment.py +82 -0
  318. quality_harness-0.5.0/trace_harness/corpus/operators.py +214 -0
  319. quality_harness-0.5.0/trace_harness/corpus/report.py +108 -0
  320. quality_harness-0.5.0/trace_harness/corpus/store.py +97 -0
  321. quality_harness-0.5.0/trace_harness/corpus/tables.py +80 -0
  322. quality_harness-0.5.0/trace_harness/dataset.py +42 -0
  323. quality_harness-0.5.0/trace_harness/detectors.py +121 -0
  324. quality_harness-0.5.0/trace_harness/docs/batch.md +158 -0
  325. quality_harness-0.5.0/trace_harness/docs/kernel.md +101 -0
  326. quality_harness-0.5.0/trace_harness/docs/load.md +84 -0
  327. quality_harness-0.5.0/trace_harness/docs/single.md +73 -0
  328. quality_harness-0.5.0/trace_harness/harness.py +212 -0
  329. quality_harness-0.5.0/trace_harness/ingest/__init__.py +13 -0
  330. quality_harness-0.5.0/trace_harness/ingest/assemble.py +166 -0
  331. quality_harness-0.5.0/trace_harness/ingest/load.py +33 -0
  332. quality_harness-0.5.0/trace_harness/ingest/sources/__init__.py +29 -0
  333. quality_harness-0.5.0/trace_harness/ingest/sources/base.py +54 -0
  334. quality_harness-0.5.0/trace_harness/ingest/sources/jaeger_file.py +292 -0
  335. quality_harness-0.5.0/trace_harness/ingest/sources/opensearch.py +260 -0
  336. quality_harness-0.5.0/trace_harness/kinds/__init__.py +6 -0
  337. quality_harness-0.5.0/trace_harness/kinds/base.py +58 -0
  338. quality_harness-0.5.0/trace_harness/kinds/genai.py +264 -0
  339. quality_harness-0.5.0/trace_harness/kinds/http.py +172 -0
  340. quality_harness-0.5.0/trace_harness/loading/__init__.py +1 -0
  341. quality_harness-0.5.0/trace_harness/loading/analysis.py +172 -0
  342. quality_harness-0.5.0/trace_harness/loading/facts.py +30 -0
  343. quality_harness-0.5.0/trace_harness/loading/http.py +29 -0
  344. quality_harness-0.5.0/trace_harness/loading/loader.py +219 -0
  345. quality_harness-0.5.0/trace_harness/loading/model.py +35 -0
  346. quality_harness-0.5.0/trace_harness/loading/store.py +77 -0
  347. quality_harness-0.5.0/trace_harness/model/__init__.py +34 -0
  348. quality_harness-0.5.0/trace_harness/model/agent.py +339 -0
  349. quality_harness-0.5.0/trace_harness/model/analysis.py +142 -0
  350. quality_harness-0.5.0/trace_harness/model/context.py +63 -0
  351. quality_harness-0.5.0/trace_harness/model/intervals.py +26 -0
  352. quality_harness-0.5.0/trace_harness/model/ir.py +125 -0
  353. quality_harness-0.5.0/trace_harness/model/measurement.py +54 -0
  354. quality_harness-0.5.0/trace_harness/model/node.py +88 -0
  355. quality_harness-0.5.0/trace_harness/model/node_tree.py +72 -0
  356. quality_harness-0.5.0/trace_harness/model/span.py +80 -0
  357. quality_harness-0.5.0/trace_harness/model/spec.py +89 -0
  358. quality_harness-0.5.0/trace_harness/ruff.toml +12 -0
  359. quality_harness-0.5.0/trace_harness/runtime.py +271 -0
  360. quality_harness-0.5.0/trace_harness/tests/__init__.py +0 -0
  361. quality_harness-0.5.0/trace_harness/tests/test_agent_run.py +471 -0
  362. quality_harness-0.5.0/trace_harness/tests/test_batch_dependencies.py +121 -0
  363. quality_harness-0.5.0/trace_harness/tests/test_budget.py +103 -0
  364. quality_harness-0.5.0/trace_harness/tests/test_cohort.py +267 -0
  365. quality_harness-0.5.0/trace_harness/tests/test_conformance.py +38 -0
  366. quality_harness-0.5.0/trace_harness/tests/test_detector_registry.py +51 -0
  367. quality_harness-0.5.0/trace_harness/tests/test_explore.py +117 -0
  368. quality_harness-0.5.0/trace_harness/tests/test_facet_engine.py +314 -0
  369. quality_harness-0.5.0/trace_harness/tests/test_http_detectors.py +56 -0
  370. quality_harness-0.5.0/trace_harness/tests/test_http_groups.py +68 -0
  371. quality_harness-0.5.0/trace_harness/tests/test_latest_selection.py +95 -0
  372. quality_harness-0.5.0/trace_harness/tests/test_loading.py +445 -0
  373. quality_harness-0.5.0/trace_harness/tests/test_loading_conformance.py +81 -0
  374. quality_harness-0.5.0/trace_harness/tests/test_measurement.py +252 -0
  375. quality_harness-0.5.0/trace_harness/tests/test_node_dependencies.py +125 -0
  376. quality_harness-0.5.0/trace_harness/tests/test_node_value.py +86 -0
  377. quality_harness-0.5.0/trace_harness/tests/test_scoped_harness.py +109 -0
  378. quality_harness-0.5.0/trace_harness/tests/test_trace_harness.py +696 -0
  379. quality_harness-0.5.0/trace_harness/tests/test_transform.py +170 -0
  380. quality_harness-0.5.0/trace_harness/tests/test_views.py +220 -0
  381. quality_harness-0.5.0/trace_harness/tests/view_fixture.py +40 -0
  382. quality_harness-0.5.0/trace_harness/transform.py +103 -0
  383. quality_harness-0.5.0/trace_harness/view/__init__.py +6 -0
  384. quality_harness-0.5.0/trace_harness/view/agent_run.py +242 -0
  385. quality_harness-0.5.0/trace_harness/view/build.py +156 -0
  386. quality_harness-0.5.0/trace_harness/view/callstack.py +57 -0
  387. quality_harness-0.5.0/trace_harness/view/display.py +81 -0
  388. quality_harness-0.5.0/trace_harness/view/engine.py +346 -0
  389. quality_harness-0.5.0/trace_harness/view/explore.py +128 -0
  390. quality_harness-0.5.0/trace_harness/view/facet.py +151 -0
  391. quality_harness-0.5.0/trace_harness/view/facets/__init__.py +15 -0
  392. quality_harness-0.5.0/trace_harness/view/facets/agent.py +22 -0
  393. quality_harness-0.5.0/trace_harness/view/facets/model_call.py +28 -0
  394. quality_harness-0.5.0/trace_harness/view/facets/service.py +17 -0
  395. quality_harness-0.5.0/trace_harness/view/http.py +102 -0
  396. quality_harness-0.5.0/trace_harness/view/interactive.py +422 -0
  397. quality_harness-0.5.0/trace_harness/view/measurements.py +93 -0
  398. quality_harness-0.5.0/trace_harness/view/payload.py +116 -0
  399. quality_harness-0.5.0/trace_harness/view/registry.py +34 -0
  400. quality_harness-0.5.0/trace_harness/view/render.py +79 -0
  401. quality_harness-0.5.0/trace_harness/view/series.py +46 -0
  402. quality_harness-0.5.0/trace_harness/view/state.py +102 -0
  403. quality_harness-0.5.0/trace_harness/view/text.py +47 -0
  404. quality_harness-0.5.0/trace_harness/view/tool_name.py +73 -0
  405. quality_harness-0.5.0/trajectory_harness/AGENTS.md +96 -0
  406. quality_harness-0.5.0/trajectory_harness/README.md +270 -0
  407. quality_harness-0.5.0/trajectory_harness/__init__.py +130 -0
  408. quality_harness-0.5.0/trajectory_harness/_measurements.py +32 -0
  409. quality_harness-0.5.0/trajectory_harness/_steps.py +14 -0
  410. quality_harness-0.5.0/trajectory_harness/_tool_calls.py +137 -0
  411. quality_harness-0.5.0/trajectory_harness/build.py +167 -0
  412. quality_harness-0.5.0/trajectory_harness/dataset.py +127 -0
  413. quality_harness-0.5.0/trajectory_harness/detect.py +192 -0
  414. quality_harness-0.5.0/trajectory_harness/detectors/__init__.py +26 -0
  415. quality_harness-0.5.0/trajectory_harness/detectors/cache_retention_bloat.py +96 -0
  416. quality_harness-0.5.0/trajectory_harness/detectors/context_bloat.py +80 -0
  417. quality_harness-0.5.0/trajectory_harness/detectors/oversized_tool_observation.py +83 -0
  418. quality_harness-0.5.0/trajectory_harness/detectors/post_compact_refetch.py +110 -0
  419. quality_harness-0.5.0/trajectory_harness/detectors/repeated_tool_call.py +73 -0
  420. quality_harness-0.5.0/trajectory_harness/detectors/retry_loop.py +100 -0
  421. quality_harness-0.5.0/trajectory_harness/detectors/short_decision_churn.py +87 -0
  422. quality_harness-0.5.0/trajectory_harness/detectors/unchanged_tool_retry.py +82 -0
  423. quality_harness-0.5.0/trajectory_harness/failures.py +74 -0
  424. quality_harness-0.5.0/trajectory_harness/loaders/__init__.py +4 -0
  425. quality_harness-0.5.0/trajectory_harness/loaders/atif_json.py +37 -0
  426. quality_harness-0.5.0/trajectory_harness/loaders/base.py +17 -0
  427. quality_harness-0.5.0/trajectory_harness/loaders/otel_json.py +580 -0
  428. quality_harness-0.5.0/trajectory_harness/measure.py +194 -0
  429. quality_harness-0.5.0/trajectory_harness/measurers/__init__.py +14 -0
  430. quality_harness-0.5.0/trajectory_harness/measurers/_tokens.py +74 -0
  431. quality_harness-0.5.0/trajectory_harness/measurers/context_usage.py +207 -0
  432. quality_harness-0.5.0/trajectory_harness/measurers/model_usage.py +248 -0
  433. quality_harness-0.5.0/trajectory_harness/measurers/retry_usage.py +122 -0
  434. quality_harness-0.5.0/trajectory_harness/measurers/tool_usage.py +172 -0
  435. quality_harness-0.5.0/trajectory_harness/metrics.py +667 -0
  436. quality_harness-0.5.0/trajectory_harness/model.py +342 -0
  437. quality_harness-0.5.0/trajectory_harness/pipeline.py +82 -0
  438. quality_harness-0.5.0/trajectory_harness/report.py +744 -0
  439. quality_harness-0.5.0/trajectory_harness/report_comparison.py +187 -0
  440. quality_harness-0.5.0/trajectory_harness/report_detection.py +101 -0
  441. quality_harness-0.5.0/trajectory_harness/report_provenance.py +89 -0
  442. quality_harness-0.5.0/trajectory_harness/runio.py +210 -0
  443. quality_harness-0.5.0/trajectory_harness/runner.py +194 -0
  444. quality_harness-0.5.0/trajectory_harness/source.py +85 -0
  445. quality_harness-0.5.0/trajectory_harness/tests/__init__.py +0 -0
  446. quality_harness-0.5.0/trajectory_harness/tests/test_atif_json.py +142 -0
  447. quality_harness-0.5.0/trajectory_harness/tests/test_efficiency.py +329 -0
  448. quality_harness-0.5.0/trajectory_harness/tests/test_failures.py +36 -0
  449. quality_harness-0.5.0/trajectory_harness/tests/test_metrics_report.py +120 -0
  450. quality_harness-0.5.0/trajectory_harness/tests/test_model_usage.py +215 -0
  451. quality_harness-0.5.0/trajectory_harness/tests/test_otel_json.py +240 -0
  452. quality_harness-0.5.0/trajectory_harness/tests/test_pipeline.py +373 -0
  453. quality_harness-0.5.0/trajectory_harness/tests/test_source.py +68 -0
  454. quality_harness-0.5.0/trajectory_harness/tests/test_structural_waste.py +281 -0
  455. quality_harness-0.5.0/trajectory_harness/tests/test_verify.py +311 -0
  456. quality_harness-0.5.0/trajectory_harness/verdict.py +125 -0
  457. quality_harness-0.5.0/trajectory_harness/verifiers/__init__.py +11 -0
  458. quality_harness-0.5.0/trajectory_harness/verifiers/execution_success.py +48 -0
  459. quality_harness-0.5.0/trajectory_harness/verifiers/measurement_threshold.py +133 -0
  460. quality_harness-0.5.0/trajectory_harness/verifiers/tool_success.py +59 -0
  461. quality_harness-0.5.0/trajectory_harness/verify.py +183 -0
  462. quality_harness-0.5.0/uv.lock +1373 -0
@@ -0,0 +1,45 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ *.egg
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ htmlcov/
13
+ .coverage
14
+ *.whl
15
+ *.tar.gz
16
+
17
+ # Go
18
+ /sdks/go/bin/
19
+
20
+ # TypeScript
21
+ node_modules/
22
+
23
+ # IDE
24
+ .idea/
25
+ .vscode/
26
+ *.swp
27
+ *.swo
28
+
29
+ # OS
30
+ .DS_Store
31
+ Thumbs.db
32
+
33
+ # uv
34
+ sdks/python/.venv/
35
+
36
+ # DevOps plugin
37
+ .devops/
38
+
39
+ # Runtime artifacts (agent tier)
40
+ runtime/
41
+ runs/
42
+
43
+ # devloop
44
+ .devloop/
45
+ .worktrees/
@@ -0,0 +1,45 @@
1
+ # Python SDKs
2
+
3
+ ## 项目定位与边界
4
+
5
+ 一个 uv workspace 管理 `quality-harness`、`harness-common` 与 `harness-toolbox` 三个独立分发包。
6
+ 领域 SDK 依赖 common/toolbox;toolbox 依赖 common,common 不反向依赖 toolbox。每个包只打包自己的命名空间。
7
+
8
+ ## 代码地图与核心模块
9
+
10
+ ```text
11
+ python/
12
+ ├── e2e_harness/ # 服务 API 契约测试
13
+ ├── eval_harness/ # Agent 效果评测与对照实验
14
+ ├── perf_harness/ # 性能、容量与资源画像
15
+ ├── trace_harness/ # 调用链分析
16
+ ├── trajectory_harness/ # Agent 决策与行动序列评估
17
+ ├── common/ # 独立 harness-common:system/access/execution 契约与 report_kit
18
+ ├── toolbox/ # 独立 harness-toolbox:协议客户端、LLM、Transport 与平台适配
19
+ ├── pyproject.toml # 包、依赖、CLI 与测试发现
20
+ └── Makefile # 测试、lint、格式化、构建与版本入口
21
+ ```
22
+
23
+ ## 关键约定
24
+
25
+ - 五个领域 SDK 互不 import;公共能力集中在中立共享包。`harness_common` 统一执行事实与输出契约,各 SDK 自行拥有 runner、scheduler 和协议原语。
26
+ - 环境共享层只管理 EnvironmentFixture 与外层执行,Case / Trial 准备清理由各 Harness 拥有;见 [环境生命周期](../../docs/environment.md)。
27
+ - 测试贴近所属包,放在各包 `tests/`;默认 pytest 覆盖完整工程,发行包排除测试。
28
+ - 领域可选依赖通过 extras 声明,避免给其它 SDK 增加安装负担。
29
+ - Python 包版本使用本目录 `make bump`,同步 `pyproject.toml` 与 `uv.lock`;仓库整体版本按根目录约定独立更新。
30
+
31
+ ## 开发与测试
32
+
33
+ 从本目录运行:
34
+
35
+ ```bash
36
+ uv sync --locked
37
+ make fix
38
+ make lint
39
+ uv run pytest -q
40
+ make build
41
+ ```
42
+
43
+ 局部验证使用 `make test TEST_FILES=<package>/tests`;toolbox 路径为 `toolbox/harness_toolbox/tests`。
44
+ 局部格式化使用 `make fix CHECK_PATHS=toolbox/harness_toolbox`,完整门禁仍运行 `make lint`。`make fix` 会修改源码,完成后再运行 lint 和测试。
45
+ 构建产物写入仓库根目录 `dist/`。领域 smoke 命令与 fixture 使用方式归对应 Harness。
@@ -0,0 +1,31 @@
1
+ CHECK_PATHS ?= $(if $(filter %.py,$(LINT_FILES)),$(filter %.py,$(LINT_FILES)),common/harness_common/ toolbox/harness_toolbox/ e2e_harness/ eval_harness/ perf_harness/ trace_harness/ trajectory_harness/)
2
+
3
+ FIX_PATHS ?= $(CHECK_PATHS)
4
+
5
+ .PHONY: test lint fix build clean bump
6
+
7
+ test:
8
+ uv run pytest -v $(TEST_FILES)
9
+
10
+ lint:
11
+ uv run ruff check $(CHECK_PATHS)
12
+
13
+ fix:
14
+ uv run ruff check --fix $(FIX_PATHS)
15
+ uv run ruff format $(FIX_PATHS)
16
+
17
+ build:
18
+ uv build --all-packages --out-dir ../../dist
19
+
20
+ clean:
21
+ rm -rf dist build *.egg-info
22
+
23
+ bump:
24
+ @current=$$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/'); \
25
+ major=$$(echo $$current | cut -d. -f1); \
26
+ minor=$$(echo $$current | cut -d. -f2); \
27
+ patch=$$(echo $$current | cut -d. -f3); \
28
+ next="$$major.$$minor.$$((patch + 1))"; \
29
+ sed -i '' "s/^version = \"$$current\"/version = \"$$next\"/" pyproject.toml; \
30
+ uv lock; \
31
+ echo "bumped $$current → $$next"
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.5
2
+ Name: quality-harness
3
+ Version: 0.5.0
4
+ Summary: Quality testing and evidence analysis SDKs for software and agents
5
+ License: Apache-2.0
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: atif<1.8,>=1.7
8
+ Requires-Dist: harness-common<0.7,>=0.6.0
9
+ Requires-Dist: harness-toolbox[http,llm,prometheus]<0.7,>=0.6.0
10
+ Requires-Dist: httpx<1.0,>=0.27
11
+ Requires-Dist: pydantic<3.0,>=2.0
12
+ Requires-Dist: pytest>=8.0
13
+ Requires-Dist: pyyaml<7.0,>=6.0
14
+ Requires-Dist: spec-case[model]>=0.2.1
15
+ Provides-Extra: dev
16
+ Requires-Dist: kubernetes-asyncio<37,>=36.1; extra == 'dev'
17
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
18
+ Provides-Extra: kube
19
+ Requires-Dist: harness-toolbox[kube]<0.7,>=0.6.0; extra == 'kube'
20
+ Provides-Extra: trace-corpus
21
+ Requires-Dist: pyarrow<22,>=14; extra == 'trace-corpus'
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ cd "$(dirname "$0")"
5
+ exec uv build --out-dir ../../dist
@@ -0,0 +1,20 @@
1
+ # Python 共享内核
2
+
3
+ `harness-common` 提供系统描述、访问契约和执行证据,不依赖 toolbox、领域 SDK 或第三方运行库。
4
+ 具体 HTTP、LLM、数据库与平台访问属于 toolbox;领域 Harness 拥有调度和判据。
5
+
6
+ ```text
7
+ harness_common/
8
+ ├── system/ # Repository/Component/Product、Service/Environment、资源、部署契约
9
+ ├── access/ # Client/DataSource、EnvironmentContext 与环境 Fixture 生命周期
10
+ ├── execution/ # Experiment/Execution/Operation、Outcome/Artifact、Reducer/Verdict 与 run 布局
11
+ ├── report_kit/ # 中立报告结构和 HTML 渲染,独立于执行生命周期
12
+ ├── identity.py # 身份协议
13
+ ├── __init__.py # 稳定包根导出
14
+ └── tests/ # 跨模块契约与 conformance 测试
15
+ ```
16
+
17
+ `system` 描述目标,`access` 获取事实,`execution` 组织与解释证据。它们是包内职责分组,
18
+ 不是独立分发包;跨组引用必须指向具体模块,避免依赖包根聚合导出造成循环。
19
+ 包根保持既有符号和类型身份;深层导入迁移见 [0.6 升级说明](README.md)。
20
+ 从 `sdks/python` 运行 `make fix`、`make lint`、`make test` 和 `make build`。
@@ -0,0 +1,34 @@
1
+ # Harness Common for Python
2
+
3
+ Standard-library contracts shared by the quality SDKs. Import public models and lifecycle
4
+ contracts from `harness_common`; concrete protocol clients live in `harness_toolbox`.
5
+
6
+ ## Upgrade to 0.6
7
+
8
+ Package-root exports such as `from harness_common import Service, ClientManager, Execution`
9
+ retain their identity and behavior. Internal modules are now grouped by responsibility:
10
+
11
+ | Previous module under `harness_common` | New module |
12
+ | --- | --- |
13
+ | `repository`, `component`, `product`, `forge`, `service`, `environment`, `host`, `workload`, `resource`, `deployment` | `system.<module>` |
14
+ | `client`, `datasource`, `context`, `fixture` | `access.<module>` |
15
+ | `artifact`, `experiment`, `operation`, `outcome`, `overlay`, `reducer`, `run`, `verdict` | `execution.<module>` |
16
+ | `execution` | `execution.execution` (`from harness_common.execution import Execution` also remains valid) |
17
+ | `llm` | `harness_toolbox.llm` |
18
+
19
+ `identity` and `report_kit` keep their existing paths. Replace old deep imports; no flat
20
+ compatibility modules are shipped. For example:
21
+
22
+ ```python
23
+ from harness_common.execution.verdict import RunVerdict
24
+ from harness_common.execution.run import run_dir_for
25
+ ```
26
+
27
+ Install `harness-toolbox[llm]>=0.6,<0.7` for `LLMClient`, `LLMConfig` and `ChatResult`.
28
+ The old `harness-common[llm]` extra is removed. The LLM re-exports from `eval_harness`
29
+ and the `eval_harness.llm` compatibility package are removed; import directly from
30
+ `harness_toolbox.llm`. Client request, timeout and retry behavior is unchanged.
31
+
32
+ Upgrade `harness-common` and `harness-toolbox` together to 0.6; `quality-harness>=0.5.0`
33
+ selects this compatible pair and the LLM extra. Publish common before toolbox, then
34
+ quality-harness, so every dependency is available when a downstream package is installed.
@@ -0,0 +1,75 @@
1
+ """common — harness-neutral shared code and runtime identities.
2
+
3
+ The harnesses (e2e / eval / perf / trace / trajectory) depend on this package but never on each
4
+ other; only genuinely neutral code lives here (no harness domain concept leaks in).
5
+
6
+ The canonical case model — `Case` / `CaseSet` / `FacetSchema` and the `Face` / `FACES`
7
+ judgment-face enum — lives upstream in `spec_case.model` / `spec_case.facets`
8
+ (the spec-case package, `[model]` extra): the case format is the asset layer's contract,
9
+ and this repo is one of its runners. Import those names from `spec_case` directly.
10
+
11
+ `Forge` and `Repository` identify where source lives; `Product` names a business product;
12
+ `Component` identifies a buildable unit within a Repository. `Environment` identifies where
13
+ execution happens. `Service` identifies a Component's runtime presence in one Environment.
14
+ `Workload` locates a declared runtime carrier; a Service can reference multiple workloads.
15
+ `Operation` identifies a capability exposed by a Service; `HttpOperation` adds its HTTP
16
+ transport contract. An `Execution` groups domain-defined work such as an e2e CaseRun or
17
+ a perf Trial; each `OperationRun` owns one raw `Outcome`. Domain engines own scheduling
18
+ details. A domain `Reducer` projects the recorded Outcomes into one or more
19
+ `Artifact` objects without calling the tested Service again. `Deployment` records an
20
+ attempt applied through a `Deployer`.
21
+ `Experiment` names reproducible verification intent; `ExperimentRun` records one execution
22
+ and references its `Artifact` outputs.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from harness_common.execution.artifact import Artifact as Artifact
28
+ from harness_common.system.component import Component as Component
29
+ from harness_common.system.deployment import Deployer as Deployer
30
+ from harness_common.system.deployment import Deployment as Deployment
31
+ from harness_common.system.environment import Environment as Environment
32
+ from harness_common.system.environment import HostEnvironment as HostEnvironment
33
+ from harness_common.system.host import Host as Host
34
+ from harness_common.system.environment import EnvironmentFacts as EnvironmentFacts
35
+ from harness_common.system.environment import EnvironmentSnapshot as EnvironmentSnapshot
36
+ from harness_common.execution.execution import Execution as Execution
37
+ from harness_common.execution.experiment import Experiment as Experiment
38
+ from harness_common.execution.experiment import ExperimentRun as ExperimentRun
39
+ from harness_common.system.forge import Forge as Forge
40
+ from harness_common.identity import Identity as Identity
41
+ from harness_common.execution.operation import HttpOperation as HttpOperation
42
+ from harness_common.execution.operation import Operation as Operation
43
+ from harness_common.execution.operation import OperationRun as OperationRun
44
+ from harness_common.execution.outcome import Outcome as Outcome
45
+ from harness_common.system.product import Product as Product
46
+ from harness_common.system.repository import Repository as Repository
47
+ from harness_common.execution.reducer import Reducer as Reducer
48
+ from harness_common.system.service import Service as Service
49
+ from harness_common.system.workload import KubernetesWorkload as KubernetesWorkload
50
+ from harness_common.system.workload import Workload as Workload
51
+ from harness_common.system.workload import WorkloadInstance as WorkloadInstance
52
+ from harness_common.system.workload import (
53
+ KubernetesWorkloadInstance as KubernetesWorkloadInstance,
54
+ )
55
+
56
+ from harness_common.access.client import Client as Client
57
+ from harness_common.access.client import ClientManager as ClientManager
58
+ from harness_common.access.client import ClientProvider as ClientProvider
59
+ from harness_common.access.datasource import DataSource as DataSource
60
+ from harness_common.access.datasource import ServiceDataSource as ServiceDataSource
61
+ from harness_common.access.client import client_key as client_key
62
+ from harness_common.access.fixture import EnvironmentFixture as EnvironmentFixture
63
+ from harness_common.access.context import EnvironmentContext as EnvironmentContext
64
+ from harness_common.access.fixture import FixtureContext as FixtureContext
65
+ from harness_common.access.fixture import EnvironmentBudgets as EnvironmentBudgets
66
+ from harness_common.access.fixture import EnvironmentRun as EnvironmentRun
67
+ from harness_common.access.fixture import run_environment as run_environment
68
+
69
+ from harness_common.system.resource import Resource as Resource
70
+ from harness_common.system.resource import ServiceResource as ServiceResource
71
+ from harness_common.system.resource import ResourceInstanceRef as ResourceInstanceRef
72
+ from harness_common.system.resource import ResourceInstance as ResourceInstance
73
+ from harness_common.system.resource import ResourceMeasurement as ResourceMeasurement
74
+ from harness_common.system.resource import ResourceCoverage as ResourceCoverage
75
+ from harness_common.system.resource import ResourceSnapshot as ResourceSnapshot
@@ -0,0 +1 @@
1
+ """Access contracts shared across harnesses."""
@@ -0,0 +1,127 @@
1
+ """Execution-scoped, asynchronous client ownership.
2
+
3
+ ClientProvider keys describe configuration, not object identity. Consumers receive a
4
+ _ClientBorrower; only the root execution owns and disposes the ClientManager.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import hashlib
11
+ import json
12
+ from typing import Protocol, TypeVar, cast
13
+
14
+
15
+ class Client(Protocol):
16
+ async def initialize(self) -> None: ...
17
+ async def dispose(self) -> None: ...
18
+
19
+
20
+ C = TypeVar("C", bound=Client, covariant=True)
21
+
22
+
23
+ class ClientProvider(Protocol[C]):
24
+ """Keyed client construction, independent of data or environment semantics.
25
+
26
+ @spec Both environment and data clients share lifecycle ownership.
27
+ @rule client_key covers implementation, target, credentials and capacity policy.
28
+ Construction is pure; initialize owns I/O, and the root owns disposal.
29
+ """
30
+
31
+ @property
32
+ def client_key(self) -> str: ...
33
+ def create_client(self, clients: _ClientBorrower) -> C: ...
34
+
35
+
36
+ class _ClientBorrower(Protocol):
37
+ async def get(self, provider: ClientProvider[C]) -> C: ...
38
+
39
+
40
+ def client_key(protocol: str, configuration: object) -> str:
41
+ """Hash JSON-compatible configuration, including credentials, without exposing it."""
42
+ canonical = json.dumps(
43
+ configuration, sort_keys=True, separators=(",", ":"), allow_nan=False
44
+ )
45
+ return protocol + ":" + hashlib.sha256(canonical.encode()).hexdigest()
46
+
47
+
48
+ class ClientManager:
49
+ """Share initialized clients within one event loop and close them at root exit.
50
+
51
+ Initialize dependencies through the supplied borrower before publishing a
52
+ client as ready. Reverse readiness order then closes consumers first.
53
+ Join all command work before leaving the root context: borrowed clients are
54
+ invalid after disposal. Managers cannot be reopened or shared across loops.
55
+ """
56
+
57
+ def __init__(self) -> None:
58
+ self._pending: dict[str, asyncio.Task[Client]] = {}
59
+ self._ready: list[Client] = []
60
+ self._disposal: asyncio.Task[None] | None = None
61
+ self._cleanup_errors: list[BaseException] = []
62
+ self._unclean: set[str] = set()
63
+
64
+ async def get(self, provider: ClientProvider[C]) -> C:
65
+ if self._disposal is not None:
66
+ raise RuntimeError("client manager is disposed")
67
+ key = provider.client_key
68
+ task = self._pending.get(key)
69
+ if task is None:
70
+ # Store before the task can run; factories can themselves request dependencies.
71
+ task = asyncio.create_task(self._initialize(provider))
72
+ self._pending[key] = task
73
+ task.add_done_callback(lambda done: self._settled(key, done))
74
+ # A cancelled consumer must not cancel initialization needed by other consumers.
75
+ return cast(C, await asyncio.shield(task))
76
+
77
+ def _settled(self, key: str, task: asyncio.Task[Client]) -> None:
78
+ failed = task.cancelled() or task.exception() is not None
79
+ if failed and key not in self._unclean and self._pending.get(key) is task:
80
+ del self._pending[key]
81
+
82
+ async def _initialize(self, provider: ClientProvider[Client]) -> Client:
83
+ client = provider.create_client(self)
84
+ try:
85
+ await client.initialize()
86
+ self._ready.append(client)
87
+ return client
88
+ except BaseException as error:
89
+ try:
90
+ await client.dispose()
91
+ except BaseException as cleanup:
92
+ self._cleanup_errors.append(cleanup)
93
+ self._unclean.add(provider.client_key)
94
+ raise BaseExceptionGroup(
95
+ "client initialization and cleanup failed", [error, cleanup]
96
+ ) from None
97
+ raise
98
+
99
+ async def dispose(self) -> None:
100
+ """Idempotent, concurrent-safe cleanup; cancellation of a waiter cannot stop it."""
101
+ if self._disposal is None:
102
+ self._disposal = asyncio.create_task(self._dispose())
103
+ await asyncio.shield(self._disposal)
104
+
105
+ async def _dispose(self) -> None:
106
+ pending = list(self._pending.values())
107
+ for task in pending:
108
+ if not task.done():
109
+ task.cancel()
110
+ await asyncio.gather(*pending, return_exceptions=True)
111
+ self._pending.clear()
112
+ for client in reversed(self._ready):
113
+ try:
114
+ await client.dispose()
115
+ except BaseException as error:
116
+ self._cleanup_errors.append(error)
117
+ self._ready.clear()
118
+ if self._cleanup_errors:
119
+ raise BaseExceptionGroup("client cleanup failed", self._cleanup_errors)
120
+
121
+ async def __aenter__(self) -> ClientManager:
122
+ if self._disposal is not None:
123
+ raise RuntimeError("client manager is disposed")
124
+ return self
125
+
126
+ async def __aexit__(self, *_: object) -> None:
127
+ await self.dispose()
@@ -0,0 +1,29 @@
1
+ """Execution-scoped environment access, independent of fixture orchestration."""
2
+
3
+ import time
4
+ from dataclasses import dataclass
5
+ from typing import Generic, TypeVar
6
+
7
+ from harness_common.access.client import _ClientBorrower
8
+ from harness_common.system.environment import Environment
9
+
10
+
11
+ E = TypeVar("E", bound=Environment)
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class EnvironmentContext(Generic[E]):
16
+ """Bind an environment, borrowed clients and a monotonic deadline in seconds.
17
+
18
+ @spec Deployment, tests and diagnosis can use this context without a fixture.
19
+ @rule It neither opens connections nor owns disposal or automatic cancellation.
20
+ Callers propagate remaining_s into operations and join work before root exit.
21
+ """
22
+
23
+ environment: E
24
+ clients: _ClientBorrower
25
+ deadline: float
26
+
27
+ @property
28
+ def remaining_s(self) -> float:
29
+ return max(0.0, self.deadline - time.monotonic())
@@ -0,0 +1,25 @@
1
+ """Data access associations built on the neutral client provider contract."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Generic, Protocol
5
+
6
+ from harness_common.access.client import C, ClientProvider
7
+ from harness_common.system.service import Service
8
+
9
+
10
+ class DataSource(ClientProvider[C], Protocol[C]):
11
+ """A source of data; lifecycle and reuse follow ClientProvider.
12
+
13
+ Accessible environment providers need not be described as data sources.
14
+ """
15
+
16
+ # Safe discovery metadata, not credentials or part of client_key.
17
+ description: str | None = None
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class ServiceDataSource(Generic[C]):
22
+ """Associate business ownership without changing client identity or lifetime."""
23
+
24
+ service: Service
25
+ source: DataSource[C]
@@ -0,0 +1,198 @@
1
+ """Environment lifetime around a domain-owned execution (no Case abstraction)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import time
7
+ from collections.abc import Awaitable, Callable
8
+ from copy import deepcopy
9
+ from dataclasses import dataclass, field
10
+ from typing import Generic, Protocol, TypeVar
11
+
12
+ from harness_common.access.client import ClientManager
13
+ from harness_common.access.context import EnvironmentContext
14
+ from harness_common.system.environment import Environment, EnvironmentSnapshot
15
+
16
+ S = TypeVar("S")
17
+ S_contra = TypeVar("S_contra", contravariant=True)
18
+ R = TypeVar("R")
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class FixtureContext(EnvironmentContext):
23
+ """Fixture phase information extends the neutral environment context."""
24
+
25
+ phase: str
26
+
27
+
28
+ class EnvironmentFixture(Protocol[S_contra]):
29
+ """Project-owned shared resources; retain partial handles in preallocated state.
30
+
31
+ Cleanup must tolerate incomplete preparation. Borrowed clients remain valid
32
+ until cleanup returns; only their manager owns final disposal.
33
+ """
34
+
35
+ async def prepare(self, ctx: FixtureContext, state: S_contra) -> None: ...
36
+ async def cleanup(self, ctx: FixtureContext, state: S_contra) -> None: ...
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class EnvironmentBudgets:
41
+ prepare_s: float
42
+ run_s: float
43
+ cleanup_s: float
44
+ dispose_s: float
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class EnvironmentPhase:
49
+ name: str
50
+ duration_ms: int
51
+ error: str | None = None
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class EnvironmentObservation:
56
+ phase: str
57
+ snapshot: EnvironmentSnapshot
58
+
59
+
60
+ @dataclass
61
+ class EnvironmentRun(Generic[R]):
62
+ """Outer health and evidence; the domain result retains its own verdicts."""
63
+
64
+ result: R | None = None
65
+ phases: list[EnvironmentPhase] = field(default_factory=list)
66
+ observations: list[EnvironmentObservation] = field(default_factory=list)
67
+
68
+ @property
69
+ def healthy(self) -> bool:
70
+ return all(phase.error is None for phase in self.phases)
71
+
72
+
73
+ async def run_environment(
74
+ environment: Environment,
75
+ state: S,
76
+ fixture: EnvironmentFixture[S],
77
+ run: Callable[[EnvironmentContext, S], Awaitable[R]],
78
+ *,
79
+ budgets: EnvironmentBudgets,
80
+ observe: Callable[[S], EnvironmentSnapshot] | None = None,
81
+ required: dict[str, str] | None = None,
82
+ ) -> EnvironmentRun[R]:
83
+ """Prepare → domain (including case cleanup) → environment cleanup → dispose.
84
+
85
+ Exceptions become outer phase errors without inventing a domain result.
86
+ Cancellation propagates after bounded teardown. Async callbacks must cooperate
87
+ with cancellation and finish their own child work before returning.
88
+ Disposal has a cooperative deadline: native cleanup is joined even on overrun.
89
+ ``observe`` is a synchronous view of facts already collected by the project.
90
+ """
91
+ if any(
92
+ value <= 0
93
+ for value in (
94
+ budgets.prepare_s,
95
+ budgets.run_s,
96
+ budgets.cleanup_s,
97
+ budgets.dispose_s,
98
+ )
99
+ ):
100
+ raise ValueError("environment phases require positive budgets")
101
+ if required and observe is None:
102
+ raise ValueError("required facts need an observe callback")
103
+ clients = ClientManager()
104
+ execution: EnvironmentRun[R] = EnvironmentRun()
105
+
106
+ def capture(phase: str) -> EnvironmentSnapshot | None:
107
+ if observe is None:
108
+ return None
109
+ snapshot = deepcopy(observe(state))
110
+ execution.observations.append(EnvironmentObservation(phase, snapshot))
111
+ return snapshot
112
+
113
+ async def phase(
114
+ name: str,
115
+ budget: float,
116
+ callback: Callable[[FixtureContext], Awaitable[None]],
117
+ *,
118
+ cooperative: bool = False,
119
+ ) -> bool:
120
+ started = time.monotonic()
121
+ error = None
122
+ try:
123
+ ctx = FixtureContext(
124
+ environment, clients, deadline=started + budget, phase=name
125
+ )
126
+ if cooperative:
127
+ await callback(ctx)
128
+ if ctx.remaining_s <= 0:
129
+ raise TimeoutError(f"{name} exceeded its time budget")
130
+ else:
131
+ async with asyncio.timeout(budget):
132
+ await callback(ctx)
133
+ except Exception as exc:
134
+ error = f"{type(exc).__name__}: {exc}"
135
+ finally:
136
+ execution.phases.append(
137
+ EnvironmentPhase(name, int((time.monotonic() - started) * 1000), error)
138
+ )
139
+ return error is None
140
+
141
+ async def prepare(ctx: FixtureContext) -> None:
142
+ try:
143
+ await fixture.prepare(ctx, state)
144
+ finally:
145
+ # Capture partial preparation too, before any teardown can rewrite it.
146
+ snapshot = capture("prepare")
147
+ if snapshot is not None:
148
+ if (snapshot.name, snapshot.kind) != (environment.name, environment.kind):
149
+ raise ValueError("snapshot conflicts with selected environment")
150
+ if required and (
151
+ not snapshot.target.source or not snapshot.target.observed_at
152
+ ):
153
+ raise ValueError("required target facts need source and observed_at")
154
+ for key, expected in (required or {}).items():
155
+ if snapshot.target.values.get(key) != expected:
156
+ raise ValueError(
157
+ f"environment condition {key}: observed {snapshot.target.values.get(key)!r}, required {expected!r}"
158
+ )
159
+
160
+ async def execute(ctx: EnvironmentContext) -> None:
161
+ execution.result = await run(ctx, state)
162
+
163
+ async def cleanup(ctx: FixtureContext) -> None:
164
+ try:
165
+ await fixture.cleanup(ctx, state)
166
+ finally:
167
+ capture("cleanup")
168
+
169
+ async def teardown() -> None:
170
+ try:
171
+ await phase("cleanup", budgets.cleanup_s, cleanup)
172
+ finally:
173
+ # ClientManager shields native disposal. Join it before returning;
174
+ # report overruns instead of leaking disposal into the next execution.
175
+ await phase(
176
+ "dispose",
177
+ budgets.dispose_s,
178
+ lambda ctx: clients.dispose(),
179
+ cooperative=True,
180
+ )
181
+
182
+ try:
183
+ if await phase("prepare", budgets.prepare_s, prepare):
184
+ await phase("run", budgets.run_s, execute)
185
+ finally:
186
+ # Teardown owns a separate task so cancellation of the run cannot skip
187
+ # cleanup or invalidate borrowed clients while cleanup still uses them.
188
+ task = asyncio.create_task(teardown())
189
+ interrupted = False
190
+ while not task.done():
191
+ try:
192
+ await asyncio.shield(task)
193
+ except asyncio.CancelledError:
194
+ interrupted = True
195
+ task.result()
196
+ if interrupted:
197
+ raise asyncio.CancelledError
198
+ return execution
@@ -0,0 +1,3 @@
1
+ """Execution facts and evidence interpretation; scheduling remains domain-owned."""
2
+
3
+ from harness_common.execution.execution import Execution as Execution