testerkit 0.4.0__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 (521) hide show
  1. testerkit/__init__.py +68 -0
  2. testerkit/_docs/_assets/operator-ui/channels/table.png +0 -0
  3. testerkit/_docs/_assets/operator-ui/dashboard/runs.png +0 -0
  4. testerkit/_docs/_assets/operator-ui/dashboard/stations.png +0 -0
  5. testerkit/_docs/_assets/operator-ui/events/filters.png +0 -0
  6. testerkit/_docs/_assets/operator-ui/events/table.png +0 -0
  7. testerkit/_docs/_assets/operator-ui/explore/chart.png +0 -0
  8. testerkit/_docs/_assets/operator-ui/explore/filters.png +0 -0
  9. testerkit/_docs/_assets/operator-ui/explore/plot-controls.png +0 -0
  10. testerkit/_docs/_assets/operator-ui/files/table.png +0 -0
  11. testerkit/_docs/_assets/operator-ui/launch/form.png +0 -0
  12. testerkit/_docs/_assets/operator-ui/metrics/filters.png +0 -0
  13. testerkit/_docs/_assets/operator-ui/metrics/pareto.png +0 -0
  14. testerkit/_docs/_assets/operator-ui/metrics/ppk.png +0 -0
  15. testerkit/_docs/_assets/operator-ui/metrics/yield.png +0 -0
  16. testerkit/_docs/_assets/operator-ui/results/detail-header.png +0 -0
  17. testerkit/_docs/_assets/operator-ui/results/detail-measurements.png +0 -0
  18. testerkit/_docs/_assets/operator-ui/results/detail-overview.png +0 -0
  19. testerkit/_docs/_assets/operator-ui/results/detail-steps.png +0 -0
  20. testerkit/_docs/_assets/operator-ui/results/stats.png +0 -0
  21. testerkit/_docs/_assets/operator-ui/results/table.png +0 -0
  22. testerkit/_docs/_assets/operator-ui/tour/designer.png +0 -0
  23. testerkit/_docs/_assets/operator-ui/tour/docs.png +0 -0
  24. testerkit/_docs/_assets/operator-ui/tour/fixtures.png +0 -0
  25. testerkit/_docs/_assets/operator-ui/tour/instruments.png +0 -0
  26. testerkit/_docs/_assets/operator-ui/tour/parts.png +0 -0
  27. testerkit/_docs/_assets/operator-ui/tour/profiles.png +0 -0
  28. testerkit/_docs/_assets/operator-ui/tour/stations.png +0 -0
  29. testerkit/_docs/_assets/operator-ui/tour/tests.png +0 -0
  30. testerkit/_docs/_assets/operator-ui/tour/uuts.png +0 -0
  31. testerkit/_docs/concepts/configuration/capabilities.md +447 -0
  32. testerkit/_docs/concepts/configuration/fixtures.md +335 -0
  33. testerkit/_docs/concepts/configuration/index.md +14 -0
  34. testerkit/_docs/concepts/configuration/parts.md +276 -0
  35. testerkit/_docs/concepts/configuration/stations.md +214 -0
  36. testerkit/_docs/concepts/data/data-stores.md +199 -0
  37. testerkit/_docs/concepts/data/event-log.md +185 -0
  38. testerkit/_docs/concepts/data/event-sourcing.md +76 -0
  39. testerkit/_docs/concepts/data/flight-streaming.md +58 -0
  40. testerkit/_docs/concepts/data/index.md +17 -0
  41. testerkit/_docs/concepts/data/sessions.md +78 -0
  42. testerkit/_docs/concepts/data/three-verbs.md +260 -0
  43. testerkit/_docs/concepts/execution/index.md +13 -0
  44. testerkit/_docs/concepts/execution/outcomes.md +199 -0
  45. testerkit/_docs/concepts/execution/step-hierarchy.md +170 -0
  46. testerkit/_docs/concepts/execution/step-manifest.md +115 -0
  47. testerkit/_docs/concepts/index.md +40 -0
  48. testerkit/_docs/concepts/overview/ai-integration.md +57 -0
  49. testerkit/_docs/concepts/overview/architecture.md +357 -0
  50. testerkit/_docs/concepts/overview/index.md +14 -0
  51. testerkit/_docs/concepts/overview/platform-vs-framework.md +147 -0
  52. testerkit/_docs/concepts/overview/pytest.md +53 -0
  53. testerkit/_docs/concepts/overview/tiers.md +46 -0
  54. testerkit/_docs/how-to/catalog/datasheet-to-test.md +169 -0
  55. testerkit/_docs/how-to/catalog/index.md +11 -0
  56. testerkit/_docs/how-to/configuration/configuring-stations.md +364 -0
  57. testerkit/_docs/how-to/configuration/custom-drivers.md +484 -0
  58. testerkit/_docs/how-to/configuration/index.md +13 -0
  59. testerkit/_docs/how-to/configuration/mock-mode.md +342 -0
  60. testerkit/_docs/how-to/data/benchmarking.md +120 -0
  61. testerkit/_docs/how-to/data/capture-an-artifact.md +84 -0
  62. testerkit/_docs/how-to/data/capture-waveform.md +110 -0
  63. testerkit/_docs/how-to/data/choosing-a-channel-verb.md +108 -0
  64. testerkit/_docs/how-to/data/compare-runs.md +120 -0
  65. testerkit/_docs/how-to/data/export-results.md +104 -0
  66. testerkit/_docs/how-to/data/find-flaky-tests.md +108 -0
  67. testerkit/_docs/how-to/data/grafana-dashboards.md +148 -0
  68. testerkit/_docs/how-to/data/index.md +23 -0
  69. testerkit/_docs/how-to/data/mcp-debug-failures.md +125 -0
  70. testerkit/_docs/how-to/data/mcp-query-runs.md +120 -0
  71. testerkit/_docs/how-to/data/querying-channels.md +117 -0
  72. testerkit/_docs/how-to/data/querying-events.md +85 -0
  73. testerkit/_docs/how-to/data/stream-live-channel.md +134 -0
  74. testerkit/_docs/how-to/execution/custom-operator-ui.md +136 -0
  75. testerkit/_docs/how-to/execution/index.md +21 -0
  76. testerkit/_docs/how-to/execution/limits.md +193 -0
  77. testerkit/_docs/how-to/execution/managing-sessions.md +100 -0
  78. testerkit/_docs/how-to/execution/multi-uut-testing.md +211 -0
  79. testerkit/_docs/how-to/execution/operator-prompts.md +151 -0
  80. testerkit/_docs/how-to/execution/profiles.md +304 -0
  81. testerkit/_docs/how-to/execution/spec-driven-testing.md +169 -0
  82. testerkit/_docs/how-to/execution/test-context.md +212 -0
  83. testerkit/_docs/how-to/execution/traceability.md +252 -0
  84. testerkit/_docs/how-to/execution/vector-expansion.md +394 -0
  85. testerkit/_docs/how-to/execution/writing-tests.md +363 -0
  86. testerkit/_docs/how-to/index.md +59 -0
  87. testerkit/_docs/how-to/overview/index.md +11 -0
  88. testerkit/_docs/how-to/overview/mcp-integration.md +168 -0
  89. testerkit/_docs/how-to/overview/operator-ui-tour.md +234 -0
  90. testerkit/_docs/integration/data/grafana.md +162 -0
  91. testerkit/_docs/integration/data/index.md +15 -0
  92. testerkit/_docs/integration/data/lakehouse-import.md +226 -0
  93. testerkit/_docs/integration/data/logging.md +131 -0
  94. testerkit/_docs/integration/data/results-api.md +51 -0
  95. testerkit/_docs/integration/index.md +20 -0
  96. testerkit/_docs/integration/runtime/harness.md +281 -0
  97. testerkit/_docs/integration/runtime/index.md +13 -0
  98. testerkit/_docs/integration/runtime/instruments.md +284 -0
  99. testerkit/_docs/integration/runtime/pytest-existing.md +315 -0
  100. testerkit/_docs/reference/catalog/cookbook.md +389 -0
  101. testerkit/_docs/reference/catalog/index.md +11 -0
  102. testerkit/_docs/reference/catalog/schema.md +375 -0
  103. testerkit/_docs/reference/cli.md +660 -0
  104. testerkit/_docs/reference/configuration.md +406 -0
  105. testerkit/_docs/reference/data/channels-schema.md +145 -0
  106. testerkit/_docs/reference/data/event-types.md +554 -0
  107. testerkit/_docs/reference/data/events-schema.md +120 -0
  108. testerkit/_docs/reference/data/files-schema.md +141 -0
  109. testerkit/_docs/reference/data/index.md +18 -0
  110. testerkit/_docs/reference/data/models.md +2229 -0
  111. testerkit/_docs/reference/data/outputs.md +82 -0
  112. testerkit/_docs/reference/data/parquet-schema.md +529 -0
  113. testerkit/_docs/reference/data/performance-limits.md +224 -0
  114. testerkit/_docs/reference/data/query-api.md +247 -0
  115. testerkit/_docs/reference/index.md +69 -0
  116. testerkit/_docs/reference/operator-ui/channels/detail.md +82 -0
  117. testerkit/_docs/reference/operator-ui/channels/list.md +70 -0
  118. testerkit/_docs/reference/operator-ui/dashboard.md +87 -0
  119. testerkit/_docs/reference/operator-ui/designer.md +167 -0
  120. testerkit/_docs/reference/operator-ui/events.md +89 -0
  121. testerkit/_docs/reference/operator-ui/files.md +74 -0
  122. testerkit/_docs/reference/operator-ui/fixtures.md +100 -0
  123. testerkit/_docs/reference/operator-ui/instruments.md +126 -0
  124. testerkit/_docs/reference/operator-ui/launch.md +79 -0
  125. testerkit/_docs/reference/operator-ui/live.md +102 -0
  126. testerkit/_docs/reference/operator-ui/measurements.md +159 -0
  127. testerkit/_docs/reference/operator-ui/metrics.md +217 -0
  128. testerkit/_docs/reference/operator-ui/parts.md +98 -0
  129. testerkit/_docs/reference/operator-ui/profiles.md +103 -0
  130. testerkit/_docs/reference/operator-ui/results/detail.md +214 -0
  131. testerkit/_docs/reference/operator-ui/results/list.md +106 -0
  132. testerkit/_docs/reference/operator-ui/stations.md +98 -0
  133. testerkit/_docs/reference/operator-ui/tests.md +173 -0
  134. testerkit/_docs/reference/operator-ui/uuts.md +67 -0
  135. testerkit/_docs/reference/overview/index.md +11 -0
  136. testerkit/_docs/reference/overview/pytest-native.md +142 -0
  137. testerkit/_docs/reference/overview/skills.md +177 -0
  138. testerkit/_docs/reference/pytest/fixtures.md +342 -0
  139. testerkit/_docs/reference/pytest/index.md +12 -0
  140. testerkit/_docs/reference/pytest/markers.md +257 -0
  141. testerkit/_docs/reference/runtime/api.md +246 -0
  142. testerkit/_docs/reference/runtime/client.md +280 -0
  143. testerkit/_docs/reference/runtime/connect.md +140 -0
  144. testerkit/_docs/reference/runtime/index.md +13 -0
  145. testerkit/_docs/tutorial/01-first-test.md +113 -0
  146. testerkit/_docs/tutorial/02-mock-instruments.md +88 -0
  147. testerkit/_docs/tutorial/03-fixtures.md +169 -0
  148. testerkit/_docs/tutorial/04-limits.md +139 -0
  149. testerkit/_docs/tutorial/05-configuration.md +194 -0
  150. testerkit/_docs/tutorial/06-specifications.md +273 -0
  151. testerkit/_docs/tutorial/07-real-instruments.md +245 -0
  152. testerkit/_docs/tutorial/08-capabilities.md +223 -0
  153. testerkit/_docs/tutorial/09-production.md +374 -0
  154. testerkit/_docs/tutorial/10-live-monitoring.md +156 -0
  155. testerkit/_docs/tutorial/11-waveforms-and-evidence.md +169 -0
  156. testerkit/_docs/tutorial/12-continuous-monitoring.md +145 -0
  157. testerkit/_docs/tutorial/13-parallel-testing.md +233 -0
  158. testerkit/_docs/tutorial/index.md +61 -0
  159. testerkit/_docs/tutorial/quickstart.md +282 -0
  160. testerkit/analysis/__init__.py +9 -0
  161. testerkit/analysis/_common.py +24 -0
  162. testerkit/analysis/measurement_facets.py +524 -0
  163. testerkit/analysis/measurements_query.py +1458 -0
  164. testerkit/analysis/metrics.py +389 -0
  165. testerkit/analysis/runs_query.py +434 -0
  166. testerkit/analysis/steps_query.py +413 -0
  167. testerkit/api/__init__.py +6 -0
  168. testerkit/api/_mime.py +71 -0
  169. testerkit/api/app.py +1171 -0
  170. testerkit/api/dialogs/__init__.py +36 -0
  171. testerkit/api/dialogs/manager.py +617 -0
  172. testerkit/api/dialogs/models.py +79 -0
  173. testerkit/api/models.py +80 -0
  174. testerkit/api/responses.py +205 -0
  175. testerkit/api/runner.py +222 -0
  176. testerkit/api/schemas.py +311 -0
  177. testerkit/benchmark/__init__.py +34 -0
  178. testerkit/benchmark/concurrency.py +248 -0
  179. testerkit/benchmark/core.py +232 -0
  180. testerkit/benchmark/runner.py +628 -0
  181. testerkit/benchmark/scenario.py +313 -0
  182. testerkit/benchmark/system.py +191 -0
  183. testerkit/benchmark/workloads.py +507 -0
  184. testerkit/catalog/__init__.py +6 -0
  185. testerkit/catalog/generic/generic_dmm.yaml +61 -0
  186. testerkit/catalog/generic/generic_eload.yaml +31 -0
  187. testerkit/catalog/generic/generic_fgen.yaml +56 -0
  188. testerkit/catalog/generic/generic_oscilloscope.yaml +45 -0
  189. testerkit/catalog/generic/generic_psu.yaml +77 -0
  190. testerkit/catalog/generic/generic_smu.yaml +73 -0
  191. testerkit/channels.py +538 -0
  192. testerkit/cli/__init__.py +32 -0
  193. testerkit/cli/_common.py +113 -0
  194. testerkit/cli/_time.py +179 -0
  195. testerkit/cli/benchmark_cmd.py +56 -0
  196. testerkit/cli/catalog_cmd.py +39 -0
  197. testerkit/cli/daemon.py +157 -0
  198. testerkit/cli/data_cmd.py +1040 -0
  199. testerkit/cli/discover_cmd.py +94 -0
  200. testerkit/cli/docs_cmd.py +100 -0
  201. testerkit/cli/instrument.py +172 -0
  202. testerkit/cli/mcp_cmd.py +37 -0
  203. testerkit/cli/metrics.py +366 -0
  204. testerkit/cli/project.py +312 -0
  205. testerkit/cli/root.py +14 -0
  206. testerkit/cli/runs.py +412 -0
  207. testerkit/cli/schema_cmd.py +87 -0
  208. testerkit/cli/serve_cmd.py +50 -0
  209. testerkit/cli/setup_cmd.py +604 -0
  210. testerkit/cli/station.py +294 -0
  211. testerkit/cli/validate.py +112 -0
  212. testerkit/client.py +463 -0
  213. testerkit/connect.py +635 -0
  214. testerkit/data/__init__.py +8 -0
  215. testerkit/data/_accumulator_pool.py +455 -0
  216. testerkit/data/_atomic.py +59 -0
  217. testerkit/data/_collection_indices.py +67 -0
  218. testerkit/data/_daemon_lifecycle.py +535 -0
  219. testerkit/data/_duckdb_daemon.py +695 -0
  220. testerkit/data/_duckdb_flight_server.py +813 -0
  221. testerkit/data/_event_filters.py +15 -0
  222. testerkit/data/_event_reader.py +101 -0
  223. testerkit/data/_flight_errors.py +199 -0
  224. testerkit/data/_flight_query.py +202 -0
  225. testerkit/data/_flight_retry.py +148 -0
  226. testerkit/data/_flight_subscribe.py +67 -0
  227. testerkit/data/_index_epoch.py +342 -0
  228. testerkit/data/_ipc_writer.py +185 -0
  229. testerkit/data/_json_safe.py +63 -0
  230. testerkit/data/_process.py +18 -0
  231. testerkit/data/_push_relay.py +124 -0
  232. testerkit/data/_runs_duckdb_daemon.py +2704 -0
  233. testerkit/data/_session_reaper.py +110 -0
  234. testerkit/data/_sql_helpers.py +30 -0
  235. testerkit/data/_store.py +24 -0
  236. testerkit/data/backends/__init__.py +7 -0
  237. testerkit/data/backends/_event_accumulator.py +957 -0
  238. testerkit/data/backends/_row_helpers.py +1172 -0
  239. testerkit/data/backends/parquet.py +1143 -0
  240. testerkit/data/backends/protocol.py +35 -0
  241. testerkit/data/channels/__init__.py +8 -0
  242. testerkit/data/channels/_flight_daemon.py +75 -0
  243. testerkit/data/channels/client.py +242 -0
  244. testerkit/data/channels/flight_manager.py +88 -0
  245. testerkit/data/channels/index.py +664 -0
  246. testerkit/data/channels/models.py +418 -0
  247. testerkit/data/channels/server.py +162 -0
  248. testerkit/data/channels/store.py +1481 -0
  249. testerkit/data/data_dir.py +61 -0
  250. testerkit/data/duckdb_manager.py +79 -0
  251. testerkit/data/event_log.py +384 -0
  252. testerkit/data/event_store.py +768 -0
  253. testerkit/data/events.py +1025 -0
  254. testerkit/data/exporters/__init__.py +6 -0
  255. testerkit/data/exporters/_helpers.py +40 -0
  256. testerkit/data/exporters/csv_exporter.py +168 -0
  257. testerkit/data/exporters/hdf5.py +234 -0
  258. testerkit/data/exporters/json_exporter.py +229 -0
  259. testerkit/data/exporters/mdf4.py +201 -0
  260. testerkit/data/exporters/stdf.py +255 -0
  261. testerkit/data/exporters/tdms.py +292 -0
  262. testerkit/data/files/__init__.py +71 -0
  263. testerkit/data/files/_backend.py +207 -0
  264. testerkit/data/files/_catalog_daemon.py +84 -0
  265. testerkit/data/files/catalog.py +218 -0
  266. testerkit/data/files/catalog_manager.py +325 -0
  267. testerkit/data/files/models.py +60 -0
  268. testerkit/data/files/serializers.py +414 -0
  269. testerkit/data/files/store.py +463 -0
  270. testerkit/data/files/streaming.py +783 -0
  271. testerkit/data/models.py +604 -0
  272. testerkit/data/ref.py +152 -0
  273. testerkit/data/retention.py +297 -0
  274. testerkit/data/run_store.py +367 -0
  275. testerkit/data/runs_duckdb_manager.py +103 -0
  276. testerkit/data/schema_dispatch.py +191 -0
  277. testerkit/data/schema_migrate.py +76 -0
  278. testerkit/data/schema_versions.py +85 -0
  279. testerkit/data/schemas.py +288 -0
  280. testerkit/data/subscribers/__init__.py +48 -0
  281. testerkit/data/subscribers/_base.py +20 -0
  282. testerkit/data/subscribers/_output_file.py +25 -0
  283. testerkit/data/subscribers/replay.py +55 -0
  284. testerkit/environment.py +84 -0
  285. testerkit/execution/__init__.py +11 -0
  286. testerkit/execution/_git.py +190 -0
  287. testerkit/execution/_state.py +740 -0
  288. testerkit/execution/accessors.py +54 -0
  289. testerkit/execution/audit.py +51 -0
  290. testerkit/execution/cascade.py +85 -0
  291. testerkit/execution/connections.py +491 -0
  292. testerkit/execution/harness.py +1931 -0
  293. testerkit/execution/instrument_events.py +42 -0
  294. testerkit/execution/limits.py +154 -0
  295. testerkit/execution/metadata.py +102 -0
  296. testerkit/execution/mocks.py +63 -0
  297. testerkit/execution/profiles.py +616 -0
  298. testerkit/execution/run_scope.py +1331 -0
  299. testerkit/execution/session_scope.py +196 -0
  300. testerkit/execution/sidecar.py +280 -0
  301. testerkit/execution/site_runner.py +705 -0
  302. testerkit/execution/sites.py +183 -0
  303. testerkit/execution/sync.py +294 -0
  304. testerkit/execution/uut_provider.py +231 -0
  305. testerkit/execution/vectors.py +205 -0
  306. testerkit/execution/verify.py +396 -0
  307. testerkit/expand.py +75 -0
  308. testerkit/files.py +235 -0
  309. testerkit/fixtures/__init__.py +6 -0
  310. testerkit/fixtures/manager.py +357 -0
  311. testerkit/grafana/__init__.py +1 -0
  312. testerkit/grafana/bootstrap.py +61 -0
  313. testerkit/grafana/cli.py +314 -0
  314. testerkit/grafana/dashboards/asset_utilization.json +92 -0
  315. testerkit/grafana/dashboards/channel_explorer.json +77 -0
  316. testerkit/grafana/dashboards/event_log.json +92 -0
  317. testerkit/grafana/dashboards/failure_pareto.json +104 -0
  318. testerkit/grafana/dashboards/measurement_distribution.json +97 -0
  319. testerkit/grafana/dashboards/measurement_trend.json +161 -0
  320. testerkit/grafana/dashboards/station_comparison.json +95 -0
  321. testerkit/grafana/dashboards/test_duration.json +104 -0
  322. testerkit/grafana/dashboards/unit_traceability.json +73 -0
  323. testerkit/grafana/dashboards/yield_overview.json +189 -0
  324. testerkit/grafana/provisioning/dashboards.yaml.j2 +12 -0
  325. testerkit/grafana/provisioning/datasources.yaml.j2 +17 -0
  326. testerkit/grafana/server.py +282 -0
  327. testerkit/init.py +724 -0
  328. testerkit/instruments/__init__.py +10 -0
  329. testerkit/instruments/base.py +82 -0
  330. testerkit/instruments/discovery/__init__.py +62 -0
  331. testerkit/instruments/discovery/_base.py +174 -0
  332. testerkit/instruments/discovery/lxi.py +152 -0
  333. testerkit/instruments/discovery/ni.py +95 -0
  334. testerkit/instruments/discovery/serial.py +73 -0
  335. testerkit/instruments/discovery/visa.py +98 -0
  336. testerkit/instruments/lifecycle.py +213 -0
  337. testerkit/instruments/loader.py +79 -0
  338. testerkit/instruments/locks.py +178 -0
  339. testerkit/instruments/mocks.py +230 -0
  340. testerkit/instruments/observer.py +270 -0
  341. testerkit/instruments/observers/__init__.py +36 -0
  342. testerkit/instruments/observers/_base.py +68 -0
  343. testerkit/instruments/observers/daqmx.py +70 -0
  344. testerkit/instruments/observers/descriptor.py +69 -0
  345. testerkit/instruments/observers/generic.py +92 -0
  346. testerkit/instruments/observers/lantz.py +31 -0
  347. testerkit/instruments/observers/modbus.py +96 -0
  348. testerkit/instruments/observers/motion.py +68 -0
  349. testerkit/instruments/observers/ni_modular.py +73 -0
  350. testerkit/instruments/observers/ophyd.py +80 -0
  351. testerkit/instruments/observers/pymeasure.py +120 -0
  352. testerkit/instruments/observers/qcodes.py +66 -0
  353. testerkit/instruments/observers/scpi.py +88 -0
  354. testerkit/instruments/observers/tektronix.py +57 -0
  355. testerkit/instruments/observers/visa.py +103 -0
  356. testerkit/instruments/pool.py +459 -0
  357. testerkit/instruments/proxy.py +59 -0
  358. testerkit/instruments/route_manager.py +329 -0
  359. testerkit/instruments/routed_proxy.py +88 -0
  360. testerkit/instruments/server.py +508 -0
  361. testerkit/instruments/switch.py +43 -0
  362. testerkit/instruments/visa.py +294 -0
  363. testerkit/matching/__init__.py +6 -0
  364. testerkit/matching/service.py +969 -0
  365. testerkit/mcp/__init__.py +6 -0
  366. testerkit/mcp/server.py +681 -0
  367. testerkit/mcp/tools.py +1868 -0
  368. testerkit/models/__init__.py +18 -0
  369. testerkit/models/capability.py +598 -0
  370. testerkit/models/catalog.py +76 -0
  371. testerkit/models/data_options.py +153 -0
  372. testerkit/models/enums.py +334 -0
  373. testerkit/models/instrument.py +152 -0
  374. testerkit/models/instrument_asset.py +25 -0
  375. testerkit/models/part.py +271 -0
  376. testerkit/models/part_manifest.py +96 -0
  377. testerkit/models/project.py +114 -0
  378. testerkit/models/station.py +140 -0
  379. testerkit/models/test_config.py +748 -0
  380. testerkit/ontology/__init__.py +39 -0
  381. testerkit/ontology/schema.py +188 -0
  382. testerkit/ontology/testerkit.yaml +1420 -0
  383. testerkit/parts/__init__.py +8 -0
  384. testerkit/parts/context.py +181 -0
  385. testerkit/parts/folder.py +251 -0
  386. testerkit/parts/loader.py +27 -0
  387. testerkit/prompts/__init__.py +17 -0
  388. testerkit/prompts/core.py +133 -0
  389. testerkit/pytest_plugin/__init__.py +1287 -0
  390. testerkit/pytest_plugin/autouse.py +369 -0
  391. testerkit/pytest_plugin/helpers.py +354 -0
  392. testerkit/pytest_plugin/hooks.py +1982 -0
  393. testerkit/pytest_plugin/markers.py +203 -0
  394. testerkit/pytest_plugin/retry.py +37 -0
  395. testerkit/pytest_plugin/sweeps.py +117 -0
  396. testerkit/queries.py +78 -0
  397. testerkit/reports/__init__.py +6 -0
  398. testerkit/reports/core.py +492 -0
  399. testerkit/reports/datasheet.py +831 -0
  400. testerkit/reports/templates/datasheet.html +383 -0
  401. testerkit/reports/templates/default.html +153 -0
  402. testerkit/sbom.py +142 -0
  403. testerkit/schema_export.py +101 -0
  404. testerkit/signals.py +63 -0
  405. testerkit/skills/README.md +58 -0
  406. testerkit/skills/templates/project-instructions.md +125 -0
  407. testerkit/skills/testerkit-analysis/SKILL.md +87 -0
  408. testerkit/skills/testerkit-capture/SKILL.md +114 -0
  409. testerkit/skills/testerkit-data/SKILL.md +138 -0
  410. testerkit/skills/testerkit-datasheets/SKILL.md +100 -0
  411. testerkit/skills/testerkit-datasheets/agents/scaffold-writer.md +115 -0
  412. testerkit/skills/testerkit-datasheets/agents/section-extractor.md +98 -0
  413. testerkit/skills/testerkit-datasheets/agents/section-reviewer.md +153 -0
  414. testerkit/skills/testerkit-datasheets/agents/section-splitter.md +89 -0
  415. testerkit/skills/testerkit-datasheets/agents/section-writer.md +226 -0
  416. testerkit/skills/testerkit-datasheets/references/catalog-pipeline.md +271 -0
  417. testerkit/skills/testerkit-datasheets/references/process-queue.md +42 -0
  418. testerkit/skills/testerkit-datasheets/references/scaffold.md +156 -0
  419. testerkit/skills/testerkit-datasheets/references/test-pipeline.md +312 -0
  420. testerkit/skills/testerkit-debug/SKILL.md +127 -0
  421. testerkit/skills/testerkit-interactive/SKILL.md +188 -0
  422. testerkit/skills/testerkit-interactive/references/live-ui-patterns.md +102 -0
  423. testerkit/skills/testerkit-mocks/SKILL.md +128 -0
  424. testerkit/skills/testerkit-parts/SKILL.md +126 -0
  425. testerkit/skills/testerkit-profiles/SKILL.md +139 -0
  426. testerkit/skills/testerkit-sites/SKILL.md +140 -0
  427. testerkit/skills/testerkit-stations/SKILL.md +146 -0
  428. testerkit/skills/testerkit-tests/SKILL.md +136 -0
  429. testerkit/store.py +1639 -0
  430. testerkit/ui/__init__.py +71 -0
  431. testerkit/ui/_asgi.py +162 -0
  432. testerkit/ui/app.py +32 -0
  433. testerkit/ui/components/__init__.py +13 -0
  434. testerkit/ui/components/artifact_viewer.py +250 -0
  435. testerkit/ui/components/channel_values.py +159 -0
  436. testerkit/ui/components/event_timeline.py +222 -0
  437. testerkit/ui/components/execution_gantt.py +210 -0
  438. testerkit/ui/components/file_streams.py +147 -0
  439. testerkit/ui/components/instrument_activity.py +146 -0
  440. testerkit/ui/components/session_table.py +118 -0
  441. testerkit/ui/pages/__init__.py +25 -0
  442. testerkit/ui/pages/channels/__init__.py +4 -0
  443. testerkit/ui/pages/channels/detail.py +932 -0
  444. testerkit/ui/pages/channels/list.py +515 -0
  445. testerkit/ui/pages/dashboard.py +236 -0
  446. testerkit/ui/pages/designer/__init__.py +5 -0
  447. testerkit/ui/pages/designer/graph.py +635 -0
  448. testerkit/ui/pages/designer/matching.py +663 -0
  449. testerkit/ui/pages/designer/page.py +593 -0
  450. testerkit/ui/pages/designer/properties.py +591 -0
  451. testerkit/ui/pages/designer/state.py +468 -0
  452. testerkit/ui/pages/docs/__init__.py +6 -0
  453. testerkit/ui/pages/docs/index.py +95 -0
  454. testerkit/ui/pages/docs/page.py +698 -0
  455. testerkit/ui/pages/events/__init__.py +3 -0
  456. testerkit/ui/pages/events/list.py +263 -0
  457. testerkit/ui/pages/explore.py +1108 -0
  458. testerkit/ui/pages/files/__init__.py +4 -0
  459. testerkit/ui/pages/files/detail.py +426 -0
  460. testerkit/ui/pages/files/list.py +447 -0
  461. testerkit/ui/pages/fixtures/__init__.py +8 -0
  462. testerkit/ui/pages/fixtures/detail.py +280 -0
  463. testerkit/ui/pages/fixtures/edit.py +370 -0
  464. testerkit/ui/pages/fixtures/list.py +180 -0
  465. testerkit/ui/pages/fixtures/new.py +195 -0
  466. testerkit/ui/pages/instruments/__init__.py +4 -0
  467. testerkit/ui/pages/instruments/detail.py +320 -0
  468. testerkit/ui/pages/instruments/edit.py +482 -0
  469. testerkit/ui/pages/instruments/list.py +249 -0
  470. testerkit/ui/pages/instruments/new.py +200 -0
  471. testerkit/ui/pages/launch.py +261 -0
  472. testerkit/ui/pages/live.py +124 -0
  473. testerkit/ui/pages/metrics_page.py +1229 -0
  474. testerkit/ui/pages/parts/__init__.py +11 -0
  475. testerkit/ui/pages/parts/detail.py +202 -0
  476. testerkit/ui/pages/parts/edit.py +310 -0
  477. testerkit/ui/pages/parts/list.py +160 -0
  478. testerkit/ui/pages/parts/new.py +170 -0
  479. testerkit/ui/pages/parts/requirements.py +153 -0
  480. testerkit/ui/pages/parts/stations.py +199 -0
  481. testerkit/ui/pages/profiles/__init__.py +4 -0
  482. testerkit/ui/pages/profiles/detail.py +113 -0
  483. testerkit/ui/pages/profiles/list.py +86 -0
  484. testerkit/ui/pages/results/__init__.py +4 -0
  485. testerkit/ui/pages/results/detail.py +631 -0
  486. testerkit/ui/pages/results/list.py +230 -0
  487. testerkit/ui/pages/stations/__init__.py +4 -0
  488. testerkit/ui/pages/stations/detail.py +256 -0
  489. testerkit/ui/pages/stations/edit.py +273 -0
  490. testerkit/ui/pages/stations/list.py +180 -0
  491. testerkit/ui/pages/stations/new.py +311 -0
  492. testerkit/ui/pages/tests/__init__.py +4 -0
  493. testerkit/ui/pages/tests/detail.py +152 -0
  494. testerkit/ui/pages/tests/list.py +194 -0
  495. testerkit/ui/pages/uuts/__init__.py +4 -0
  496. testerkit/ui/pages/uuts/list.py +97 -0
  497. testerkit/ui/shared/__init__.py +8 -0
  498. testerkit/ui/shared/components.py +1826 -0
  499. testerkit/ui/shared/dialogs.py +105 -0
  500. testerkit/ui/shared/event_binding.py +194 -0
  501. testerkit/ui/shared/layout.py +314 -0
  502. testerkit/ui/shared/services.py +1719 -0
  503. testerkit/ui/shared/timestamps.py +21 -0
  504. testerkit/ui/static/branding/favicon-16.png +0 -0
  505. testerkit/ui/static/branding/favicon-32.png +0 -0
  506. testerkit/ui/static/branding/favicon-48.png +0 -0
  507. testerkit/ui/static/branding/testerkit-icon-256.png +0 -0
  508. testerkit/ui/static/branding/testerkit-mark.svg +1 -0
  509. testerkit/ui/static/branding/testerkit-wordmark.svg +1 -0
  510. testerkit/ui/static/global.css +465 -0
  511. testerkit/utils/__init__.py +17 -0
  512. testerkit/utils/enum_meta.py +800 -0
  513. testerkit/utils/paths.py +75 -0
  514. testerkit/utils/ranges.py +257 -0
  515. testerkit/validation.py +82 -0
  516. testerkit/verbs.py +142 -0
  517. testerkit-0.4.0.dist-info/METADATA +250 -0
  518. testerkit-0.4.0.dist-info/RECORD +521 -0
  519. testerkit-0.4.0.dist-info/WHEEL +4 -0
  520. testerkit-0.4.0.dist-info/entry_points.txt +5 -0
  521. testerkit-0.4.0.dist-info/licenses/LICENSE +191 -0
@@ -0,0 +1,76 @@
1
+ # Event sourcing
2
+
3
+ The platform stores test execution data as an immutable, append-only log of events. The queryable Run / Step / Measurement views (parquet, DuckDB index) are *views built from* that log — derived from the events, rebuildable at any time, never the source of truth themselves.
4
+
5
+ The companion pages cover the **what**: [Event log](event-log.md) for the event-type taxonomy and dispatch; [Data stores](data-stores.md) for the on-disk layout of events, channels, files, and the run parquet. This page covers the **why** — what a traditional CRUD test-result schema (one where you create a run row and update it in place) forces on a platform, and what changes when you flip the model.
6
+
7
+ ## Why one mutable row is awkward for a live run
8
+
9
+ A traditional test-result schema models a Run as a single row, with steps and measurements as child rows in a hierarchy. To record what happened, you have one of two options, each with a real cost:
10
+
11
+ **Option A — UPDATE-style.** Insert the run row at start (with `outcome=NULL, ended_at=NULL`), then UPDATE both fields when the run finishes. Simple, but updating a live row in place brings race conditions when something reads it mid-update, audit-trail difficulties (when did the row become `passed`?), and a category of bugs where consumers see partially-updated state.
12
+
13
+ **Option B — Post-processing stance.** Buffer everything in memory until the run ends, then INSERT the whole hierarchy in one transaction. No live visibility during the run; total data loss if the producer crashes mid-flight.
14
+
15
+ Both shapes force the schema to be the source of truth, which means one row has to represent both "the run is in progress" and "the run is finished" — and that's what forces the in-place updates.
16
+
17
+ ## The inversion: events as primary, runs as projections
18
+
19
+ TesterKit emits a separate immutable event for each thing that happens: `RunStarted`, `StepStarted`, `MeasurementRecorded`, `StepEnded`, `RunEnded`. The event log is the source of truth. The Run/Step/Measurement views (parquet, DuckDB index) are derived projections — regenerable from the events at any time.
20
+
21
+ Most data systems treat the entity (Run) as primary and the audit log (events) as a secondary concern. TesterKit inverts that: events are primary, the entity is a projection. That single inversion is what sidesteps the whole problem. Nothing ever needs an UPDATE because the view isn't authoritative — it's rebuilt from the events.
22
+
23
+ ## Properties that fall out
24
+
25
+ - **Live visibility during a run.** The UI subscribes to the event log and surfaces in-progress runs as events arrive. No "wait for end" behavior, no NULL outcomes in queries.
26
+ - **Crash recovery is automatic.** A producer that crashes mid-run leaves a `RunStarted` event with no matching `RunEnded`. The orphan-finalization path emits a synthetic `RunEnded(aborted)` and the view rebuilds normally. No partially-written run row to reconcile.
27
+ - **Replay is free.** Want a new analytical view three years from now — different schema, new format, additional aggregations? Replay the event log into a new projection. Traditional CRUD requires migration scripts; here it's just consumption.
28
+ - **Audit log = primary data path.** Some regulated industries (medical, aerospace, defense) require immutable audit trails of test runs. In CRUD systems that's a parallel write you bolt on; here it's the architecture.
29
+ - **Time-travel queries are natural.** "What did the system know at 14:32:17?" is just `WHERE occurred_at <= '14:32:17'` grouped by `run_id`. CRUD systems need temporal-database extensions to answer that.
30
+ - **Cross-system correlation comes for free.** An async temperature probe doesn't have to be a first-class participant in execution. It writes channel samples (or emits events) on its own schedule; any consumer can correlate against any time window. Producers don't have to know each other exist — that's a property of log-based architectures that integration-by-database can't get.
31
+ - **Composable consumers.** New analytics view, new format, new dashboard? Subscribe to (or replay) the event log and build your own view. The producer side doesn't change.
32
+
33
+ ## The principled split
34
+
35
+ Not everything is event-sourced — that would be the wrong shape for some data. TesterKit splits along a clean line:
36
+
37
+ | Data shape | Pattern | Why |
38
+ |---|---|---|
39
+ | Configuration (`testerkit.yaml`, `station.yaml`, parts, catalog) | CRUD via YAML, hand-edited | Operators evolve them deliberately over time |
40
+ | Test execution data (runs, steps, measurements, events) | Append-only events → derived projections | Immutable historical record |
41
+ | Channel sample data (high-rate time-series) | Append-only sample streams (event log carries metadata) | Same domain semantics, different physics — too large for the event log |
42
+
43
+ Configuration *should* be mutable: you add a station, change a limit, update a part spec. Execution data *shouldn't* be mutable: a run happened on a date with an outcome, and that doesn't change. Channel data is execution data with a size exception — sample streams at kHz–MHz rates can't fit through the event log, so they get their own append-only log with event-log metadata referencing them.
44
+
45
+ Annotations, retroactive flags, and RMA links don't break this — they're "new facts about old runs," not edits. The event-sourced answer is to emit a new event type for the new fact (e.g. a future `RunFlagged` or `MeasurementAnnotated`) and let projections incorporate it. You're not mutating the past, you're recording new observations about it.
46
+
47
+ ## Materializers run in whatever process cares
48
+
49
+ In a CRUD world, "the database" is a single shared mutable structure every consumer reads and writes against. In an event-sourced world, the *events* are the shared contract — and each consumer can run *its own* materializer in *its own* process, on whatever cadence makes sense for that consumer. Different processes can care about different projections without coordinating.
50
+
51
+ That's why TesterKit's materializers take the shape they do:
52
+
53
+ - The runs daemon cares about an always-on, queryable view of recent runs, so it runs as a long-lived consumer: it collects events per in-flight run and, on `RunEnded`, writes the canonical per-run parquet. It also takes over for runs whose runner crashed. Same view, different trigger.
54
+ - Each exporter (CSV, JSON, HDF5, STDF, …) replays events after the fact via `testerkit export`, each in its own process at whatever cadence the operator invokes.
55
+ - Any future consumer — a Grafana exporter, a Snowflake pipeline, an analytics view — would run its own materializer in its own process at whatever cadence it likes (per-event, per-run, batched hourly). Sync or async, in-process or out, ephemeral or long-running — those are local choices, not architectural commitments.
56
+
57
+ There's no "the materialization service" everyone has to wait on, no central writer that becomes a bottleneck. Each consumer's timing is local to its process; from the system's perspective they're all running independently, all deriving their own views from the same shared event log. That's the property that makes log-based architectures composable without coordination.
58
+
59
+ ## Trade-offs
60
+
61
+ - **Eventual consistency on projections.** Between event emission and projection materialization there's a window where queries see stale data. For test execution this window is short (per-event for live views; end-of-run for the canonical parquet) and the live event view fills the gap, but it's a real property to be aware of.
62
+ - **More upfront design work.** You have to choose event types deliberately. Adding a new dimension means adding an event field (additive, fine) or a new event class (also fine). But the design conversation is heavier than "add a column."
63
+ - **Harder mental model for newcomers.** Most developers expect to query a `runs` table directly. The "events are primary, this query hits a projection" framing requires explanation. This page is part of paying that tax.
64
+
65
+ ## See also
66
+
67
+ **Same topic, other quadrants:**
68
+
69
+ - [Reference → Event types](../../reference/data/event-types.md) — every event class the runtime emits, generated from source
70
+ - [Reference → Parquet schema](../../reference/data/parquet-schema.md) — the materialized projection's column-level reference
71
+ - [How-to → Export results](../../how-to/data/export-results.md) — exporters as event-replay consumers (one materializer per format)
72
+
73
+ **Sibling concepts:**
74
+
75
+ - [Event log](event-log.md) — the *what*: event types, dispatch, durability
76
+ - [Data stores](data-stores.md) — events / channels / files / run projection; on-disk layout, `data_dir` resolution, schema-evolution contract
@@ -0,0 +1,58 @@
1
+ # Flight Cross-Process Model
2
+
3
+ TesterKit uses Apache Arrow Flight for cross-process data access. This enables real-time queries from any process — the operator UI, CLI tools, AI agents, or Grafana — without file locking or polling.
4
+
5
+ ## Why Arrow Flight
6
+
7
+ Arrow Flight provides:
8
+
9
+ - **Zero-copy** — Arrow record batches transfer between processes without being copied or re-encoded, so live queries stay fast even with a lot of data
10
+ - **Cross-process** — Multiple processes query the same data through a shared background service
11
+ - **Language-agnostic** — Any Arrow Flight client (Python, Go, Rust, Java) can connect
12
+ - **SQL queryability** — [DuckDB](https://duckdb.org/) (an embedded analytical SQL engine that reads Parquet/Arrow directly) runs as the query engine behind the service
13
+
14
+ Reading the files directly from each process works, but processes can collide on file locks, and a reader can't see data still buffered in memory — the shared background service avoids both.
15
+
16
+ ## How multiple processes see the same live data
17
+
18
+ ```mermaid
19
+ flowchart LR
20
+ A["pytest run"] -->|write + read| svc["Shared background service"]
21
+ B["testerkit serve / UI"] -->|read| svc
22
+ C["testerkit runs / your script"] -->|read| svc
23
+ svc --> disk["Persistent index on disk"]
24
+ svc --> live["Live in-memory overlay\n(in-progress runs)"]
25
+ ```
26
+
27
+ Your pytest run, `testerkit serve`, `testerkit runs`, and any script you write all talk to the same shared background service. The service keeps data in two places: a persistent index on disk (a DuckDB file) and a live in-memory overlay for in-progress runs. The overlay is what lets a query see a result the instant after it's written — before the run is even complete.
28
+
29
+ The first tool that needs the data starts the shared background service automatically; everything else just connects to it. You never manage it yourself. The service shuts itself down after it has been idle for a while.
30
+
31
+ ## On disk and live at the same time
32
+
33
+ The event data uses a dual-write pattern for crash safety and instant queryability:
34
+
35
+ 1. **Arrow files on disk** — append-only, survive crashes. Date-partitioned, with one file per writing process.
36
+ 2. **Live push to the shared service** — new data is sent to the service as it's written, making it available for SQL queries immediately.
37
+
38
+ If that live push fails, the data is still safe in the Arrow files on disk. On the next start, the service loads those files into the index and picks up where it left off.
39
+
40
+ Event data and channel (waveform) data each have their own shared service — separate background processes running the same mechanism — so a heavy waveform capture doesn't compete with event queries.
41
+
42
+ ## Downsampling waveforms for display
43
+
44
+ A captured waveform can have millions of samples — too many to plot. The `max_points` parameter thins it to a target count using LTTB (Largest Triangle Three Buckets), a downsampling algorithm that preserves peaks and dips so the displayed shape still matches the real signal.
45
+
46
+ ```python
47
+ # Query with decimation for visualization
48
+ table = channel_store.query(
49
+ "scope.ch1_waveform",
50
+ session_id="abc123",
51
+ max_points=1000, # LTTB downsample to 1000 points
52
+ )
53
+ ```
54
+
55
+ ## See also
56
+ - [Event Log Architecture](event-log.md) — How events flow through the system
57
+ - [Data stores](data-stores.md) — all four data stores
58
+ - [Querying Channels Guide](../../how-to/data/querying-channels.md) — Practical channel queries
@@ -0,0 +1,17 @@
1
+ # Concepts — Data
2
+
3
+ Where the run data lives and how the platform stays consistent across processes. The event log is the source of truth; everything else is a derived view.
4
+
5
+ - [Event log](event-log.md) — the durable, append-only record of every test run; the source of truth
6
+ - [Event sourcing](event-sourcing.md) — why the platform is event-sourced rather than mutation-based; what that buys you for replay, debugging, and audit
7
+ - [Sessions](sessions.md) — connect-to-disconnect observation windows; how a single session can contain multiple runs (multi-UUT) or a long-running instrument session (operator UI, scripts)
8
+ - [Three verbs](three-verbs.md) — `observe`, `verify`, `stream`; how the platform routes a measured value to the right store by shape
9
+ - [Data stores](data-stores.md) — EventStore (events), ChannelStore (time-series), FileStore (artifacts), RunStore (run rows); on-disk layout, `data_dir` resolution, schema-evolution contract
10
+ - [Flight streaming](flight-streaming.md) — cross-process data access via Apache Arrow Flight; why the platform uses it for low-latency queries
11
+
12
+ ## See also
13
+
14
+ - [Reference → Event types](../../reference/data/event-types.md) — every event class the runtime emits, generated from source
15
+ - [Reference → Parquet schema](../../reference/data/parquet-schema.md) — every column in the materialized run parquet
16
+ - [Reference → Query API](../../reference/data/query-api.md) — `RunsQuery`, `StepsQuery`, `MeasurementsQuery` — the read path the UI and HTTP API both use
17
+ - [How-to → Querying events](../../how-to/data/querying-events.md), [Querying channels](../../how-to/data/querying-channels.md), [Export results](../../how-to/data/export-results.md)
@@ -0,0 +1,78 @@
1
+ # Sessions as Observation Windows
2
+
3
+ A session is the window from connect to disconnect — the time a process is actively using instruments and logging data.
4
+
5
+ ## What is a Session?
6
+
7
+ A session begins when a process calls `connect()` and ends when the connection is released. During a session, all events share the same `session_id`, making it easy to group and query related activity.
8
+
9
+ There's no sessions table. A session is simply every event that shares one `session_id` — TesterKit groups them when you query. It "begins" and "ends" because the first and last events (`SessionStarted` / `SessionEnded`) mark the boundaries.
10
+
11
+ Sessions are broader than test runs. A single session might contain multiple test runs (e.g., retesting the same UUT), or no test runs at all (e.g., a calibration script or manual instrument exploration).
12
+
13
+ ## Session Metadata
14
+
15
+ `SessionStarted` (see [event-log](event-log.md) for the full event list) records who ran it, on which station, and how (pytest, Jupyter, a script). Per-run context (UUT, part, test phase, git, environment) lives on `RunStarted`, emitted once per test run within the session.
16
+
17
+ | Category | Fields |
18
+ |----------|--------|
19
+ | **Session** | `session_type` |
20
+ | **Station** | `station_id`, `station_name`, `station_type`, `station_location`, `station_hostname` |
21
+ | **Process** | `pid`, `client` (pytest, jupyter, script name) |
22
+ | **Operator** | `operator_id`, `operator_name` |
23
+ | **Fixture / site** | `fixture_id`, `site_count`, `site_names` |
24
+
25
+ `RunStarted` (emitted once per test run within a session) carries the per-run context:
26
+
27
+ | Category | Fields |
28
+ |----------|--------|
29
+ | **UUT** | `uut_serial_number`, `uut_part_number`, `uut_revision`, `uut_lot_number` |
30
+ | **Part** | `part_id`, `part_name`, `part_revision` |
31
+ | **Site** | `site_index`, `site_name` |
32
+ | **Test context** | `fixture_id`, `test_phase`, `project_name` |
33
+ | **Git** | `git_commit`, `git_branch`, `git_remote` |
34
+ | **Environment** | `environment_json` (Python version, testerkit version, top-level deps, lockfile hash) |
35
+ | **Custom** | `custom_metadata` dict |
36
+
37
+ Config files (station, fixture, part spec) are tracked via git — the `git_commit` field on each `RunStarted` identifies the exact code and config state.
38
+
39
+ ## Why Sessions Exist
40
+
41
+ Sessions solve three problems:
42
+
43
+ 1. **Grouping events across runs** — Multiple test runs on the same UUT during one sitting share a session. You can query "everything that happened while bench-7 was connected" without knowing individual run IDs.
44
+
45
+ 2. **Live monitoring** — The operator UI subscribes to events by `session_id` to show real-time progress. The session boundary tells the UI when to start and stop monitoring.
46
+
47
+ 3. **Resource coordination** — Sessions track which instruments are in use, enabling per-resource locking. Two scripts can use different instruments on the same station simultaneously.
48
+
49
+ ## The `connect()` API
50
+
51
+ ```python
52
+ from testerkit import connect
53
+
54
+ # Using a `with` block (scripts, notebooks)
55
+ with connect("cell-7", mock=True) as station:
56
+ dmm = station.instrument("dmm")
57
+ v = dmm.measure_voltage()
58
+ # All interactions logged with this session's ID
59
+
60
+ # Explicit start/stop (UI, long-running processes)
61
+ station = connect("cell-7")
62
+ station.start()
63
+ dmm = station.instrument("dmm")
64
+ # ... work ...
65
+ station.stop()
66
+ ```
67
+
68
+ `connect()` starts a session that:
69
+ - gets a new `session_id`
70
+ - emits `SessionStarted` with full context
71
+ - locks each instrument it uses, so two scripts can share a station
72
+ - emits `SessionEnded` when it closes
73
+
74
+ ## See also
75
+ - [Event Log Architecture](event-log.md) — How events are stored and queried
76
+ - [Data stores](data-stores.md) — where the events behind a session are stored
77
+ - [connect() reference](../../reference/runtime/connect.md) — full API surface
78
+ - [Managing Sessions Guide](../../how-to/execution/managing-sessions.md) — Practical session management
@@ -0,0 +1,260 @@
1
+ # The Three Test-Author Verbs
2
+
3
+ Every value a test records plays one of three roles:
4
+
5
+ | Role | What it means | Written by |
6
+ |---|---|---|
7
+ | `input` | What you set — a commanded value, setpoint, or condition | `context.configure("vin", 5.0)` |
8
+ | `output` | What you read back — a response, environment reading, or captured artifact | `observe("v_rail", 3.3)` |
9
+ | `measurement` | The judged result with limits and a pass/fail outcome | `verify("v_rail", 3.31, limit=…)` / `measure(…)` |
10
+
11
+ The platform routes each call to the right store. You write the verb; the role follows automatically.
12
+
13
+ `stream` is a fourth verb — for continuous signals — and is covered below. It does not add an input/output/measurement field to the vector; it writes to the channel timeline.
14
+
15
+ ## What `configure` does
16
+
17
+ `context.configure` records the stimulus you commanded before the DUT responded:
18
+
19
+ ```python
20
+ def test_load_regulation(psu, dmm, context):
21
+ context.configure("psu.voltage", 5.0, unit="V") # → input role
22
+ context.configure("load.current", 1.0, unit="A") # → input role
23
+
24
+ reading = dmm.measure_dc_voltage()
25
+ verify("vout", reading, limit=Limit(low=4.75, high=5.25, unit="V"))
26
+ ```
27
+
28
+ The `input` values travel with the measurement row. When you query across runs, you can plot `vout` (measurement) against `psu.voltage` (input) to see load regulation without doing any joins.
29
+
30
+ ## What `observe` does
31
+
32
+ `observe` records a value you read from the DUT or environment — the response side:
33
+
34
+ ```python
35
+ observe("temp", temp_probe.read(), unit="°C") # → output role
36
+ observe("v_rail", dmm.measure_dc_voltage()) # → output role, scalar
37
+ observe("scope_cap", scope.capture()) # → output role, Waveform
38
+ ```
39
+
40
+ `observe` accepts any shape — the value's shape determines which store receives it:
41
+
42
+ | Value shape | Routes to | How to query later |
43
+ |---|---|---|
44
+ | Scalar (`int` / `float` / `bool` / `str`) | Inline on the measurement row | `FieldRef.output("v_rail")` |
45
+ | Array of scalars (list / ndarray) | ChannelStore | `/channels/{id}` chart panel |
46
+ | `Waveform` (Y + `sample_interval`) | ChannelStore | `/channels/{id}` chart panel |
47
+ | `XYData` (paired x/y arrays with per-axis units) | FileStore as `.npz` | `FieldRef.output("iv_curve")` → URI |
48
+ | File / object (image / `bytes` / `Path` / Pydantic model) | FileStore (via a registered serializer) | `FieldRef.output("setup_photo")` → URI |
49
+
50
+ The verb you call decides that a value is an output. The value's shape decides where the bytes land. The two choices are independent.
51
+
52
+ ## What `verify` and `measure` do
53
+
54
+ `verify` records a judged result — the measurement proper:
55
+
56
+ ```python
57
+ verify("rise_time_us", rise_time(wf), limit=Limit(low=0, high=20, unit="us"))
58
+ # → measurement role: value, limits, outcome (PASSED / FAILED / ERRORED), unit
59
+ ```
60
+
61
+ `measure` is the record-only sibling — it stamps `Outcome.DONE` and never raises on a missing limit:
62
+
63
+ ```python
64
+ measure("rail_ripple", ripple(dmm.read_waveform()))
65
+ # → measurement role, no limit check
66
+ ```
67
+
68
+ Both are scalar-only. Passing a `Waveform`, array, or blob to `verify` or `measure` raises `TypeError` with a message pointing at the two-verb pattern: `observe` the evidence first, then `verify` the derived scalar.
69
+
70
+ ## Querying by role
71
+
72
+ Because role is stored with every field, the query API lets you reference any field by its role and name — you don't have to guess a prefixed column name like `in_vin`:
73
+
74
+ ```python
75
+ from testerkit.queries import MeasurementsQuery, FieldRef
76
+
77
+ q = MeasurementsQuery()
78
+
79
+ # Plot vout (measurement) vs vin (input) across runs
80
+ rows = q.parametric(
81
+ y=FieldRef.measurement("vout"),
82
+ x=FieldRef.input("vin"),
83
+ )
84
+
85
+ # Plot temperature (output) vs date across runs
86
+ rows = q.parametric(
87
+ y=FieldRef.output("temp"),
88
+ x="run_started_at", # bare string = a built-in run column
89
+ )
90
+
91
+ # A bare string is measurement shorthand — the most common case
92
+ rows = q.parametric(y="vout", x=FieldRef.input("vin"))
93
+ ```
94
+
95
+ `FieldRef.input("vin")` / `FieldRef.output("v_rail")` / `FieldRef.measurement("vout")` are the three constructors. A bare string resolves to `FieldRef.measurement(name)` because measurements carry limits and outcomes — they're what analysis is overwhelmingly about. Inputs and outputs always require the explicit `FieldRef`.
96
+
97
+ See [Reference → Query API](../../reference/data/query-api.md) for the full `MeasurementsQuery` surface.
98
+
99
+ ## Discrete vs continuous — where `stream` fits
100
+
101
+ `observe` and `stream` both write to ChannelStore for array/waveform values. The difference is **intent**, not data shape:
102
+
103
+ | | `observe` (discrete) | `stream` (continuous) |
104
+ |---|---|---|
105
+ | Unit of data | One call, one captured value | The channel itself; samples are unnamed appends |
106
+ | Identity | Each call has a name and a vector context | The channel has a name; samples don't |
107
+ | Role on the vector | `output` (for scalars / URIs) | None — stream doesn't add an input/output/measurement field |
108
+ | T&M shape | Triggered acquisition, scope capture, snapshot | Live sensor feed, free-run, continuous monitor |
109
+
110
+ **Rate doesn't decide. Intent does.** A 1-sample-per-5-minute temperature probe is continuous because the temperature is a signal that exists whether you sample it or not.
111
+
112
+ - **Continuous → stream** — samples accumulate on the channel timeline; the channel as a whole is the unit you query later
113
+ - **Discrete → observe** — each call is anchored to a specific moment in the test; queryable by `FieldRef.output(name)` for scalar outputs or URI navigation for waveforms
114
+
115
+ What breaks if you mix them up:
116
+
117
+ - `observe` in a high-rate loop → one event recorded per call (floods the event log), and the last call overwrites the previous output URI on scalar outputs
118
+ - `stream` for a single discrete capture → no output stamped on the vector → can't navigate from the measurement to the supporting waveform without knowing the `channel_id` + time window
119
+
120
+ To associate a streamed channel with a measurement vector, pass the sink to `observe`:
121
+
122
+ ```python
123
+ with testerkit.channels.stream("scope.continuous") as cap:
124
+ for _ in range(n_samples):
125
+ cap.write(scope.read_trace())
126
+ observe("scope.snapshot", cap) # stamps cap.uri as an output on the active vector
127
+ ```
128
+
129
+ ## Two layers — test-author verbs vs store-direct
130
+
131
+ TesterKit exposes data writing at two layers:
132
+
133
+ | Layer | Use when | Discrete | Continuous |
134
+ |---|---|---|---|
135
+ | **Test-author verbs** | Inside a test (has a vector context) | `observe(name, value)`, `verify(name, value, limit=…)` | `stream(name, sample)` |
136
+ | **Store-direct** | Outside a test — notebooks, scripts, operator UI | `channels.write(name, sample)` | `channels.stream(name)` |
137
+
138
+ The test-author verbs are built on top of the store-direct calls. What `observe` adds is recording the value's role and storage URI on the active test vector. None of that makes sense outside a test, so the store-direct surface skips it.
139
+
140
+ `stream` does not associate with the vector automatically. Only `observe` stamps the vector. To associate a streamed channel with the active vector, pass the sink to `observe` as shown above.
141
+
142
+ ## Engineering units
143
+
144
+ `configure`, `observe`, `verify`, `measure`, and `stream` all accept an optional `unit=` keyword across every call shape — top-level verb, pytest fixture, and `Context` method. The unit is stored alongside the value and is visible in query results:
145
+
146
+ ```python
147
+ context.configure("psu.voltage", 12.0, unit="V")
148
+ observe("temp", 24.8, unit="°C")
149
+ stream("current", sample, unit="A")
150
+ verify("output_voltage", dmm.measure_dc_voltage(), Limit(low=4.75, high=5.25, unit="V"))
151
+ measure("quiescent_current", psu.measure_current(), unit="A")
152
+ ```
153
+
154
+ For `verify`, the unit can come from `unit=` on the call, from `Limit(unit=)`, or from both when they agree. If both are provided and they differ, TesterKit raises `ValueError` immediately — it does not silently let one override the other.
155
+
156
+ For multi-axis data (IV curves, S-parameter sweeps, optical spectra), use `XYData` — it carries `x_unit` and `y_unit` as separate per-axis fields:
157
+
158
+ ```python
159
+ from testerkit.data.models import XYData
160
+
161
+ iv = XYData(x=[0.0, 0.5, 1.0, 1.5], y=[0.0, 2.1, 4.3, 6.8],
162
+ x_unit="V", y_unit="mA", x_name="Bias", y_name="Current")
163
+ observe("iv_curve", iv) # → FileStore .npz; output URI on the vector
164
+ ```
165
+
166
+ ## Capturing evidence and judging a metric together
167
+
168
+ The canonical pattern is `observe` for the raw evidence, then `verify` for the derived scalar:
169
+
170
+ ```python
171
+ def test_psu_step_response(psu, scope, context):
172
+ psu.set_voltage(5.0)
173
+ wf = scope.capture() # block-mode acquisition
174
+
175
+ observe("scope_cap", wf) # Waveform → ChannelStore
176
+ # output URI stamped on this vector
177
+
178
+ verify("rise_time_us", rise_time(wf),
179
+ limit=Limit(low=0, high=20, unit="us")) # scalar → measurement row, judged
180
+ verify("overshoot_v", overshoot(wf),
181
+ limit=Limit(low=0, high=0.05, unit="V")) # same vector, same scope_cap URI
182
+ ```
183
+
184
+ Both `rise_time_us` and `overshoot_v` measurement rows carry the `scope_cap` channel URI. From any failing measurement on `/results/{run_id}` you can navigate directly to the supporting waveform.
185
+
186
+ ## Streaming continuously while the test runs
187
+
188
+ For continuous capture across the test body, use the context-managed sink:
189
+
190
+ ```python
191
+ import testerkit.channels
192
+
193
+ with testerkit.channels.stream("scope.continuous") as cap:
194
+ for _ in range(n_samples):
195
+ cap.write(scope.read_trace())
196
+ observe("scope.snapshot", cap) # stamps cap.uri as an output on the active vector
197
+ ```
198
+
199
+ For byte-stream artifacts (video / audio / vendor capture), use `testerkit.files.stream`:
200
+
201
+ ```python
202
+ import testerkit.files
203
+
204
+ with testerkit.files.stream("camera", format="mp4") as cam:
205
+ for frame in camera.read_frames():
206
+ cam.write(frame)
207
+ ```
208
+
209
+ `files.stream` requires `format=` — the platform cannot infer `mp4` vs `wav` vs `tdms` vs `raw` from opaque bytes. Every other dispatch (scalar vs array vs blob, ChannelStore vs FileStore, inline vs URI) is automatic.
210
+
211
+ ## How streams are stored — segmented per session, unified by channel
212
+
213
+ A long-running stream is not one giant file. ChannelStore files are partitioned by date and by session:
214
+
215
+ ```
216
+ data/channels/2026-06-03/env_temp_aaaa1111.arrow ← Session A, day 1
217
+ data/channels/2026-06-04/env_temp_aaaa1111.arrow ← Session A, day 2
218
+ data/channels/2026-06-03/env_temp_bbbb2222.arrow ← Session B, day 1
219
+ ```
220
+
221
+ The session boundary is the natural unit for retention, export, and crash isolation. The date partition keeps any single file from growing unbounded.
222
+
223
+ The logical channel identity is not partitioned. `channels.query("env.temp")` with no session filter spans every session that wrote to that channel. Add `session_id=…` to scope a query to one session.
224
+
225
+ ## Concurrent sessions share instruments, not records
226
+
227
+ If two test sessions run concurrently and both share a scope (locked atomically by the platform), their captures are isolated on disk:
228
+
229
+ - ChannelStore: `session_id` is in the filename
230
+ - FileStore: `session_id` is in the directory
231
+ - EventStore: events from both sessions interleave on one timeline, but every event carries `session_id`
232
+ - RunStore: one parquet file per run, never cross-run mixing
233
+
234
+ The instrument lock orders captures in time; storage isolation is independent. The one shared thing is the channel-id namespace — `/channels/scope_cap` shows both sessions' data unless filtered. That's deliberate: cross-session views matter for trends and fixture-channel accumulation.
235
+
236
+ ## Custom types
237
+
238
+ Unknown value types fall back to pickle with a `RuntimeWarning` that names the type and points at `register_serializer`. Register once and your type is auto-routed thereafter:
239
+
240
+ ```python
241
+ from testerkit.data.files import register_serializer
242
+
243
+ register_serializer(
244
+ MyInstrumentFrame,
245
+ extension=".bin",
246
+ mime="application/octet-stream",
247
+ write=lambda value, dest: dest.write_bytes(value.to_bytes()),
248
+ )
249
+ ```
250
+
251
+ Alternatively, implement `testerkit_serialize(dest: Path) -> Path` on your type — no registration call needed.
252
+
253
+ ## See also
254
+
255
+ - [Data stores](data-stores.md) — where parquet, ChannelStore, and FileStore live on disk and how they relate
256
+ - [Tutorial 11 — Waveforms and evidence](../../tutorial/11-waveforms-and-evidence.md) — pytest test with `observe` + `verify`
257
+ - [Tutorial 12 — Continuous monitoring](../../tutorial/12-continuous-monitoring.md) — interactive session with `channels.stream` + live UI
258
+ - [Reference → Query API](../../reference/data/query-api.md) — `MeasurementsQuery`, `FieldRef`, and the role-based query surface
259
+ - [Reference → Parquet schema](../../reference/data/parquet-schema.md) — column conventions; `channel://` and `file://` URI formats
260
+ - [Reference → Channels](../../reference/operator-ui/channels/list.md) — the operator UI page where ChannelStore samples appear
@@ -0,0 +1,13 @@
1
+ # Concepts — Execution
2
+
3
+ How a test run unfolds — the step model, what each step records, and how outcomes roll up from leaves to runs.
4
+
5
+ - [Step hierarchy](step-hierarchy.md) — how test classes, methods, and parametrize vectors nest into a tree of step events
6
+ - [Step manifest](step-manifest.md) — what each step records (inputs, outputs, measurements, retries) and how the platform turns events into parquet rows
7
+ - [Outcomes](outcomes.md) — the severity ladder (`skipped` → `done` → `passed` → `failed` → `errored` → `terminated` → `aborted`) and how parent steps roll up child outcomes
8
+
9
+ ## See also
10
+
11
+ - [How-to → Writing tests](../../how-to/execution/writing-tests.md) — task recipe for authoring test code that fits the step model
12
+ - [Reference → Pytest fixtures](../../reference/pytest/fixtures.md) — the fixtures the step model exposes (`context`, `verify`, `measure`)
13
+ - [Data](../data/index.md) — where step events and the materialized run go after execution