jobctrl 0.0.1__tar.gz → 2.0.7__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 (570) hide show
  1. jobctrl-2.0.7/.gitignore +66 -0
  2. jobctrl-2.0.7/PKG-INFO +81 -0
  3. jobctrl-2.0.7/README.md +29 -0
  4. jobctrl-2.0.7/pyproject.toml +130 -0
  5. jobctrl-2.0.7/src/jobctrl/__init__.py +3 -0
  6. jobctrl-2.0.7/src/jobctrl/__main__.py +5 -0
  7. jobctrl-2.0.7/src/jobctrl/actions.py +363 -0
  8. jobctrl-2.0.7/src/jobctrl/apply/__init__.py +1 -0
  9. jobctrl-2.0.7/src/jobctrl/apply/activities.py +145 -0
  10. jobctrl-2.0.7/src/jobctrl/apply/auto_apply.py +179 -0
  11. jobctrl-2.0.7/src/jobctrl/apply/chrome.py +1187 -0
  12. jobctrl-2.0.7/src/jobctrl/apply/dashboard.py +224 -0
  13. jobctrl-2.0.7/src/jobctrl/apply/launcher.py +2149 -0
  14. jobctrl-2.0.7/src/jobctrl/apply/prompt.py +511 -0
  15. jobctrl-2.0.7/src/jobctrl/apply/telemetry.py +110 -0
  16. jobctrl-2.0.7/src/jobctrl/apply/workflow.py +206 -0
  17. jobctrl-2.0.7/src/jobctrl/assets/fonts/GEIST-LICENSE.txt +93 -0
  18. jobctrl-2.0.7/src/jobctrl/assets/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  19. jobctrl-2.0.7/src/jobctrl/assets/fonts/geist-latin-wght-normal.woff2 +0 -0
  20. jobctrl-2.0.7/src/jobctrl/browser_capabilities.py +1022 -0
  21. jobctrl-2.0.7/src/jobctrl/cli.py +3370 -0
  22. jobctrl-2.0.7/src/jobctrl/config/employers.yaml +305 -0
  23. jobctrl-2.0.7/src/jobctrl/config/sites.yaml +183 -0
  24. jobctrl-2.0.7/src/jobctrl/config.py +2053 -0
  25. jobctrl-2.0.7/src/jobctrl/contact/__init__.py +6 -0
  26. jobctrl-2.0.7/src/jobctrl/contact/activities.py +169 -0
  27. jobctrl-2.0.7/src/jobctrl/contact/workflow.py +203 -0
  28. jobctrl-2.0.7/src/jobctrl/database.py +4257 -0
  29. jobctrl-2.0.7/src/jobctrl/digest.py +729 -0
  30. jobctrl-2.0.7/src/jobctrl/discovery/__init__.py +0 -0
  31. jobctrl-2.0.7/src/jobctrl/discovery/activities.py +648 -0
  32. jobctrl-2.0.7/src/jobctrl/discovery/execution_reconciliation.py +1385 -0
  33. jobctrl-2.0.7/src/jobctrl/discovery/jobspy.py +2032 -0
  34. jobctrl-2.0.7/src/jobctrl/discovery/manual_capture_import.py +102 -0
  35. jobctrl-2.0.7/src/jobctrl/discovery/manual_capture_workflow.py +399 -0
  36. jobctrl-2.0.7/src/jobctrl/discovery/role_title_matcher.py +269 -0
  37. jobctrl-2.0.7/src/jobctrl/discovery/smartextract.py +1653 -0
  38. jobctrl-2.0.7/src/jobctrl/discovery/target_queries.py +618 -0
  39. jobctrl-2.0.7/src/jobctrl/discovery/title_filter.py +681 -0
  40. jobctrl-2.0.7/src/jobctrl/discovery/workday.py +938 -0
  41. jobctrl-2.0.7/src/jobctrl/discovery/workflow.py +600 -0
  42. jobctrl-2.0.7/src/jobctrl/domain/__init__.py +61 -0
  43. jobctrl-2.0.7/src/jobctrl/domain/apply/__init__.py +52 -0
  44. jobctrl-2.0.7/src/jobctrl/domain/apply/aggregate.py +477 -0
  45. jobctrl-2.0.7/src/jobctrl/domain/apply/entities.py +69 -0
  46. jobctrl-2.0.7/src/jobctrl/domain/apply/process_manager.py +701 -0
  47. jobctrl-2.0.7/src/jobctrl/domain/apply/repeat_application.py +643 -0
  48. jobctrl-2.0.7/src/jobctrl/domain/apply/services.py +361 -0
  49. jobctrl-2.0.7/src/jobctrl/domain/apply/use_cases.py +657 -0
  50. jobctrl-2.0.7/src/jobctrl/domain/apply/value_objects.py +297 -0
  51. jobctrl-2.0.7/src/jobctrl/domain/compensation/__init__.py +57 -0
  52. jobctrl-2.0.7/src/jobctrl/domain/compensation/market.py +1523 -0
  53. jobctrl-2.0.7/src/jobctrl/domain/compensation/posted.py +508 -0
  54. jobctrl-2.0.7/src/jobctrl/domain/contact/__init__.py +161 -0
  55. jobctrl-2.0.7/src/jobctrl/domain/contact/aggregate.py +135 -0
  56. jobctrl-2.0.7/src/jobctrl/domain/contact/outreach.py +634 -0
  57. jobctrl-2.0.7/src/jobctrl/domain/contact/outreach_gates.py +606 -0
  58. jobctrl-2.0.7/src/jobctrl/domain/contact/outreach_use_cases.py +619 -0
  59. jobctrl-2.0.7/src/jobctrl/domain/contact/research.py +304 -0
  60. jobctrl-2.0.7/src/jobctrl/domain/contact/research_services.py +321 -0
  61. jobctrl-2.0.7/src/jobctrl/domain/contact/research_use_cases.py +164 -0
  62. jobctrl-2.0.7/src/jobctrl/domain/contact/source_policy.py +289 -0
  63. jobctrl-2.0.7/src/jobctrl/domain/contact/use_cases.py +264 -0
  64. jobctrl-2.0.7/src/jobctrl/domain/contact/value_objects.py +150 -0
  65. jobctrl-2.0.7/src/jobctrl/domain/discovery/__init__.py +134 -0
  66. jobctrl-2.0.7/src/jobctrl/domain/discovery/aggregate.py +179 -0
  67. jobctrl-2.0.7/src/jobctrl/domain/discovery/execution.py +130 -0
  68. jobctrl-2.0.7/src/jobctrl/domain/discovery/identity.py +254 -0
  69. jobctrl-2.0.7/src/jobctrl/domain/discovery/scheduler.py +424 -0
  70. jobctrl-2.0.7/src/jobctrl/domain/discovery/search_units.py +202 -0
  71. jobctrl-2.0.7/src/jobctrl/domain/discovery/source_registry.py +352 -0
  72. jobctrl-2.0.7/src/jobctrl/domain/discovery/use_cases.py +830 -0
  73. jobctrl-2.0.7/src/jobctrl/domain/discovery/value_objects.py +204 -0
  74. jobctrl-2.0.7/src/jobctrl/domain/enrichment/__init__.py +75 -0
  75. jobctrl-2.0.7/src/jobctrl/domain/enrichment/aggregate.py +388 -0
  76. jobctrl-2.0.7/src/jobctrl/domain/enrichment/entities.py +156 -0
  77. jobctrl-2.0.7/src/jobctrl/domain/enrichment/filter_override.py +119 -0
  78. jobctrl-2.0.7/src/jobctrl/domain/enrichment/services.py +360 -0
  79. jobctrl-2.0.7/src/jobctrl/domain/enrichment/snapshot_services.py +658 -0
  80. jobctrl-2.0.7/src/jobctrl/domain/enrichment/snapshot_set.py +401 -0
  81. jobctrl-2.0.7/src/jobctrl/domain/enrichment/snapshot_use_case.py +539 -0
  82. jobctrl-2.0.7/src/jobctrl/domain/enrichment/snapshot_value_objects.py +417 -0
  83. jobctrl-2.0.7/src/jobctrl/domain/enrichment/use_cases.py +435 -0
  84. jobctrl-2.0.7/src/jobctrl/domain/enrichment/value_objects.py +174 -0
  85. jobctrl-2.0.7/src/jobctrl/domain/errors.py +73 -0
  86. jobctrl-2.0.7/src/jobctrl/domain/events/__init__.py +486 -0
  87. jobctrl-2.0.7/src/jobctrl/domain/events/apply.py +109 -0
  88. jobctrl-2.0.7/src/jobctrl/domain/events/base.py +41 -0
  89. jobctrl-2.0.7/src/jobctrl/domain/events/contact.py +328 -0
  90. jobctrl-2.0.7/src/jobctrl/domain/events/discovery.py +317 -0
  91. jobctrl-2.0.7/src/jobctrl/domain/events/enrichment.py +150 -0
  92. jobctrl-2.0.7/src/jobctrl/domain/events/interview.py +46 -0
  93. jobctrl-2.0.7/src/jobctrl/domain/events/materials.py +115 -0
  94. jobctrl-2.0.7/src/jobctrl/domain/events/operations.py +307 -0
  95. jobctrl-2.0.7/src/jobctrl/domain/events/orchestration.py +107 -0
  96. jobctrl-2.0.7/src/jobctrl/domain/events/profile.py +32 -0
  97. jobctrl-2.0.7/src/jobctrl/domain/events/scoring.py +57 -0
  98. jobctrl-2.0.7/src/jobctrl/domain/events/workflow.py +215 -0
  99. jobctrl-2.0.7/src/jobctrl/domain/extraction.py +54 -0
  100. jobctrl-2.0.7/src/jobctrl/domain/identifiers.py +25 -0
  101. jobctrl-2.0.7/src/jobctrl/domain/interview/__init__.py +23 -0
  102. jobctrl-2.0.7/src/jobctrl/domain/interview/use_cases.py +718 -0
  103. jobctrl-2.0.7/src/jobctrl/domain/interview/value_objects.py +198 -0
  104. jobctrl-2.0.7/src/jobctrl/domain/job_content_identity.py +230 -0
  105. jobctrl-2.0.7/src/jobctrl/domain/materials/__init__.py +42 -0
  106. jobctrl-2.0.7/src/jobctrl/domain/materials/adversarial.py +410 -0
  107. jobctrl-2.0.7/src/jobctrl/domain/materials/aggregate.py +531 -0
  108. jobctrl-2.0.7/src/jobctrl/domain/materials/analysis.py +432 -0
  109. jobctrl-2.0.7/src/jobctrl/domain/materials/analysis_eeo_screen.py +207 -0
  110. jobctrl-2.0.7/src/jobctrl/domain/materials/analysis_grounding.py +338 -0
  111. jobctrl-2.0.7/src/jobctrl/domain/materials/analyze_use_case.py +276 -0
  112. jobctrl-2.0.7/src/jobctrl/domain/materials/claim_grounding.py +310 -0
  113. jobctrl-2.0.7/src/jobctrl/domain/materials/coverage_audit.py +240 -0
  114. jobctrl-2.0.7/src/jobctrl/domain/materials/entities.py +244 -0
  115. jobctrl-2.0.7/src/jobctrl/domain/materials/fabrication_detector.py +1004 -0
  116. jobctrl-2.0.7/src/jobctrl/domain/materials/policy.py +546 -0
  117. jobctrl-2.0.7/src/jobctrl/domain/materials/provenance.py +206 -0
  118. jobctrl-2.0.7/src/jobctrl/domain/materials/provenance_builder.py +465 -0
  119. jobctrl-2.0.7/src/jobctrl/domain/materials/quality.py +1558 -0
  120. jobctrl-2.0.7/src/jobctrl/domain/materials/requirement_coverage.py +1460 -0
  121. jobctrl-2.0.7/src/jobctrl/domain/materials/services.py +654 -0
  122. jobctrl-2.0.7/src/jobctrl/domain/materials/use_cases.py +3763 -0
  123. jobctrl-2.0.7/src/jobctrl/domain/materials/value_objects.py +486 -0
  124. jobctrl-2.0.7/src/jobctrl/domain/materials/voice.py +237 -0
  125. jobctrl-2.0.7/src/jobctrl/domain/materials/voice_metrics.py +236 -0
  126. jobctrl-2.0.7/src/jobctrl/domain/operations/__init__.py +45 -0
  127. jobctrl-2.0.7/src/jobctrl/domain/operations/evidence_map.py +317 -0
  128. jobctrl-2.0.7/src/jobctrl/domain/operations/projections.py +412 -0
  129. jobctrl-2.0.7/src/jobctrl/domain/pipeline/__init__.py +20 -0
  130. jobctrl-2.0.7/src/jobctrl/domain/pipeline/aggregate.py +66 -0
  131. jobctrl-2.0.7/src/jobctrl/domain/pipeline/repository.py +41 -0
  132. jobctrl-2.0.7/src/jobctrl/domain/pipeline/state_machine.py +275 -0
  133. jobctrl-2.0.7/src/jobctrl/domain/pipeline/use_cases.py +113 -0
  134. jobctrl-2.0.7/src/jobctrl/domain/pipeline_types.py +177 -0
  135. jobctrl-2.0.7/src/jobctrl/domain/ports/__init__.py +5 -0
  136. jobctrl-2.0.7/src/jobctrl/domain/ports/apply.py +224 -0
  137. jobctrl-2.0.7/src/jobctrl/domain/ports/contact.py +125 -0
  138. jobctrl-2.0.7/src/jobctrl/domain/ports/discovery.py +326 -0
  139. jobctrl-2.0.7/src/jobctrl/domain/ports/enrichment.py +90 -0
  140. jobctrl-2.0.7/src/jobctrl/domain/ports/events.py +44 -0
  141. jobctrl-2.0.7/src/jobctrl/domain/ports/llm.py +91 -0
  142. jobctrl-2.0.7/src/jobctrl/domain/ports/materials.py +416 -0
  143. jobctrl-2.0.7/src/jobctrl/domain/ports/politeness.py +258 -0
  144. jobctrl-2.0.7/src/jobctrl/domain/ports/preparation.py +33 -0
  145. jobctrl-2.0.7/src/jobctrl/domain/ports/retrieval.py +48 -0
  146. jobctrl-2.0.7/src/jobctrl/domain/ports/scoring.py +142 -0
  147. jobctrl-2.0.7/src/jobctrl/domain/ports/watermark.py +38 -0
  148. jobctrl-2.0.7/src/jobctrl/domain/preparation/__init__.py +15 -0
  149. jobctrl-2.0.7/src/jobctrl/domain/preparation/work_items.py +149 -0
  150. jobctrl-2.0.7/src/jobctrl/domain/profile/__init__.py +63 -0
  151. jobctrl-2.0.7/src/jobctrl/domain/profile/aggregate.py +275 -0
  152. jobctrl-2.0.7/src/jobctrl/domain/profile/ports.py +101 -0
  153. jobctrl-2.0.7/src/jobctrl/domain/profile/snapshot.py +145 -0
  154. jobctrl-2.0.7/src/jobctrl/domain/profile/use_cases.py +76 -0
  155. jobctrl-2.0.7/src/jobctrl/domain/profile/value_objects.py +693 -0
  156. jobctrl-2.0.7/src/jobctrl/domain/rpc/__init__.py +26 -0
  157. jobctrl-2.0.7/src/jobctrl/domain/rpc/messages.py +127 -0
  158. jobctrl-2.0.7/src/jobctrl/domain/scoring/__init__.py +99 -0
  159. jobctrl-2.0.7/src/jobctrl/domain/scoring/aggregate.py +197 -0
  160. jobctrl-2.0.7/src/jobctrl/domain/scoring/policy.py +851 -0
  161. jobctrl-2.0.7/src/jobctrl/domain/scoring/requirement_fit.py +196 -0
  162. jobctrl-2.0.7/src/jobctrl/domain/scoring/retrieval.py +534 -0
  163. jobctrl-2.0.7/src/jobctrl/domain/scoring/services.py +780 -0
  164. jobctrl-2.0.7/src/jobctrl/domain/scoring/use_cases.py +1053 -0
  165. jobctrl-2.0.7/src/jobctrl/domain/scoring/value_objects.py +1361 -0
  166. jobctrl-2.0.7/src/jobctrl/domain/tenant.py +13 -0
  167. jobctrl-2.0.7/src/jobctrl/enrichment/__init__.py +0 -0
  168. jobctrl-2.0.7/src/jobctrl/enrichment/activities.py +200 -0
  169. jobctrl-2.0.7/src/jobctrl/enrichment/detail.py +2499 -0
  170. jobctrl-2.0.7/src/jobctrl/infrastructure/__init__.py +1 -0
  171. jobctrl-2.0.7/src/jobctrl/infrastructure/analysis/__init__.py +41 -0
  172. jobctrl-2.0.7/src/jobctrl/infrastructure/analysis/antigravity_analysis_adapter.py +235 -0
  173. jobctrl-2.0.7/src/jobctrl/infrastructure/analysis/claude_analysis_adapter.py +272 -0
  174. jobctrl-2.0.7/src/jobctrl/infrastructure/analysis/codex_analysis_adapter.py +328 -0
  175. jobctrl-2.0.7/src/jobctrl/infrastructure/analysis/ensemble.py +286 -0
  176. jobctrl-2.0.7/src/jobctrl/infrastructure/analysis/gemini_schema.py +70 -0
  177. jobctrl-2.0.7/src/jobctrl/infrastructure/analysis/llm_analysis_synthesizer.py +51 -0
  178. jobctrl-2.0.7/src/jobctrl/infrastructure/analysis/prompts.py +115 -0
  179. jobctrl-2.0.7/src/jobctrl/infrastructure/analysis/strict_schema.py +69 -0
  180. jobctrl-2.0.7/src/jobctrl/infrastructure/apply/__init__.py +23 -0
  181. jobctrl-2.0.7/src/jobctrl/infrastructure/apply/claude_code_cli.py +624 -0
  182. jobctrl-2.0.7/src/jobctrl/infrastructure/apply/email_sender.py +27 -0
  183. jobctrl-2.0.7/src/jobctrl/infrastructure/apply/local_chrome.py +87 -0
  184. jobctrl-2.0.7/src/jobctrl/infrastructure/apply_tools/__init__.py +2 -0
  185. jobctrl-2.0.7/src/jobctrl/infrastructure/apply_tools/mcp_server.py +849 -0
  186. jobctrl-2.0.7/src/jobctrl/infrastructure/captcha/__init__.py +15 -0
  187. jobctrl-2.0.7/src/jobctrl/infrastructure/captcha/capsolver.py +152 -0
  188. jobctrl-2.0.7/src/jobctrl/infrastructure/compensation/__init__.py +35 -0
  189. jobctrl-2.0.7/src/jobctrl/infrastructure/compensation/levels_fyi_public.py +563 -0
  190. jobctrl-2.0.7/src/jobctrl/infrastructure/compensation/refresh.py +137 -0
  191. jobctrl-2.0.7/src/jobctrl/infrastructure/compensation/sqlite_market_repository.py +1557 -0
  192. jobctrl-2.0.7/src/jobctrl/infrastructure/compensation/sqlite_repository.py +290 -0
  193. jobctrl-2.0.7/src/jobctrl/infrastructure/compensation/workflow.py +198 -0
  194. jobctrl-2.0.7/src/jobctrl/infrastructure/contact/__init__.py +17 -0
  195. jobctrl-2.0.7/src/jobctrl/infrastructure/contact/outreach_repository.py +507 -0
  196. jobctrl-2.0.7/src/jobctrl/infrastructure/contact/research_fetcher.py +251 -0
  197. jobctrl-2.0.7/src/jobctrl/infrastructure/contact/research_repository.py +450 -0
  198. jobctrl-2.0.7/src/jobctrl/infrastructure/contact/sqlite_repository.py +351 -0
  199. jobctrl-2.0.7/src/jobctrl/infrastructure/discovery/__init__.py +76 -0
  200. jobctrl-2.0.7/src/jobctrl/infrastructure/discovery/ats_adapters.py +610 -0
  201. jobctrl-2.0.7/src/jobctrl/infrastructure/discovery/jobstreaming_gateway.py +264 -0
  202. jobctrl-2.0.7/src/jobctrl/infrastructure/discovery/location_filter.py +459 -0
  203. jobctrl-2.0.7/src/jobctrl/infrastructure/discovery/production_wiring.py +2278 -0
  204. jobctrl-2.0.7/src/jobctrl/infrastructure/discovery/sqlite_execution_repository.py +298 -0
  205. jobctrl-2.0.7/src/jobctrl/infrastructure/discovery/sqlite_repository.py +937 -0
  206. jobctrl-2.0.7/src/jobctrl/infrastructure/discovery/sqlite_run_repository.py +186 -0
  207. jobctrl-2.0.7/src/jobctrl/infrastructure/discovery/sqlite_search_unit_repository.py +882 -0
  208. jobctrl-2.0.7/src/jobctrl/infrastructure/enrichment/__init__.py +25 -0
  209. jobctrl-2.0.7/src/jobctrl/infrastructure/enrichment/linkedin_apply_resolver.py +524 -0
  210. jobctrl-2.0.7/src/jobctrl/infrastructure/enrichment/playwright_fetcher.py +341 -0
  211. jobctrl-2.0.7/src/jobctrl/infrastructure/enrichment/sqlite_repository.py +414 -0
  212. jobctrl-2.0.7/src/jobctrl/infrastructure/events/__init__.py +40 -0
  213. jobctrl-2.0.7/src/jobctrl/infrastructure/events/in_process_bus.py +77 -0
  214. jobctrl-2.0.7/src/jobctrl/infrastructure/events/watermark.py +63 -0
  215. jobctrl-2.0.7/src/jobctrl/infrastructure/gmail/__init__.py +5 -0
  216. jobctrl-2.0.7/src/jobctrl/infrastructure/gmail/auth.py +239 -0
  217. jobctrl-2.0.7/src/jobctrl/infrastructure/gmail/client.py +302 -0
  218. jobctrl-2.0.7/src/jobctrl/infrastructure/gmail/feedback.py +956 -0
  219. jobctrl-2.0.7/src/jobctrl/infrastructure/gmail/mcp_server.py +252 -0
  220. jobctrl-2.0.7/src/jobctrl/infrastructure/interview/__init__.py +7 -0
  221. jobctrl-2.0.7/src/jobctrl/infrastructure/interview/sqlite_repository.py +271 -0
  222. jobctrl-2.0.7/src/jobctrl/infrastructure/llm/__init__.py +9 -0
  223. jobctrl-2.0.7/src/jobctrl/infrastructure/llm/llm_client.py +745 -0
  224. jobctrl-2.0.7/src/jobctrl/infrastructure/llm/model_catalog.py +256 -0
  225. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/__init__.py +42 -0
  226. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/bullet_provenance_repository.py +210 -0
  227. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/employer_analysis_repository.py +287 -0
  228. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/html_resume_pdf.py +1038 -0
  229. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/playwright_html_pdf.py +161 -0
  230. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/resume_html_migration.py +522 -0
  231. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/resume_style.py +68 -0
  232. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/sqlite_repository.py +951 -0
  233. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/unit_of_work.py +68 -0
  234. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/voice_adapter.py +199 -0
  235. jobctrl-2.0.7/src/jobctrl/infrastructure/materials/voice_prompts.py +89 -0
  236. jobctrl-2.0.7/src/jobctrl/infrastructure/network/__init__.py +83 -0
  237. jobctrl-2.0.7/src/jobctrl/infrastructure/network/http_client.py +152 -0
  238. jobctrl-2.0.7/src/jobctrl/infrastructure/network/politeness.py +361 -0
  239. jobctrl-2.0.7/src/jobctrl/infrastructure/network/proxy.py +79 -0
  240. jobctrl-2.0.7/src/jobctrl/infrastructure/network/public_http.py +283 -0
  241. jobctrl-2.0.7/src/jobctrl/infrastructure/network/rate_limiter.py +183 -0
  242. jobctrl-2.0.7/src/jobctrl/infrastructure/network/robots.py +155 -0
  243. jobctrl-2.0.7/src/jobctrl/infrastructure/network/url_safety.py +266 -0
  244. jobctrl-2.0.7/src/jobctrl/infrastructure/observability/__init__.py +38 -0
  245. jobctrl-2.0.7/src/jobctrl/infrastructure/observability/adapter_spans.py +117 -0
  246. jobctrl-2.0.7/src/jobctrl/infrastructure/observability/enrichment_spans.py +157 -0
  247. jobctrl-2.0.7/src/jobctrl/infrastructure/observability/llm_spans.py +125 -0
  248. jobctrl-2.0.7/src/jobctrl/infrastructure/observability/otel.py +167 -0
  249. jobctrl-2.0.7/src/jobctrl/infrastructure/observability/source_spans.py +124 -0
  250. jobctrl-2.0.7/src/jobctrl/infrastructure/pipeline/__init__.py +1 -0
  251. jobctrl-2.0.7/src/jobctrl/infrastructure/pipeline/sqlite_repository.py +351 -0
  252. jobctrl-2.0.7/src/jobctrl/infrastructure/preflight.py +73 -0
  253. jobctrl-2.0.7/src/jobctrl/infrastructure/preparation/__init__.py +7 -0
  254. jobctrl-2.0.7/src/jobctrl/infrastructure/preparation/sqlite_repository.py +148 -0
  255. jobctrl-2.0.7/src/jobctrl/infrastructure/profile/__init__.py +17 -0
  256. jobctrl-2.0.7/src/jobctrl/infrastructure/profile/factory.py +80 -0
  257. jobctrl-2.0.7/src/jobctrl/infrastructure/profile/pdf_parser.py +42 -0
  258. jobctrl-2.0.7/src/jobctrl/infrastructure/profile/sqlite_repository.py +1000 -0
  259. jobctrl-2.0.7/src/jobctrl/infrastructure/projections/__init__.py +24 -0
  260. jobctrl-2.0.7/src/jobctrl/infrastructure/projections/location_normalization.py +99 -0
  261. jobctrl-2.0.7/src/jobctrl/infrastructure/projections/projection_builder.py +5015 -0
  262. jobctrl-2.0.7/src/jobctrl/infrastructure/projections/source_quality.py +512 -0
  263. jobctrl-2.0.7/src/jobctrl/infrastructure/projections/sqlite_projection_store.py +1407 -0
  264. jobctrl-2.0.7/src/jobctrl/infrastructure/rpc/__init__.py +1 -0
  265. jobctrl-2.0.7/src/jobctrl/infrastructure/rpc/handlers.py +667 -0
  266. jobctrl-2.0.7/src/jobctrl/infrastructure/rpc/server.py +354 -0
  267. jobctrl-2.0.7/src/jobctrl/infrastructure/rpc/workflow_starter.py +143 -0
  268. jobctrl-2.0.7/src/jobctrl/infrastructure/runtime_identity.py +227 -0
  269. jobctrl-2.0.7/src/jobctrl/infrastructure/scoring/__init__.py +27 -0
  270. jobctrl-2.0.7/src/jobctrl/infrastructure/scoring/criteria_provider.py +147 -0
  271. jobctrl-2.0.7/src/jobctrl/infrastructure/scoring/feedback.py +164 -0
  272. jobctrl-2.0.7/src/jobctrl/infrastructure/scoring/sqlite_repository.py +820 -0
  273. jobctrl-2.0.7/src/jobctrl/infrastructure/setup_probes.py +951 -0
  274. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/__init__.py +7 -0
  275. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/activity_runtime_telemetry.py +378 -0
  276. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/client.py +22 -0
  277. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/concurrency.py +65 -0
  278. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/durability_probe.py +129 -0
  279. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/finalize.py +283 -0
  280. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/pipeline_step_lifecycle.py +294 -0
  281. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/registry.py +93 -0
  282. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/run_in_activity.py +122 -0
  283. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/runtime_guard.py +28 -0
  284. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/task_queue_observation.py +227 -0
  285. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/task_queues.py +7 -0
  286. jobctrl-2.0.7/src/jobctrl/infrastructure/temporal/worker.py +101 -0
  287. jobctrl-2.0.7/src/jobctrl/interview/__init__.py +15 -0
  288. jobctrl-2.0.7/src/jobctrl/interview/activities.py +334 -0
  289. jobctrl-2.0.7/src/jobctrl/interview/workflow.py +175 -0
  290. jobctrl-2.0.7/src/jobctrl/llm.py +744 -0
  291. jobctrl-2.0.7/src/jobctrl/materials/__init__.py +1 -0
  292. jobctrl-2.0.7/src/jobctrl/materials/activities.py +741 -0
  293. jobctrl-2.0.7/src/jobctrl/model_defaults.py +5 -0
  294. jobctrl-2.0.7/src/jobctrl/operational_metrics.py +239 -0
  295. jobctrl-2.0.7/src/jobctrl/pipeline/__init__.py +29 -0
  296. jobctrl-2.0.7/src/jobctrl/pipeline/current_policy_selectors.py +287 -0
  297. jobctrl-2.0.7/src/jobctrl/pipeline/preparation.py +781 -0
  298. jobctrl-2.0.7/src/jobctrl/pipeline/runner.py +2848 -0
  299. jobctrl-2.0.7/src/jobctrl/pipeline/workflow.py +503 -0
  300. jobctrl-2.0.7/src/jobctrl/preparation/__init__.py +15 -0
  301. jobctrl-2.0.7/src/jobctrl/preparation/workflow.py +314 -0
  302. jobctrl-2.0.7/src/jobctrl/profile/__init__.py +1 -0
  303. jobctrl-2.0.7/src/jobctrl/profile/activities.py +70 -0
  304. jobctrl-2.0.7/src/jobctrl/profile/importer.py +26 -0
  305. jobctrl-2.0.7/src/jobctrl/profile/workflow.py +168 -0
  306. jobctrl-2.0.7/src/jobctrl/profile_import.py +621 -0
  307. jobctrl-2.0.7/src/jobctrl/provider_packs.py +690 -0
  308. jobctrl-2.0.7/src/jobctrl/resume_profile.py +539 -0
  309. jobctrl-2.0.7/src/jobctrl/runtime.py +363 -0
  310. jobctrl-2.0.7/src/jobctrl/scoring/__init__.py +1 -0
  311. jobctrl-2.0.7/src/jobctrl/scoring/activities.py +320 -0
  312. jobctrl-2.0.7/src/jobctrl/scoring/cover_letter.py +334 -0
  313. jobctrl-2.0.7/src/jobctrl/scoring/employer_analysis.py +117 -0
  314. jobctrl-2.0.7/src/jobctrl/scoring/eval.py +515 -0
  315. jobctrl-2.0.7/src/jobctrl/scoring/scorer.py +1230 -0
  316. jobctrl-2.0.7/src/jobctrl/scoring/tailor.py +925 -0
  317. jobctrl-2.0.7/src/jobctrl/state.py +1055 -0
  318. jobctrl-2.0.7/src/jobctrl/wizard/__init__.py +0 -0
  319. jobctrl-2.0.7/src/jobctrl/wizard/init.py +582 -0
  320. jobctrl-2.0.7/src/jobctrl/workflow_specs.py +486 -0
  321. jobctrl-2.0.7/tests/__init__.py +0 -0
  322. jobctrl-2.0.7/tests/conftest.py +92 -0
  323. jobctrl-2.0.7/tests/fixtures/discovery_barcelona_acceptance.json +16 -0
  324. jobctrl-2.0.7/tests/fixtures/hybrid_search_topk.json +47 -0
  325. jobctrl-2.0.7/tests/fixtures/resume_tailoring_quality_eval.json +209 -0
  326. jobctrl-2.0.7/tests/fixtures/scoring_eval_cases.json +23 -0
  327. jobctrl-2.0.7/tests/politeness_helpers.py +126 -0
  328. jobctrl-2.0.7/tests/test_actions.py +465 -0
  329. jobctrl-2.0.7/tests/test_activity_apply.py +231 -0
  330. jobctrl-2.0.7/tests/test_activity_cover.py +62 -0
  331. jobctrl-2.0.7/tests/test_activity_enrich.py +62 -0
  332. jobctrl-2.0.7/tests/test_activity_profile_import.py +73 -0
  333. jobctrl-2.0.7/tests/test_activity_runtime_telemetry.py +246 -0
  334. jobctrl-2.0.7/tests/test_activity_score.py +62 -0
  335. jobctrl-2.0.7/tests/test_activity_tailor.py +103 -0
  336. jobctrl-2.0.7/tests/test_analyze_job_use_case.py +326 -0
  337. jobctrl-2.0.7/tests/test_antigravity_adapter.py +331 -0
  338. jobctrl-2.0.7/tests/test_apply_chrome_dry_run_guard.py +1045 -0
  339. jobctrl-2.0.7/tests/test_apply_eligibility_checker.py +81 -0
  340. jobctrl-2.0.7/tests/test_apply_log_artifact_registration.py +386 -0
  341. jobctrl-2.0.7/tests/test_apply_prompt_builder.py +373 -0
  342. jobctrl-2.0.7/tests/test_apply_queue_selectors.py +561 -0
  343. jobctrl-2.0.7/tests/test_apply_regressions.py +1746 -0
  344. jobctrl-2.0.7/tests/test_apply_run_aggregate.py +179 -0
  345. jobctrl-2.0.7/tests/test_apply_runs_projection_from_events.py +302 -0
  346. jobctrl-2.0.7/tests/test_apply_runs_table_dropped.py +76 -0
  347. jobctrl-2.0.7/tests/test_apply_saga.py +587 -0
  348. jobctrl-2.0.7/tests/test_apply_tools_mcp_server.py +720 -0
  349. jobctrl-2.0.7/tests/test_apply_use_cases.py +502 -0
  350. jobctrl-2.0.7/tests/test_ats_adapters.py +340 -0
  351. jobctrl-2.0.7/tests/test_audit_projection_parity.py +731 -0
  352. jobctrl-2.0.7/tests/test_auto_apply_reconciler.py +246 -0
  353. jobctrl-2.0.7/tests/test_autonomous_agent_port.py +84 -0
  354. jobctrl-2.0.7/tests/test_browser_capabilities.py +648 -0
  355. jobctrl-2.0.7/tests/test_browser_capabilities_rpc.py +192 -0
  356. jobctrl-2.0.7/tests/test_browser_capability_cli.py +110 -0
  357. jobctrl-2.0.7/tests/test_browser_port.py +61 -0
  358. jobctrl-2.0.7/tests/test_browser_ua_propagation.py +294 -0
  359. jobctrl-2.0.7/tests/test_bullet_provenance.py +1576 -0
  360. jobctrl-2.0.7/tests/test_bundled_runtime.py +533 -0
  361. jobctrl-2.0.7/tests/test_capsolver_client.py +112 -0
  362. jobctrl-2.0.7/tests/test_claim_grounding.py +233 -0
  363. jobctrl-2.0.7/tests/test_claude_code_cli_adapter.py +543 -0
  364. jobctrl-2.0.7/tests/test_codex_adapter_status.py +87 -0
  365. jobctrl-2.0.7/tests/test_codex_home_isolation.py +420 -0
  366. jobctrl-2.0.7/tests/test_compensation_gateway_routing.py +126 -0
  367. jobctrl-2.0.7/tests/test_compensation_refresh_cli.py +62 -0
  368. jobctrl-2.0.7/tests/test_config_file_transactions.py +107 -0
  369. jobctrl-2.0.7/tests/test_contact_aggregate.py +99 -0
  370. jobctrl-2.0.7/tests/test_contact_projection_parity.py +113 -0
  371. jobctrl-2.0.7/tests/test_contact_provenance.py +60 -0
  372. jobctrl-2.0.7/tests/test_contact_repository.py +230 -0
  373. jobctrl-2.0.7/tests/test_contact_research_projection_parity.py +123 -0
  374. jobctrl-2.0.7/tests/test_contact_research_source_policy.py +169 -0
  375. jobctrl-2.0.7/tests/test_contact_research_workflow.py +513 -0
  376. jobctrl-2.0.7/tests/test_content_validator.py +362 -0
  377. jobctrl-2.0.7/tests/test_cover_requirements.py +131 -0
  378. jobctrl-2.0.7/tests/test_coverage_audit.py +293 -0
  379. jobctrl-2.0.7/tests/test_crawl_politeness_config.py +138 -0
  380. jobctrl-2.0.7/tests/test_criteria_provider.py +56 -0
  381. jobctrl-2.0.7/tests/test_dashboard_execution_policy.py +93 -0
  382. jobctrl-2.0.7/tests/test_dashboard_projection.py +860 -0
  383. jobctrl-2.0.7/tests/test_database_backup.py +212 -0
  384. jobctrl-2.0.7/tests/test_detail_page_fetcher.py +53 -0
  385. jobctrl-2.0.7/tests/test_digest_cli.py +133 -0
  386. jobctrl-2.0.7/tests/test_digest_parity.py +506 -0
  387. jobctrl-2.0.7/tests/test_discover_reliability.py +990 -0
  388. jobctrl-2.0.7/tests/test_discovery_execution_lineage.py +681 -0
  389. jobctrl-2.0.7/tests/test_discovery_execution_reconciliation.py +1932 -0
  390. jobctrl-2.0.7/tests/test_discovery_identity.py +1456 -0
  391. jobctrl-2.0.7/tests/test_discovery_limits.py +1625 -0
  392. jobctrl-2.0.7/tests/test_discovery_location_filter.py +162 -0
  393. jobctrl-2.0.7/tests/test_discovery_preparation_orchestration.py +421 -0
  394. jobctrl-2.0.7/tests/test_discovery_production_wiring.py +1655 -0
  395. jobctrl-2.0.7/tests/test_discovery_scheduler_pr4.py +81 -0
  396. jobctrl-2.0.7/tests/test_discovery_search_units.py +741 -0
  397. jobctrl-2.0.7/tests/test_discovery_title_filter.py +18 -0
  398. jobctrl-2.0.7/tests/test_doctor_gmail_mcp.py +117 -0
  399. jobctrl-2.0.7/tests/test_doctor_langfuse.py +98 -0
  400. jobctrl-2.0.7/tests/test_doctor_playwright.py +133 -0
  401. jobctrl-2.0.7/tests/test_doctor_temporal.py +32 -0
  402. jobctrl-2.0.7/tests/test_domain_event_parity.py +101 -0
  403. jobctrl-2.0.7/tests/test_domain_events.py +565 -0
  404. jobctrl-2.0.7/tests/test_domain_types.py +183 -0
  405. jobctrl-2.0.7/tests/test_due_follow_up_projection_parity.py +101 -0
  406. jobctrl-2.0.7/tests/test_employer_analysis_eeo_screen.py +187 -0
  407. jobctrl-2.0.7/tests/test_employer_analysis_ensemble.py +574 -0
  408. jobctrl-2.0.7/tests/test_employer_analysis_leg_config.py +126 -0
  409. jobctrl-2.0.7/tests/test_employer_analysis_model.py +560 -0
  410. jobctrl-2.0.7/tests/test_employer_analysis_projection.py +251 -0
  411. jobctrl-2.0.7/tests/test_employer_analysis_repository.py +180 -0
  412. jobctrl-2.0.7/tests/test_enrichment_aggregate.py +419 -0
  413. jobctrl-2.0.7/tests/test_enrichment_detail_staleness.py +351 -0
  414. jobctrl-2.0.7/tests/test_enrichment_events_pr3.py +81 -0
  415. jobctrl-2.0.7/tests/test_enrichment_extractors.py +226 -0
  416. jobctrl-2.0.7/tests/test_enrichment_politeness_gate.py +699 -0
  417. jobctrl-2.0.7/tests/test_enrichment_quarantine_gating.py +466 -0
  418. jobctrl-2.0.7/tests/test_enrichment_queue_selectors.py +443 -0
  419. jobctrl-2.0.7/tests/test_enrichment_repository.py +341 -0
  420. jobctrl-2.0.7/tests/test_enrichment_snapshot_pr3.py +626 -0
  421. jobctrl-2.0.7/tests/test_enrichment_url_safety.py +457 -0
  422. jobctrl-2.0.7/tests/test_enrichment_use_cases.py +328 -0
  423. jobctrl-2.0.7/tests/test_event_publication.py +86 -0
  424. jobctrl-2.0.7/tests/test_event_type_migration.py +80 -0
  425. jobctrl-2.0.7/tests/test_event_type_rename.py +49 -0
  426. jobctrl-2.0.7/tests/test_event_watermark.py +113 -0
  427. jobctrl-2.0.7/tests/test_evidence_interview_value_objects.py +142 -0
  428. jobctrl-2.0.7/tests/test_fetch_surface_enforcement.py +265 -0
  429. jobctrl-2.0.7/tests/test_gateway_http_client.py +294 -0
  430. jobctrl-2.0.7/tests/test_gemini_schema.py +114 -0
  431. jobctrl-2.0.7/tests/test_gmail_connector.py +492 -0
  432. jobctrl-2.0.7/tests/test_gmail_feedback.py +493 -0
  433. jobctrl-2.0.7/tests/test_gmail_mcp_config.py +112 -0
  434. jobctrl-2.0.7/tests/test_hybrid_search_index.py +151 -0
  435. jobctrl-2.0.7/tests/test_in_process_bus.py +84 -0
  436. jobctrl-2.0.7/tests/test_interview_prep_generation.py +617 -0
  437. jobctrl-2.0.7/tests/test_job_aggregate.py +282 -0
  438. jobctrl-2.0.7/tests/test_job_list_projection.py +559 -0
  439. jobctrl-2.0.7/tests/test_job_repository.py +283 -0
  440. jobctrl-2.0.7/tests/test_jobspy_gateway_boundary.py +112 -0
  441. jobctrl-2.0.7/tests/test_jobstreaming_gateway.py +227 -0
  442. jobctrl-2.0.7/tests/test_jobstreaming_resumable_discovery.py +953 -0
  443. jobctrl-2.0.7/tests/test_jsonrpc_handlers.py +1763 -0
  444. jobctrl-2.0.7/tests/test_jsonrpc_server.py +213 -0
  445. jobctrl-2.0.7/tests/test_keychain_credentials.py +288 -0
  446. jobctrl-2.0.7/tests/test_levels_fyi_public.py +142 -0
  447. jobctrl-2.0.7/tests/test_linkedin_apply_resolver.py +298 -0
  448. jobctrl-2.0.7/tests/test_linkedin_authenticated_enrichment_retry.py +462 -0
  449. jobctrl-2.0.7/tests/test_llm_analysis_synthesizer.py +51 -0
  450. jobctrl-2.0.7/tests/test_llm_client.py +382 -0
  451. jobctrl-2.0.7/tests/test_llm_defaults.py +31 -0
  452. jobctrl-2.0.7/tests/test_llm_no_credential_leakage.py +88 -0
  453. jobctrl-2.0.7/tests/test_llm_port.py +362 -0
  454. jobctrl-2.0.7/tests/test_llm_provider_routing.py +348 -0
  455. jobctrl-2.0.7/tests/test_llm_spans.py +128 -0
  456. jobctrl-2.0.7/tests/test_llm_spend_budget.py +88 -0
  457. jobctrl-2.0.7/tests/test_location_normalization.py +37 -0
  458. jobctrl-2.0.7/tests/test_manual_capture_workflow.py +503 -0
  459. jobctrl-2.0.7/tests/test_market_compensation_estimator.py +501 -0
  460. jobctrl-2.0.7/tests/test_market_compensation_repository.py +973 -0
  461. jobctrl-2.0.7/tests/test_materials_adversarial.py +61 -0
  462. jobctrl-2.0.7/tests/test_materials_aggregate.py +339 -0
  463. jobctrl-2.0.7/tests/test_materials_quality.py +741 -0
  464. jobctrl-2.0.7/tests/test_materials_quality_eval.py +177 -0
  465. jobctrl-2.0.7/tests/test_materials_queue_selectors.py +797 -0
  466. jobctrl-2.0.7/tests/test_materials_repository.py +919 -0
  467. jobctrl-2.0.7/tests/test_materials_unit_of_work.py +348 -0
  468. jobctrl-2.0.7/tests/test_materials_use_cases.py +2679 -0
  469. jobctrl-2.0.7/tests/test_model_catalog.py +154 -0
  470. jobctrl-2.0.7/tests/test_operational_metrics.py +241 -0
  471. jobctrl-2.0.7/tests/test_otel_init.py +170 -0
  472. jobctrl-2.0.7/tests/test_outreach_draft_gates.py +449 -0
  473. jobctrl-2.0.7/tests/test_outreach_follow_up_derivation.py +177 -0
  474. jobctrl-2.0.7/tests/test_outreach_no_auto_send.py +213 -0
  475. jobctrl-2.0.7/tests/test_outreach_no_send_transport.py +71 -0
  476. jobctrl-2.0.7/tests/test_outreach_projection_parity.py +129 -0
  477. jobctrl-2.0.7/tests/test_outreach_thread_aggregate.py +290 -0
  478. jobctrl-2.0.7/tests/test_p1b_error_inversion.py +414 -0
  479. jobctrl-2.0.7/tests/test_pdf_renderer_ports.py +521 -0
  480. jobctrl-2.0.7/tests/test_pipeline_observability.py +773 -0
  481. jobctrl-2.0.7/tests/test_pipeline_repository.py +495 -0
  482. jobctrl-2.0.7/tests/test_pipeline_status_cli.py +12 -0
  483. jobctrl-2.0.7/tests/test_pipeline_step_events.py +133 -0
  484. jobctrl-2.0.7/tests/test_pipeline_step_lifecycle_emission.py +527 -0
  485. jobctrl-2.0.7/tests/test_pipeline_step_projection.py +146 -0
  486. jobctrl-2.0.7/tests/test_pipeline_use_cases.py +202 -0
  487. jobctrl-2.0.7/tests/test_politeness_gateway.py +511 -0
  488. jobctrl-2.0.7/tests/test_politeness_ports.py +76 -0
  489. jobctrl-2.0.7/tests/test_posted_compensation_parser.py +214 -0
  490. jobctrl-2.0.7/tests/test_posted_compensation_repository.py +171 -0
  491. jobctrl-2.0.7/tests/test_preflight.py +225 -0
  492. jobctrl-2.0.7/tests/test_preparation_work_items.py +76 -0
  493. jobctrl-2.0.7/tests/test_profile_aggregate.py +425 -0
  494. jobctrl-2.0.7/tests/test_profile_import.py +190 -0
  495. jobctrl-2.0.7/tests/test_profile_snapshot.py +92 -0
  496. jobctrl-2.0.7/tests/test_profile_use_cases.py +121 -0
  497. jobctrl-2.0.7/tests/test_projection_builder.py +1022 -0
  498. jobctrl-2.0.7/tests/test_provider_packs.py +525 -0
  499. jobctrl-2.0.7/tests/test_public_destination_http.py +126 -0
  500. jobctrl-2.0.7/tests/test_python_package_attribution.py +44 -0
  501. jobctrl-2.0.7/tests/test_release_check.py +1304 -0
  502. jobctrl-2.0.7/tests/test_repeat_application_prevention.py +751 -0
  503. jobctrl-2.0.7/tests/test_requirement_fit_resolution.py +192 -0
  504. jobctrl-2.0.7/tests/test_requirement_led_tailoring.py +1227 -0
  505. jobctrl-2.0.7/tests/test_resume_assembler.py +152 -0
  506. jobctrl-2.0.7/tests/test_resume_html_migration.py +301 -0
  507. jobctrl-2.0.7/tests/test_role_title_matcher.py +116 -0
  508. jobctrl-2.0.7/tests/test_rpc_cancel_run.py +49 -0
  509. jobctrl-2.0.7/tests/test_rpc_handlers_apply_workflow.py +155 -0
  510. jobctrl-2.0.7/tests/test_rpc_otel.py +188 -0
  511. jobctrl-2.0.7/tests/test_rpc_server_workflow_mode.py +104 -0
  512. jobctrl-2.0.7/tests/test_runtime_identity.py +243 -0
  513. jobctrl-2.0.7/tests/test_score_aggregate.py +444 -0
  514. jobctrl-2.0.7/tests/test_score_eligibility_stage_state.py +98 -0
  515. jobctrl-2.0.7/tests/test_score_queue_selectors.py +494 -0
  516. jobctrl-2.0.7/tests/test_score_repository.py +792 -0
  517. jobctrl-2.0.7/tests/test_score_use_cases.py +1342 -0
  518. jobctrl-2.0.7/tests/test_scorer.py +1213 -0
  519. jobctrl-2.0.7/tests/test_scoring_eval.py +138 -0
  520. jobctrl-2.0.7/tests/test_scoring_eval_feedback.py +111 -0
  521. jobctrl-2.0.7/tests/test_scoring_policy.py +297 -0
  522. jobctrl-2.0.7/tests/test_setup_probes.py +680 -0
  523. jobctrl-2.0.7/tests/test_setup_synthesis_auth.py +286 -0
  524. jobctrl-2.0.7/tests/test_smartextract_discovery.py +802 -0
  525. jobctrl-2.0.7/tests/test_smartextract_resilience.py +115 -0
  526. jobctrl-2.0.7/tests/test_source_quality_projection_pr4.py +259 -0
  527. jobctrl-2.0.7/tests/test_source_registry.py +503 -0
  528. jobctrl-2.0.7/tests/test_source_spans.py +83 -0
  529. jobctrl-2.0.7/tests/test_sqlite_profile_repository.py +291 -0
  530. jobctrl-2.0.7/tests/test_stage_runner_thread_safety.py +135 -0
  531. jobctrl-2.0.7/tests/test_stage_state_machine.py +297 -0
  532. jobctrl-2.0.7/tests/test_state_dashboard.py +426 -0
  533. jobctrl-2.0.7/tests/test_state_legacy_parity.py +216 -0
  534. jobctrl-2.0.7/tests/test_state_machine_parity.py +180 -0
  535. jobctrl-2.0.7/tests/test_strict_schema.py +131 -0
  536. jobctrl-2.0.7/tests/test_tailor_provenance_integration.py +878 -0
  537. jobctrl-2.0.7/tests/test_tailor_retailor.py +690 -0
  538. jobctrl-2.0.7/tests/test_tailor_voice_audit_integration.py +763 -0
  539. jobctrl-2.0.7/tests/test_target_search_preferences.py +660 -0
  540. jobctrl-2.0.7/tests/test_task_queue_observation.py +121 -0
  541. jobctrl-2.0.7/tests/test_temporal_client.py +49 -0
  542. jobctrl-2.0.7/tests/test_temporal_concurrency.py +52 -0
  543. jobctrl-2.0.7/tests/test_temporal_registry.py +28 -0
  544. jobctrl-2.0.7/tests/test_temporal_worker.py +51 -0
  545. jobctrl-2.0.7/tests/test_title_filter.py +286 -0
  546. jobctrl-2.0.7/tests/test_voice_adapter.py +203 -0
  547. jobctrl-2.0.7/tests/test_voice_metrics.py +106 -0
  548. jobctrl-2.0.7/tests/test_voice_payload.py +89 -0
  549. jobctrl-2.0.7/tests/test_workday_discovery.py +376 -0
  550. jobctrl-2.0.7/tests/test_workday_gateway_routing.py +257 -0
  551. jobctrl-2.0.7/tests/test_worker_heartbeat_loop.py +331 -0
  552. jobctrl-2.0.7/tests/test_worker_reconciler.py +298 -0
  553. jobctrl-2.0.7/tests/test_workflow_apply.py +319 -0
  554. jobctrl-2.0.7/tests/test_workflow_discovery.py +1180 -0
  555. jobctrl-2.0.7/tests/test_workflow_durability_probe.py +173 -0
  556. jobctrl-2.0.7/tests/test_workflow_finalize.py +248 -0
  557. jobctrl-2.0.7/tests/test_workflow_heavy_rpc_conversions.py +207 -0
  558. jobctrl-2.0.7/tests/test_workflow_id_overlap.py +75 -0
  559. jobctrl-2.0.7/tests/test_workflow_job_pipeline.py +595 -0
  560. jobctrl-2.0.7/tests/test_workflow_job_preparation.py +439 -0
  561. jobctrl-2.0.7/tests/test_workflow_runs_projection_from_events.py +990 -0
  562. jobctrl-2.0.7/tests/test_workflow_starter_cache.py +172 -0
  563. jobctrl-2.0.7/tests/test_workspace_migration.py +265 -0
  564. jobctrl-2.0.7/uv.lock +2008 -0
  565. jobctrl-0.0.1/PKG-INFO +0 -31
  566. jobctrl-0.0.1/PYPI-MARKER.md +0 -12
  567. jobctrl-0.0.1/pyproject.toml +0 -38
  568. jobctrl-0.0.1/src/jobctrl/__init__.py +0 -6
  569. {jobctrl-0.0.1 → jobctrl-2.0.7}/LICENSE +0 -0
  570. {jobctrl-0.0.1 → jobctrl-2.0.7}/NOTICE +0 -0
@@ -0,0 +1,66 @@
1
+ # User data (NEVER commit)
2
+ profile.json
3
+ resume.txt
4
+ resume.pdf
5
+ *.env
6
+ .env.*
7
+ !.env.example
8
+
9
+ # Runtime artifacts
10
+ .jobctrl-e2e-state.json
11
+ *.db
12
+ *.db-shm
13
+ *.db-wal
14
+ *.db-journal
15
+ .runtime_tailor_test/
16
+ tailored_resumes/
17
+ cover_letters/
18
+ chrome-workers/
19
+ apply-workers/
20
+ apply_logs/
21
+ logs/
22
+ .dev/
23
+
24
+ # Local design and planning scratchpads
25
+ handoff_from_design/
26
+ docs/superpowers/
27
+ .planning/
28
+
29
+ # MCP configs (per-machine)
30
+ .mcp*.json
31
+
32
+ # Python
33
+ __pycache__/
34
+ .pytest_cache/
35
+ .ruff_cache/
36
+ *.py[cod]
37
+ *$py.class
38
+ *.egg-info/
39
+ dist/
40
+ build/
41
+ *.egg
42
+
43
+ # Node
44
+ node_modules/
45
+
46
+ # IDE
47
+ .vscode/
48
+ .idea/
49
+ *.swp
50
+ *.swo
51
+
52
+ # OS
53
+ .DS_Store
54
+ Thumbs.db
55
+
56
+ # Claude Code
57
+ .claude/
58
+ .codex/
59
+ .venv/
60
+ .worktrees/
61
+
62
+ # VitePress docs site
63
+ docs/.vitepress/dist/
64
+ docs/.vitepress/cache/
65
+
66
+ # Generated by @tanstack/router-plugin
jobctrl-2.0.7/PKG-INFO ADDED
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: jobctrl
3
+ Version: 2.0.7
4
+ Summary: AI-powered end-to-end job application pipeline
5
+ Project-URL: Homepage, https://github.com/ebarti/JobCtrl
6
+ Project-URL: Repository, https://github.com/ebarti/JobCtrl
7
+ Project-URL: Issues, https://github.com/ebarti/JobCtrl/issues
8
+ Author: Eloi Barti
9
+ License-Expression: AGPL-3.0-only
10
+ Keywords: ai,apply,automation,job-application,job-search,resume
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Office/Business
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: beautifulsoup4>=4.12
20
+ Requires-Dist: httpx>=0.24
21
+ Requires-Dist: jobstreaming==0.0.2
22
+ Requires-Dist: opentelemetry-api>=1.27
23
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27
24
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.48b0
25
+ Requires-Dist: opentelemetry-sdk>=1.27
26
+ Requires-Dist: pandas>=2.0
27
+ Requires-Dist: playwright>=1.40
28
+ Requires-Dist: publicsuffix2<3,>=2.20191221
29
+ Requires-Dist: pydantic>=2.10
30
+ Requires-Dist: pypdf>=5.0
31
+ Requires-Dist: python-dotenv>=1.0
32
+ Requires-Dist: pyyaml>=6.0
33
+ Requires-Dist: rich>=13.0
34
+ Requires-Dist: temporalio>=1.6
35
+ Requires-Dist: typer>=0.9.0
36
+ Requires-Dist: websocket-client>=1.8
37
+ Provides-Extra: dev
38
+ Requires-Dist: build>=1.2; extra == 'dev'
39
+ Requires-Dist: cryptography>=43.0; extra == 'dev'
40
+ Requires-Dist: google-auth>=2.0; extra == 'dev'
41
+ Requires-Dist: hatchling==1.29.0; extra == 'dev'
42
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
43
+ Requires-Dist: pytest-randomly>=4.0; extra == 'dev'
44
+ Requires-Dist: pytest>=7.0; extra == 'dev'
45
+ Requires-Dist: ruff==0.15.8; extra == 'dev'
46
+ Provides-Extra: providers
47
+ Requires-Dist: claude-agent-sdk==0.2.115; extra == 'providers'
48
+ Requires-Dist: google-antigravity==0.1.2; extra == 'providers'
49
+ Requires-Dist: openai-codex-cli-bin==0.137.0a4; extra == 'providers'
50
+ Requires-Dist: openai-codex==0.1.0b3; extra == 'providers'
51
+ Description-Content-Type: text/markdown
52
+
53
+ # JobCtrl Automation Worker
54
+
55
+ This package contains JobCtrl's Python automation engine and CLI. It owns job
56
+ discovery, enrichment, scoring, tailoring, cover letters, PDF generation,
57
+ profile import, and local browser apply automation.
58
+
59
+ Use it from the repository root through `uv`:
60
+
61
+ ```bash
62
+ uv --project workers/automation run jobctrl doctor
63
+ uv --project workers/automation run jobctrl run
64
+ uv --project workers/automation run jobctrl worker
65
+ ```
66
+
67
+ The full local application is normally started from the repository root with
68
+ `pnpm dev`, which runs Temporal, the TypeScript API, the React web app, and this
69
+ worker together.
70
+
71
+ Useful docs:
72
+
73
+ - root `README.md` for the public overview and quick start;
74
+ - `docs/user/getting-started.md` for setup and local stack commands;
75
+ - `docs/user/configuration.md` for environment variables;
76
+ - `docs/architecture/` and `docs/architecture/pipeline/` for internal
77
+ runtime ownership.
78
+
79
+ Profile data, generated materials, browser state, logs, and SQLite databases
80
+ are sensitive local artifacts. Do not commit them or include them in public bug
81
+ reports.
@@ -0,0 +1,29 @@
1
+ # JobCtrl Automation Worker
2
+
3
+ This package contains JobCtrl's Python automation engine and CLI. It owns job
4
+ discovery, enrichment, scoring, tailoring, cover letters, PDF generation,
5
+ profile import, and local browser apply automation.
6
+
7
+ Use it from the repository root through `uv`:
8
+
9
+ ```bash
10
+ uv --project workers/automation run jobctrl doctor
11
+ uv --project workers/automation run jobctrl run
12
+ uv --project workers/automation run jobctrl worker
13
+ ```
14
+
15
+ The full local application is normally started from the repository root with
16
+ `pnpm dev`, which runs Temporal, the TypeScript API, the React web app, and this
17
+ worker together.
18
+
19
+ Useful docs:
20
+
21
+ - root `README.md` for the public overview and quick start;
22
+ - `docs/user/getting-started.md` for setup and local stack commands;
23
+ - `docs/user/configuration.md` for environment variables;
24
+ - `docs/architecture/` and `docs/architecture/pipeline/` for internal
25
+ runtime ownership.
26
+
27
+ Profile data, generated materials, browser state, logs, and SQLite databases
28
+ are sensitive local artifacts. Do not commit them or include them in public bug
29
+ reports.
@@ -0,0 +1,130 @@
1
+ [project]
2
+ name = "jobctrl"
3
+ version = "2.0.7"
4
+ description = "AI-powered end-to-end job application pipeline"
5
+ readme = "README.md"
6
+ license = "AGPL-3.0-only"
7
+ requires-python = ">=3.11"
8
+ authors = [{ name = "Eloi Barti" }]
9
+ keywords = ["job-search", "automation", "ai", "resume", "apply", "job-application"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Environment :: Console",
13
+ "License :: OSI Approved :: GNU Affero General Public License v3",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Topic :: Office/Business",
18
+ ]
19
+
20
+ dependencies = [
21
+ "typer>=0.9.0",
22
+ "rich>=13.0",
23
+ "httpx>=0.24",
24
+ "websocket-client>=1.8",
25
+ "beautifulsoup4>=4.12",
26
+ "playwright>=1.40",
27
+ "python-dotenv>=1.0",
28
+ "pypdf>=5.0",
29
+ "pyyaml>=6.0",
30
+ "pandas>=2.0",
31
+ "temporalio>=1.6",
32
+ "opentelemetry-api>=1.27",
33
+ "opentelemetry-sdk>=1.27",
34
+ "opentelemetry-exporter-otlp-proto-http>=1.27",
35
+ "opentelemetry-instrumentation-httpx>=0.48b0",
36
+ # Typed, concurrent job-board events plus resumable checkpoints. Pin the
37
+ # integration contract so JobCtrl's durable adapter changes deliberately.
38
+ "jobstreaming==0.0.2",
39
+ # Pydantic backs the canonical employer-analysis domain model
40
+ # (``domain/materials/analysis.py``) and is also pulled transitively by
41
+ # the agent SDKs below. Declared explicitly because the domain layer
42
+ # imports it directly.
43
+ "pydantic>=2.10",
44
+ # Gmail verification scopes derive sender domains from application URLs and
45
+ # need public-suffix-aware eTLD+1 parsing.
46
+ "publicsuffix2>=2.20191221,<3",
47
+ ]
48
+
49
+ [project.optional-dependencies]
50
+ dev = [
51
+ "build>=1.2",
52
+ "cryptography>=43.0",
53
+ "google-auth>=2.0",
54
+ "hatchling==1.29.0",
55
+ "pytest>=7.0",
56
+ "pytest-asyncio>=0.23",
57
+ "pytest-randomly>=4.0",
58
+ "ruff==0.15.8",
59
+ ]
60
+ providers = [
61
+ "claude-agent-sdk==0.2.115",
62
+ "openai-codex==0.1.0b3",
63
+ "openai-codex-cli-bin==0.137.0a4",
64
+ "google-antigravity==0.1.2",
65
+ ]
66
+
67
+ # Source checkouts keep the provider ensemble available on ordinary `uv sync`
68
+ # and `uv run`. Production payload builders use `--no-default-groups --no-dev`
69
+ # and acquire provider wheels separately through the signed provider-pack path.
70
+ [dependency-groups]
71
+ provider-runtime = [
72
+ "claude-agent-sdk==0.2.115",
73
+ "openai-codex==0.1.0b3",
74
+ "openai-codex-cli-bin==0.137.0a4",
75
+ "google-antigravity==0.1.2",
76
+ ]
77
+ release-build = ["build==1.4.3", "hatchling==1.29.0"]
78
+
79
+ [project.scripts]
80
+ jobctrl = "jobctrl.cli:app"
81
+
82
+ [project.urls]
83
+ Homepage = "https://github.com/ebarti/JobCtrl"
84
+ Repository = "https://github.com/ebarti/JobCtrl"
85
+ Issues = "https://github.com/ebarti/JobCtrl/issues"
86
+
87
+ [build-system]
88
+ # Release builds run with --no-isolation, so both the build backend and the
89
+ # locked development environment must resolve this exact, audited backend.
90
+ requires = ["hatchling==1.29.0"]
91
+ build-backend = "hatchling.build"
92
+
93
+ [tool.hatch.build.targets.wheel]
94
+ packages = ["src/jobctrl"]
95
+
96
+ [tool.hatch.build]
97
+ artifacts = ["src/jobctrl/config/*.yaml", "src/jobctrl/assets/fonts/*"]
98
+
99
+ [tool.hatch.build.targets.sdist.force-include]
100
+ "../../LICENSE" = "LICENSE"
101
+ "../../NOTICE" = "NOTICE"
102
+
103
+ [tool.ruff]
104
+ target-version = "py311"
105
+ line-length = 120
106
+
107
+ [tool.pytest.ini_options]
108
+ asyncio_mode = "strict"
109
+ markers = [
110
+ "system_browser: opt-in smoke requiring a real local system Chrome/Chromium; set JOBCTRL_RUN_SYSTEM_BROWSER_TESTS=1",
111
+ ]
112
+
113
+ [tool.pyright]
114
+ venvPath = "."
115
+ venv = ".venv"
116
+ extraPaths = ["src"]
117
+ include = ["src", "tests"]
118
+ pythonVersion = "3.11"
119
+
120
+ [tool.uv]
121
+ default-groups = ["provider-runtime"]
122
+
123
+ # The workspace dependency cutoff intentionally freezes the broad dependency
124
+ # graph. Keep it as a fixed project value, rather than a caller-provided
125
+ # relative `UV_EXCLUDE_NEWER` value, so locked source runtimes and release
126
+ # workflows resolve against the same graph. Provider runtimes and the audited
127
+ # JobStreaming integration move independently, so admit only their reviewed
128
+ # releases without moving the global cutoff for everything else.
129
+ exclude-newer = "2026-07-09T19:15:43.240437Z"
130
+ exclude-newer-package = { "google-antigravity" = "2026-06-04T21:19:27Z", "claude-agent-sdk" = "2026-07-10T00:00:00Z", "jobstreaming" = "2026-07-18T00:00:00Z" }
@@ -0,0 +1,3 @@
1
+ """JobCtrl — AI-powered end-to-end job application pipeline."""
2
+
3
+ __version__ = "2.0.7"
@@ -0,0 +1,5 @@
1
+ """Enable `python -m jobctrl`."""
2
+
3
+ from jobctrl.cli import app
4
+
5
+ app()
@@ -0,0 +1,363 @@
1
+ """Structured local action entrypoints for JobCtrl automation.
2
+
3
+ These wrappers give API/UI callers one local surface for invoking existing
4
+ Python automation without pasting shell commands. They intentionally delegate to
5
+ the current stage implementations; later phases can replace the internals with
6
+ queue-backed workers without changing the caller contract.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import traceback
12
+ from dataclasses import asdict, dataclass, field
13
+ from pathlib import Path
14
+ from time import perf_counter
15
+ from typing import Any
16
+ from uuid import uuid4
17
+
18
+ from jobctrl import config
19
+ from jobctrl.database import get_connection, init_db
20
+ from jobctrl.operational_metrics import record_operational_attempt_metric
21
+ from jobctrl.pipeline import SUPPORTED_STAGE_ORDER
22
+ from jobctrl.state import record_job_event, utc_now
23
+ from jobctrl.workflow_specs import (
24
+ build_apply_workflow_spec,
25
+ build_profile_import_workflow_spec,
26
+ build_run_stage_workflow_spec,
27
+ start_workflow_spec_and_wait_sync,
28
+ workflow_result_to_dict,
29
+ )
30
+
31
+ INTERNAL_PIPELINE_ACTION_STAGES: tuple[str, ...] = (*SUPPORTED_STAGE_ORDER, "enrich")
32
+ ACTION_STAGES: tuple[str, ...] = (*INTERNAL_PIPELINE_ACTION_STAGES, "apply", "profile_import")
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class LocalActionRequest:
37
+ """Input for a local automation action."""
38
+
39
+ stage: str
40
+ job_url: str | None = None
41
+ limit: int = 0
42
+ workers: int = 1
43
+ min_score: int = 7
44
+ validation_mode: str = "normal"
45
+ dry_run: bool = False
46
+ rescore: bool = False
47
+ retailor: bool = False
48
+ tailor_models: tuple[str, ...] = ()
49
+ tailor_judge_model: str | None = None
50
+ tailor_judge_min_score: float | None = None
51
+ model: str = "default"
52
+ headless: bool = False
53
+ continuous: bool = False
54
+ pdf_path: str | None = None
55
+ import_profile: bool = True
56
+ import_style: bool = True
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class LocalActionResult:
61
+ """Structured result returned by every local action wrapper."""
62
+
63
+ ok: bool
64
+ action_id: str
65
+ stage: str
66
+ status: str
67
+ started_at: str
68
+ finished_at: str
69
+ duration_ms: int
70
+ job_url: str | None = None
71
+ dry_run: bool = False
72
+ result: dict[str, Any] = field(default_factory=dict)
73
+ error: str | None = None
74
+ traceback: str | None = None
75
+
76
+ def to_dict(self) -> dict[str, Any]:
77
+ return asdict(self)
78
+
79
+
80
+ def run_local_action(request: LocalActionRequest) -> LocalActionResult:
81
+ """Run one structured local action and record start/finish events."""
82
+ action_id = f"act-{uuid4().hex}"
83
+ started_at = utc_now()
84
+ start = perf_counter()
85
+
86
+ if request.stage not in ACTION_STAGES:
87
+ return LocalActionResult(
88
+ ok=False,
89
+ action_id=action_id,
90
+ stage=request.stage,
91
+ status="failed",
92
+ started_at=started_at,
93
+ finished_at=utc_now(),
94
+ duration_ms=int((perf_counter() - start) * 1000),
95
+ job_url=request.job_url,
96
+ dry_run=request.dry_run,
97
+ error=f"Unknown action stage: {request.stage}",
98
+ )
99
+
100
+ try:
101
+ _bootstrap_runtime()
102
+ _record_action_event(
103
+ request,
104
+ "ActionStarted",
105
+ "info",
106
+ f"{request.stage} action started",
107
+ {"action_id": action_id, "dry_run": request.dry_run},
108
+ )
109
+
110
+ if request.stage == "profile_import" and request.dry_run:
111
+ result = {"planned": _describe_action(request)}
112
+ return _finish_action(
113
+ request,
114
+ action_id,
115
+ started_at,
116
+ start,
117
+ ok=True,
118
+ status="dry_run",
119
+ result=result,
120
+ )
121
+
122
+ result = _execute_action(request)
123
+ ok = _action_succeeded(result)
124
+ status = "succeeded" if ok else "failed"
125
+ return _finish_action(request, action_id, started_at, start, ok=ok, status=status, result=result)
126
+ except Exception as exc: # noqa: BLE001 - action API must return structured failure
127
+ return _finish_action(
128
+ request,
129
+ action_id,
130
+ started_at,
131
+ start,
132
+ ok=False,
133
+ status="failed",
134
+ result={},
135
+ error=str(exc),
136
+ traceback_text=traceback.format_exc(),
137
+ )
138
+
139
+
140
+ def run_stage_action(
141
+ stage: str,
142
+ *,
143
+ job_url: str | None = None,
144
+ limit: int = 0,
145
+ workers: int = 1,
146
+ min_score: int = 7,
147
+ validation_mode: str = "normal",
148
+ dry_run: bool = False,
149
+ rescore: bool = False,
150
+ retailor: bool = False,
151
+ tailor_models: tuple[str, ...] = (),
152
+ tailor_judge_model: str | None = None,
153
+ tailor_judge_min_score: float | None = None,
154
+ ) -> LocalActionResult:
155
+ """Convenience entrypoint for a pipeline stage action."""
156
+ return run_local_action(
157
+ LocalActionRequest(
158
+ stage=stage,
159
+ job_url=job_url,
160
+ limit=limit,
161
+ workers=workers,
162
+ min_score=min_score,
163
+ validation_mode=validation_mode,
164
+ dry_run=dry_run,
165
+ rescore=rescore,
166
+ retailor=retailor,
167
+ tailor_models=tailor_models,
168
+ tailor_judge_model=tailor_judge_model,
169
+ tailor_judge_min_score=tailor_judge_min_score,
170
+ )
171
+ )
172
+
173
+
174
+ def _bootstrap_runtime() -> None:
175
+ config.load_env()
176
+ config.ensure_dirs()
177
+ init_db()
178
+
179
+
180
+ def _execute_action(request: LocalActionRequest) -> dict[str, Any]:
181
+ if request.stage in INTERNAL_PIPELINE_ACTION_STAGES:
182
+ return _start_and_wait(
183
+ build_run_stage_workflow_spec(
184
+ {
185
+ "tenantId": "local",
186
+ "stage": request.stage,
187
+ "minScore": request.min_score,
188
+ "dryRun": request.dry_run,
189
+ "workers": request.workers,
190
+ "validationMode": request.validation_mode,
191
+ "limit": request.limit,
192
+ "rescore": request.rescore,
193
+ "retailor": request.retailor,
194
+ "tailorModels": request.tailor_models,
195
+ "tailorJudgeModel": request.tailor_judge_model,
196
+ "tailorJudgeMinScore": request.tailor_judge_min_score,
197
+ }
198
+ )
199
+ )
200
+ if request.stage == "apply":
201
+ return _start_and_wait(
202
+ build_apply_workflow_spec(
203
+ {
204
+ "tenantId": "local",
205
+ "jobUrl": request.job_url,
206
+ "limit": _effective_apply_limit(request),
207
+ "minScore": request.min_score,
208
+ "headless": request.headless,
209
+ "model": request.model,
210
+ "dryRun": request.dry_run,
211
+ "continuous": request.continuous,
212
+ "workers": request.workers,
213
+ }
214
+ )
215
+ )
216
+ if request.stage == "profile_import":
217
+ if not request.pdf_path:
218
+ raise ValueError("profile_import requires pdf_path.")
219
+ return _start_and_wait(
220
+ build_profile_import_workflow_spec(
221
+ {
222
+ "tenantId": "local",
223
+ "pdfPath": str(Path(request.pdf_path).expanduser()),
224
+ "importProfile": request.import_profile,
225
+ "importStyle": request.import_style,
226
+ }
227
+ )
228
+ )
229
+ raise ValueError(f"Unknown action stage: {request.stage}")
230
+
231
+
232
+ def _start_and_wait(spec) -> dict[str, Any]:
233
+ started = start_workflow_spec_and_wait_sync(spec)
234
+ result = workflow_result_to_dict(started.result)
235
+ return {
236
+ "status": _workflow_status(result),
237
+ "runId": started.run_id,
238
+ "workflowId": started.workflow_id,
239
+ "firstExecutionRunId": started.first_execution_run_id,
240
+ **(result if isinstance(result, dict) else {"result": result}),
241
+ }
242
+
243
+
244
+ def _action_succeeded(result: dict[str, Any]) -> bool:
245
+ if result.get("errors"):
246
+ return False
247
+ if int(result.get("failed") or 0) > 0:
248
+ return False
249
+ if result.get("failure") or result.get("error"):
250
+ return False
251
+ if result.get("ok") is False:
252
+ return False
253
+ status = str(result.get("status") or "ok").lower()
254
+ return not status.startswith("error") and status not in {"failed", "failure"}
255
+
256
+
257
+ def _workflow_status(result: Any) -> str:
258
+ if isinstance(result, dict):
259
+ if result.get("failure") or result.get("error") or result.get("ok") is False:
260
+ return "failed"
261
+ return str(result.get("status") or "succeeded")
262
+ return "succeeded"
263
+
264
+
265
+ def _effective_apply_limit(request: LocalActionRequest) -> int:
266
+ return 0 if request.continuous else max(1, request.limit)
267
+
268
+
269
+ def _finish_action(
270
+ request: LocalActionRequest,
271
+ action_id: str,
272
+ started_at: str,
273
+ start: float,
274
+ *,
275
+ ok: bool,
276
+ status: str,
277
+ result: dict[str, Any],
278
+ error: str | None = None,
279
+ traceback_text: str | None = None,
280
+ ) -> LocalActionResult:
281
+ finished_at = utc_now()
282
+ duration_ms = int((perf_counter() - start) * 1000)
283
+ try:
284
+ _record_action_event(
285
+ request,
286
+ "ActionSucceeded" if ok else "ActionFailed",
287
+ "info" if ok else "error",
288
+ f"{request.stage} action {status}",
289
+ {
290
+ "action_id": action_id,
291
+ "duration_ms": duration_ms,
292
+ "error": error,
293
+ },
294
+ )
295
+ if request.dry_run:
296
+ _record_dry_run_metric(request, action_id, duration_ms)
297
+ except Exception: # noqa: BLE001 - action results must stay JSON-safe when event logging fails
298
+ pass
299
+ return LocalActionResult(
300
+ ok=ok,
301
+ action_id=action_id,
302
+ stage=request.stage,
303
+ status=status,
304
+ started_at=started_at,
305
+ finished_at=finished_at,
306
+ duration_ms=duration_ms,
307
+ job_url=request.job_url,
308
+ dry_run=request.dry_run,
309
+ result=result,
310
+ error=error,
311
+ traceback=traceback_text,
312
+ )
313
+
314
+
315
+ def _record_action_event(
316
+ request: LocalActionRequest,
317
+ event_type: str,
318
+ level: str,
319
+ message: str,
320
+ payload: dict[str, Any],
321
+ ) -> None:
322
+ conn = get_connection()
323
+ record_job_event(
324
+ conn,
325
+ request.job_url,
326
+ request.stage,
327
+ event_type,
328
+ level=level,
329
+ message=message,
330
+ payload=payload,
331
+ )
332
+ conn.commit()
333
+
334
+
335
+ def _record_dry_run_metric(request: LocalActionRequest, action_id: str, duration_ms: int) -> None:
336
+ conn = get_connection()
337
+ record_operational_attempt_metric(
338
+ conn,
339
+ stage=request.stage,
340
+ attempt_kind="local_action",
341
+ outcome="dry_run",
342
+ adapter="api",
343
+ run_id=action_id,
344
+ job_url=request.job_url,
345
+ duration_ms=duration_ms,
346
+ metadata={
347
+ "workers": request.workers,
348
+ "limit": request.limit,
349
+ "rescore": request.rescore,
350
+ "retailor": request.retailor,
351
+ "tailor_models": list(request.tailor_models),
352
+ "tailor_judge_model": request.tailor_judge_model,
353
+ "tailor_judge_min_score": request.tailor_judge_min_score,
354
+ },
355
+ )
356
+ conn.commit()
357
+
358
+
359
+ def _describe_action(request: LocalActionRequest) -> dict[str, Any]:
360
+ planned = asdict(request)
361
+ if request.stage == "apply":
362
+ planned["effective_limit"] = _effective_apply_limit(request)
363
+ return planned
@@ -0,0 +1 @@
1
+ """Apply pipeline: Chrome management, prompt building, orchestration, and dashboard."""