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