stage-cli 1.1.0__tar.gz → 1.1.2__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 (294) hide show
  1. {stage_cli-1.1.0 → stage_cli-1.1.2}/CHANGELOG.md +40 -0
  2. {stage_cli-1.1.0 → stage_cli-1.1.2}/PKG-INFO +6 -6
  3. {stage_cli-1.1.0 → stage_cli-1.1.2}/README.md +5 -5
  4. {stage_cli-1.1.0 → stage_cli-1.1.2}/SECURITY.md +1 -1
  5. {stage_cli-1.1.0 → stage_cli-1.1.2}/assets/ci-flow.svg +1 -1
  6. stage_cli-1.1.2/src/stage/__init__.py +1 -0
  7. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/render.py +2 -2
  8. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/lexicon/eligibility.yaml +6 -0
  9. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/http/client.py +32 -3
  10. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/normalize/location.py +22 -3
  11. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/health.py +1 -1
  12. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_eligibility.py +30 -0
  13. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_health.py +33 -0
  14. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_http_client.py +43 -1
  15. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_location.py +24 -0
  16. {stage_cli-1.1.0 → stage_cli-1.1.2}/uv.lock +24 -24
  17. stage_cli-1.1.0/src/stage/__init__.py +0 -1
  18. {stage_cli-1.1.0 → stage_cli-1.1.2}/.gitattributes +0 -0
  19. {stage_cli-1.1.0 → stage_cli-1.1.2}/.gitignore +0 -0
  20. {stage_cli-1.1.0 → stage_cli-1.1.2}/.python-version +0 -0
  21. {stage_cli-1.1.0 → stage_cli-1.1.2}/LICENSE +0 -0
  22. {stage_cli-1.1.0 → stage_cli-1.1.2}/assets/cli.png +0 -0
  23. {stage_cli-1.1.0 → stage_cli-1.1.2}/assets/tui.png +0 -0
  24. {stage_cli-1.1.0 → stage_cli-1.1.2}/pyproject.toml +0 -0
  25. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/__main__.py +0 -0
  26. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/banner.py +0 -0
  27. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/bootstrap/__init__.py +0 -0
  28. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/bootstrap/openjobs.py +0 -0
  29. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/classify/__init__.py +0 -0
  30. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/classify/eligibility.py +0 -0
  31. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/classify/internship.py +0 -0
  32. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/classify/role.py +0 -0
  33. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/classify/scope.py +0 -0
  34. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/__init__.py +0 -0
  35. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/app.py +0 -0
  36. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/commands/__init__.py +0 -0
  37. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/commands/discovery.py +0 -0
  38. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/commands/insight.py +0 -0
  39. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/commands/pipeline.py +0 -0
  40. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/commands/postings.py +0 -0
  41. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/commands/schedule.py +0 -0
  42. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/housekeeping.py +0 -0
  43. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/logfile.py +0 -0
  44. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/notify.py +0 -0
  45. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/options.py +0 -0
  46. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/runlock.py +0 -0
  47. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/schedule.py +0 -0
  48. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/schedule_state.py +0 -0
  49. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/selection.py +0 -0
  50. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/cli/serialize.py +0 -0
  51. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/companies.py +0 -0
  52. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/a.yaml +0 -0
  53. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/b.yaml +0 -0
  54. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/c.yaml +0 -0
  55. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/d.yaml +0 -0
  56. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/e.yaml +0 -0
  57. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/f.yaml +0 -0
  58. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/g.yaml +0 -0
  59. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/h.yaml +0 -0
  60. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/i.yaml +0 -0
  61. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/j.yaml +0 -0
  62. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/k.yaml +0 -0
  63. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/l.yaml +0 -0
  64. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/m.yaml +0 -0
  65. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/n.yaml +0 -0
  66. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/o.yaml +0 -0
  67. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/other.yaml +0 -0
  68. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/p.yaml +0 -0
  69. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/q.yaml +0 -0
  70. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/r.yaml +0 -0
  71. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/s.yaml +0 -0
  72. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/t.yaml +0 -0
  73. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/u.yaml +0 -0
  74. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/v.yaml +0 -0
  75. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/w.yaml +0 -0
  76. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/x.yaml +0 -0
  77. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/y.yaml +0 -0
  78. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/companies/z.yaml +0 -0
  79. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/fonts/DejaVuSans.LICENSE.txt +0 -0
  80. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/fonts/DejaVuSans.ttf +0 -0
  81. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/lexicon/company_tokens.yaml +0 -0
  82. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/lexicon/inclusive_suffixes.yaml +0 -0
  83. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/lexicon/internship.yaml +0 -0
  84. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/lexicon/language.yaml +0 -0
  85. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/lexicon/locations.yaml +0 -0
  86. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/lexicon/roles.yaml +0 -0
  87. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/lexicon/terms.yaml +0 -0
  88. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/lexicon/workday_facets.yaml +0 -0
  89. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/data/seed_companies.yaml +0 -0
  90. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/dedup/__init__.py +0 -0
  91. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/dedup/identity.py +0 -0
  92. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/dedup/resolve.py +0 -0
  93. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/__init__.py +0 -0
  94. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/company.py +0 -0
  95. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/coverage.py +0 -0
  96. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/custom_board.py +0 -0
  97. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/discovery.py +0 -0
  98. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/enums.py +0 -0
  99. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/events.py +0 -0
  100. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/filters.py +0 -0
  101. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/health.py +0 -0
  102. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/ids.py +0 -0
  103. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/job.py +0 -0
  104. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/matching.py +0 -0
  105. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/priority.py +0 -0
  106. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/quarantine.py +0 -0
  107. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/rate_state.py +0 -0
  108. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/retention.py +0 -0
  109. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/rotation.py +0 -0
  110. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/signals.py +0 -0
  111. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/sync_run.py +0 -0
  112. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/text.py +0 -0
  113. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/validator.py +0 -0
  114. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/visits.py +0 -0
  115. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/domain/workday.py +0 -0
  116. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/http/__init__.py +0 -0
  117. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/http/breaker.py +0 -0
  118. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/http/cache.py +0 -0
  119. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/http/profiles.py +0 -0
  120. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/lexicon.py +0 -0
  121. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/normalize/__init__.py +0 -0
  122. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/normalize/language.py +0 -0
  123. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/normalize/terms.py +0 -0
  124. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/normalize/urls.py +0 -0
  125. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/paths.py +0 -0
  126. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/py.typed +0 -0
  127. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/__init__.py +0 -0
  128. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/canary.py +0 -0
  129. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/coverage.py +0 -0
  130. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/discover.py +0 -0
  131. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/export.py +0 -0
  132. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/maintenance.py +0 -0
  133. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/quarantine.py +0 -0
  134. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/query.py +0 -0
  135. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/services/sync.py +0 -0
  136. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/__init__.py +0 -0
  137. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/_text.py +0 -0
  138. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/ashby.py +0 -0
  139. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/bamboohr.py +0 -0
  140. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/base.py +0 -0
  141. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/breezy.py +0 -0
  142. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/collage.py +0 -0
  143. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/community_feeds.py +0 -0
  144. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/curated_markdown.py +0 -0
  145. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/custom_json.py +0 -0
  146. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/espresso.py +0 -0
  147. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/feed.py +0 -0
  148. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/greenhouse.py +0 -0
  149. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/jobbank.py +0 -0
  150. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/jobvite.py +0 -0
  151. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/lever.py +0 -0
  152. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/oracle_cloud.py +0 -0
  153. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/platforms.py +0 -0
  154. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/quebec_emploi.py +0 -0
  155. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/recruitee.py +0 -0
  156. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/simplify.py +0 -0
  157. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/smartrecruiters.py +0 -0
  158. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/speedyapply.py +0 -0
  159. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/themuse.py +0 -0
  160. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/workable.py +0 -0
  161. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/workday.py +0 -0
  162. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/sources/zshah.py +0 -0
  163. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/storage/__init__.py +0 -0
  164. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/storage/migrations/0001_initial.sql +0 -0
  165. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/storage/migrations/__init__.py +0 -0
  166. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/storage/repository.py +0 -0
  167. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/storage/search.py +0 -0
  168. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/storage/sqlite_repo.py +0 -0
  169. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/storage/writer.py +0 -0
  170. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/__init__.py +0 -0
  171. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/app.py +0 -0
  172. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/help.py +0 -0
  173. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/safe.py +0 -0
  174. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/screens/__init__.py +0 -0
  175. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/screens/boards.py +0 -0
  176. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/screens/postings.py +0 -0
  177. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/screens/review.py +0 -0
  178. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/screens/splash.py +0 -0
  179. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/screens/stats.py +0 -0
  180. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/screens/sync.py +0 -0
  181. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/state.py +0 -0
  182. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/theme.tcss +0 -0
  183. {stage_cli-1.1.0 → stage_cli-1.1.2}/src/stage/tui/widgets/__init__.py +0 -0
  184. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/conftest.py +0 -0
  185. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/bilingual_titles.json +0 -0
  186. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/blackrock_search.html +0 -0
  187. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/croesus_career.html +0 -0
  188. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/disney_search.html +0 -0
  189. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/ea_searchjobs.html +0 -0
  190. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/espresso_search.html +0 -0
  191. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/gildan_openpositions.html +0 -0
  192. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/greenhouse_jobs.json +0 -0
  193. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/jobbank_search.html +0 -0
  194. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/jobvite_agscareer.html +0 -0
  195. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/jobvite_resolver.html +0 -0
  196. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/l3harris_search.html +0 -0
  197. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/lightspeed_openings.html +0 -0
  198. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/loblaw_jobs.html +0 -0
  199. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/nbc_search.html +0 -0
  200. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/parsons_search.html +0 -0
  201. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/publicissapient_sitemap.html +0 -0
  202. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/rentec_careers.html +0 -0
  203. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/sega_vacancies.html +0 -0
  204. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/sfrmk_search.html +0 -0
  205. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/shopify_internships.html +0 -0
  206. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/fixtures/twosigma_openroles.html +0 -0
  207. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/golden/classification.json +0 -0
  208. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_adapter_salvage.py +0 -0
  209. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_adopt_unregistered.py +0 -0
  210. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_board_identity.py +0 -0
  211. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_board_rest.py +0 -0
  212. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_canary.py +0 -0
  213. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_canary_events.py +0 -0
  214. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_classify.py +0 -0
  215. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_cli_help.py +0 -0
  216. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_cli_options.py +0 -0
  217. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_collage_adapter.py +0 -0
  218. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_companies_registry.py +0 -0
  219. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_company_fold_key.py +0 -0
  220. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_coverage.py +0 -0
  221. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_curated_markdown.py +0 -0
  222. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_custom_json.py +0 -0
  223. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_custom_json_buckets.py +0 -0
  224. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_custom_json_formats.py +0 -0
  225. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_custom_json_pagination.py +0 -0
  226. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_dedup.py +0 -0
  227. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_degree_and_disciplines.py +0 -0
  228. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_detail_queue.py +0 -0
  229. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_discover.py +0 -0
  230. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_export.py +0 -0
  231. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_feed_adapters.py +0 -0
  232. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_feeds.py +0 -0
  233. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_fetch_authority.py +0 -0
  234. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_fold_inclusive.py +0 -0
  235. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_golden.py +0 -0
  236. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_greenhouse_adapter.py +0 -0
  237. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_housekeeping.py +0 -0
  238. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_html_boards.py +0 -0
  239. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_html_text.py +0 -0
  240. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_http_akamai.py +0 -0
  241. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_http_conditional.py +0 -0
  242. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_http_resilience.py +0 -0
  243. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_integrity_repair.py +0 -0
  244. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_jobvite_adapter.py +0 -0
  245. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_language.py +0 -0
  246. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_layering.py +0 -0
  247. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_lever_adapter.py +0 -0
  248. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_lexicon.py +0 -0
  249. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_malformed_input.py +0 -0
  250. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_migrations.py +0 -0
  251. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_new_adapters.py +0 -0
  252. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_notify.py +0 -0
  253. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_openjobs_crossref.py +0 -0
  254. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_oracle_cloud_adapter.py +0 -0
  255. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_paths.py +0 -0
  256. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_permissions.py +0 -0
  257. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_platform_shapes.py +0 -0
  258. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_profiles.py +0 -0
  259. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_quarantine.py +0 -0
  260. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_quarantine_relabel.py +0 -0
  261. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_quebec_emploi.py +0 -0
  262. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_rate_state.py +0 -0
  263. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_recall_limits.py +0 -0
  264. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_recheck.py +0 -0
  265. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_registry_shards.py +0 -0
  266. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_render.py +0 -0
  267. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_request_log.py +0 -0
  268. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_rescreen.py +0 -0
  269. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_review_contradictions.py +0 -0
  270. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_rotation.py +0 -0
  271. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_run_budget.py +0 -0
  272. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_schedule.py +0 -0
  273. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_schedule_state.py +0 -0
  274. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_schema_baseline.py +0 -0
  275. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_search.py +0 -0
  276. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_serialize.py +0 -0
  277. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_show.py +0 -0
  278. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_signals_persist.py +0 -0
  279. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_smartrecruiters_adapter.py +0 -0
  280. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_speedyapply.py +0 -0
  281. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_startup_budget.py +0 -0
  282. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_sync_e2e.py +0 -0
  283. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_sync_shape.py +0 -0
  284. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_sync_step2.py +0 -0
  285. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_terms.py +0 -0
  286. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_tombstones.py +0 -0
  287. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_tui.py +0 -0
  288. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_unregistered_quarantine.py +0 -0
  289. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_untrusted_display.py +0 -0
  290. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_update_badges.py +0 -0
  291. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_verify_registry.py +0 -0
  292. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_workday_adapter.py +0 -0
  293. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_workday_urls.py +0 -0
  294. {stage_cli-1.1.0 → stage_cli-1.1.2}/tests/test_writer.py +0 -0
@@ -4,6 +4,44 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
5
  follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.1.2] - 2026-09-09
8
+
9
+ ### Fixed
10
+
11
+ - Job Bank has returned nothing since 31 August. Its server drops the connection
12
+ on any client offering TLS 1.3, so no request ever reached the board. That one
13
+ host is capped at TLS 1.2 now. Certificates are still checked and every other
14
+ board still gets TLS 1.3.
15
+ - A connection that died before the server answered was logged as `ConnectError:`
16
+ with nothing after the colon, which is why Job Bank looked fine for eight days.
17
+ The cause is carried through now:
18
+ `ConnectError: ConnectionResetError: [Errno 54] Connection reset by peer`.
19
+
20
+ ## [1.1.1] - 2026-09-07
21
+
22
+ ### Fixed
23
+
24
+ - A posting that lists several cities is no longer read as foreign when a foreign
25
+ city happens to be listed first. "London, New York" and "New York, London, or
26
+ Paris" are US postings. 15 rows come back, among them quant and software
27
+ internships at Point72, Squarepoint, Xantium, GSA and Booz Allen. Rome, NY and
28
+ Vienna, VA come back too. A comma still binds a city to its region, so
29
+ "Toronto, ON", "Berlin, DE" and "San Jose, Costa Rica" read as before. All
30
+ 21,270 stored location strings were replayed against the change; 32 moved, all
31
+ of them from international to domestic.
32
+ - `doctor` listed boards you had already switched off, and kept listing them for
33
+ as long as their old failures sat in the database. It now reads the registry the
34
+ way sync does. Sync itself still sees every row, disabled ones included, because
35
+ that is how it tells an orphaned posting from a live one.
36
+ - `doctor` and `sync` told you to clear a blocked bucket with
37
+ `stage sources --clear`. That flag does not exist. Both print
38
+ `--reset-rate-limit` now.
39
+ - Process engineering, category management, talent acquisition, environmental
40
+ engineering, construction management and investor relations are named as non-CS
41
+ work instead of landing in the unknown pile. 109 quarantined rows, no change to
42
+ what Stage keeps. A software role inside one of those areas still counts as
43
+ software.
44
+
7
45
  ## [1.1.0] - 2026-09-03
8
46
 
9
47
  ### Added
@@ -69,5 +107,7 @@ them in a SQLite database on your own machine.
69
107
 
70
108
  Python 3.12, 3.13 or 3.14 on macOS, Linux or Windows.
71
109
 
110
+ [1.1.2]: https://github.com/NicholasXydis/Stage/releases/tag/v1.1.2
111
+ [1.1.1]: https://github.com/NicholasXydis/Stage/releases/tag/v1.1.1
72
112
  [1.1.0]: https://github.com/NicholasXydis/Stage/releases/tag/v1.1.0
73
113
  [1.0.0]: https://github.com/NicholasXydis/Stage/releases/tag/v1.0.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stage-cli
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: Aggregates CS internship postings (Canada/US, EN + FR) into a local SQLite database
5
5
  Project-URL: Homepage, https://github.com/NicholasXydis/Stage
6
6
  Project-URL: Repository, https://github.com/NicholasXydis/Stage
@@ -59,7 +59,7 @@ Free and open source. No account, no server, no telemetry.
59
59
 
60
60
  ![python](https://img.shields.io/static/v1?label=python&message=3.12%20%7C%203.13%20%7C%203.14&color=4a9eff&logo=python&logoColor=white)
61
61
 
62
- ![tests](https://img.shields.io/static/v1?label=tests&message=2%2C381&color=44bb00) ![coverage](https://img.shields.io/static/v1?label=coverage&message=89%25&color=44bb00) [![ci](https://img.shields.io/github/actions/workflow/status/NicholasXydis/Stage/ci.yml?branch=main&label=ci&logo=githubactions&logoColor=white)](https://github.com/NicholasXydis/Stage/actions/workflows/ci.yml)
62
+ ![tests](https://img.shields.io/static/v1?label=tests&message=2%2C414&color=44bb00) ![coverage](https://img.shields.io/static/v1?label=coverage&message=89%25&color=44bb00) [![ci](https://img.shields.io/github/actions/workflow/status/NicholasXydis/Stage/ci.yml?branch=main&label=ci&logo=githubactions&logoColor=white)](https://github.com/NicholasXydis/Stage/actions/workflows/ci.yml)
63
63
 
64
64
  <br>
65
65
 
@@ -90,7 +90,7 @@ Internships disappear fast. By the time you find the right posting, hundreds of
90
90
 
91
91
  Stage scans 1,450+ employer career boards and finds internships CS undergrads in Canada and the U.S. can actually apply to. Every rejected posting is logged with the exact reason it was filtered out.
92
92
 
93
- It has kept **4,165** postings out of **138,533** screened so far.
93
+ It has kept **3,577** postings out of **166,298** screened so far.
94
94
 
95
95
  ## A look at it
96
96
 
@@ -272,7 +272,7 @@ Stage/
272
272
  │ ├─ http/ rate posture, circuit breaker, validator cache
273
273
  │ ├─ storage/ SQLite repository, migrations, FTS
274
274
  │ └─ data/ packaged registry (a directory) and lexicons
275
- ├─ tests/ 2,381 tests, 89% branch coverage
275
+ ├─ tests/ 2,414 tests, 89% branch coverage
276
276
  └─ .github/workflows/ CI, CodeQL, scheduled canary, release
277
277
  ```
278
278
 
@@ -299,7 +299,7 @@ Stage/
299
299
  ▼ ▼
300
300
  ┌─────────────┐ ┌─────────────┐
301
301
  │ jobs │ │ quarantine │
302
- │ 4,165 │ │ 134,368 │
302
+ │ 5,495 │ │ 160,803 │
303
303
  └──────┬──────┘ └─────────────┘
304
304
  │ dedup collapses the same posting across sources
305
305
  ▼
@@ -318,7 +318,7 @@ Stage/
318
318
 
319
319
  | Workflow | File | Purpose |
320
320
  | --- | --- | --- |
321
- | CI | `.github/workflows/ci.yml` | Ruff, format, mypy strict, 2,381 tests, coverage floor, badge drift |
321
+ | CI | `.github/workflows/ci.yml` | Ruff, format, mypy strict, 2,414 tests, coverage floor, badge drift |
322
322
  | Supported Pythons | `.github/workflows/ci.yml` | The full suite again on 3.13 and 3.14 |
323
323
  | Wheel | `.github/workflows/ci.yml` | Builds and smoke-tests the wheel on Ubuntu, Windows and macOS |
324
324
  | CodeQL | `.github/workflows/codeql.yml` | Static analysis for Python and the workflows themselves |
@@ -20,7 +20,7 @@ Free and open source. No account, no server, no telemetry.
20
20
 
21
21
  ![python](https://img.shields.io/static/v1?label=python&message=3.12%20%7C%203.13%20%7C%203.14&color=4a9eff&logo=python&logoColor=white)
22
22
 
23
- ![tests](https://img.shields.io/static/v1?label=tests&message=2%2C381&color=44bb00) ![coverage](https://img.shields.io/static/v1?label=coverage&message=89%25&color=44bb00) [![ci](https://img.shields.io/github/actions/workflow/status/NicholasXydis/Stage/ci.yml?branch=main&label=ci&logo=githubactions&logoColor=white)](https://github.com/NicholasXydis/Stage/actions/workflows/ci.yml)
23
+ ![tests](https://img.shields.io/static/v1?label=tests&message=2%2C414&color=44bb00) ![coverage](https://img.shields.io/static/v1?label=coverage&message=89%25&color=44bb00) [![ci](https://img.shields.io/github/actions/workflow/status/NicholasXydis/Stage/ci.yml?branch=main&label=ci&logo=githubactions&logoColor=white)](https://github.com/NicholasXydis/Stage/actions/workflows/ci.yml)
24
24
 
25
25
  <br>
26
26
 
@@ -51,7 +51,7 @@ Internships disappear fast. By the time you find the right posting, hundreds of
51
51
 
52
52
  Stage scans 1,450+ employer career boards and finds internships CS undergrads in Canada and the U.S. can actually apply to. Every rejected posting is logged with the exact reason it was filtered out.
53
53
 
54
- It has kept **4,165** postings out of **138,533** screened so far.
54
+ It has kept **3,577** postings out of **166,298** screened so far.
55
55
 
56
56
  ## A look at it
57
57
 
@@ -233,7 +233,7 @@ Stage/
233
233
  │ ├─ http/ rate posture, circuit breaker, validator cache
234
234
  │ ├─ storage/ SQLite repository, migrations, FTS
235
235
  │ └─ data/ packaged registry (a directory) and lexicons
236
- ├─ tests/ 2,381 tests, 89% branch coverage
236
+ ├─ tests/ 2,414 tests, 89% branch coverage
237
237
  └─ .github/workflows/ CI, CodeQL, scheduled canary, release
238
238
  ```
239
239
 
@@ -260,7 +260,7 @@ Stage/
260
260
  ▼ ▼
261
261
  ┌─────────────┐ ┌─────────────┐
262
262
  │ jobs │ │ quarantine │
263
- │ 4,165 │ │ 134,368 │
263
+ │ 5,495 │ │ 160,803 │
264
264
  └──────┬──────┘ └─────────────┘
265
265
  │ dedup collapses the same posting across sources
266
266
  ▼
@@ -279,7 +279,7 @@ Stage/
279
279
 
280
280
  | Workflow | File | Purpose |
281
281
  | --- | --- | --- |
282
- | CI | `.github/workflows/ci.yml` | Ruff, format, mypy strict, 2,381 tests, coverage floor, badge drift |
282
+ | CI | `.github/workflows/ci.yml` | Ruff, format, mypy strict, 2,414 tests, coverage floor, badge drift |
283
283
  | Supported Pythons | `.github/workflows/ci.yml` | The full suite again on 3.13 and 3.14 |
284
284
  | Wheel | `.github/workflows/ci.yml` | Builds and smoke-tests the wheel on Ubuntu, Windows and macOS |
285
285
  | CodeQL | `.github/workflows/codeql.yml` | Static analysis for Python and the workflows themselves |
@@ -4,7 +4,7 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | --- | --- |
7
- | 1.1.0 | ✅ |
7
+ | 1.1.2 | ✅ |
8
8
 
9
9
  Only the latest release receives security fixes.
10
10
 
@@ -22,7 +22,7 @@
22
22
 
23
23
  <rect x="240" y="42" width="180" height="58" rx="12" fill="#111C30" stroke="#3B82F6" stroke-width="2"/>
24
24
  <text x="330" y="66" fill="#F0F6FC" font-size="14">ruff, format, mypy</text>
25
- <text x="330" y="86" fill="#F0F6FC" font-size="14">2,381 tests, 89%</text>
25
+ <text x="330" y="86" fill="#F0F6FC" font-size="14">2,414 tests, 89%</text>
26
26
 
27
27
  <path d="M420 71H470" stroke="#3B82F6" stroke-width="3" marker-end="url(#arrow)"/>
28
28
 
@@ -0,0 +1 @@
1
+ __version__ = "1.1.2"
@@ -811,7 +811,7 @@ def render_doctor(
811
811
  f"{_duration(state.blocks_remaining_s(now))} — "
812
812
  f"{first_line(state.reason) or 'no reason recorded'}"
813
813
  )
814
- console.print(" [dim]Clear one with stage sources --clear <bucket>.[/dim]")
814
+ console.print(" [dim]Clear one with stage sources --reset-rate-limit <bucket>.[/dim]")
815
815
 
816
816
  failing = report.failing_boards
817
817
  if failing:
@@ -1062,7 +1062,7 @@ async def render_sync(
1062
1062
  console.print(
1063
1063
  f" [dim]{blocked.consecutive_failures} consecutive failure(s): "
1064
1064
  f"{reason}. Not fetched this run — clear it with "
1065
- f"[bold]stage sources --clear {blocked.bucket}[/bold].[/dim]"
1065
+ f"[bold]stage sources --reset-rate-limit {blocked.bucket}[/bold].[/dim]"
1066
1066
  )
1067
1067
  case SourceStarted(source=source, companies=companies):
1068
1068
  console.print(f"\n[bold cyan]{source}[/bold cyan] — {companies} board(s)")
@@ -280,6 +280,12 @@ non_cs:
280
280
  - people experience
281
281
  - people operations
282
282
  excluded_titles:
283
+ - process engineering
284
+ - category management
285
+ - environmental engineering
286
+ - talent acquisition
287
+ - investor relations
288
+ - construction management
283
289
  - information technology intern
284
290
  - information technology internship
285
291
  - it intern
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import json
3
3
  import random
4
+ import ssl
4
5
  import time
5
6
  from collections.abc import Mapping
6
7
  from dataclasses import dataclass, field
@@ -38,6 +39,7 @@ RETRYABLE_STATUSES = frozenset({429, 500, 502, 503, 504})
38
39
  BLOCKING_STATUSES = frozenset({401, 403})
39
40
  BLOCKED_COOLDOWN_S = 1800.0
40
41
  DENIED_HOSTS_BEFORE_BLOCK = 2
42
+ TLS12_ONLY_HOSTS = frozenset({"www.jobbank.gc.ca"})
41
43
  MAX_REDIRECTS = 3
42
44
  ORIGIN_BOUND_HEADERS = frozenset(
43
45
  {"authorization", "cookie", "origin", "referer", "proxy-authorization", "x-csrf-token"}
@@ -229,6 +231,25 @@ class HostBudget:
229
231
  return state
230
232
 
231
233
 
234
+ def _tls12_context() -> ssl.SSLContext:
235
+ context = httpx.create_ssl_context()
236
+ context.maximum_version = ssl.TLSVersion.TLSv1_2
237
+ return context
238
+
239
+
240
+ def _describe(exc: BaseException) -> str:
241
+ detail = str(exc)
242
+ seen = {id(exc)}
243
+ cause = exc.__cause__ or exc.__context__
244
+ while not detail and cause is not None and id(cause) not in seen:
245
+ detail = str(cause)
246
+ seen.add(id(cause))
247
+ if detail:
248
+ return f"{type(exc).__name__}: {type(cause).__name__}: {detail}"
249
+ cause = cause.__cause__ or cause.__context__
250
+ return f"{type(exc).__name__}: {detail}"
251
+
252
+
232
253
  def _origin_of(url: httpx.URL) -> tuple[str, str, int | None]:
233
254
  return (url.scheme, (url.host or "").lower(), url.port)
234
255
 
@@ -269,11 +290,19 @@ class HttpClient:
269
290
  for bucket, state in self._rate_state.items()
270
291
  if state.is_blocked(self._now)
271
292
  }
293
+ mounts: dict[str, httpx.AsyncBaseTransport] | None = None
294
+ if transport is None:
295
+ capped = self._allowed_hosts & TLS12_ONLY_HOSTS
296
+ mounts = {
297
+ f"https://{host}": httpx.AsyncHTTPTransport(verify=_tls12_context())
298
+ for host in sorted(capped)
299
+ } or None
272
300
  self._client = httpx.AsyncClient(
273
301
  timeout=timeout or DEFAULT_TIMEOUT,
274
302
  headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
275
303
  follow_redirects=False,
276
304
  transport=transport,
305
+ mounts=mounts or {},
277
306
  )
278
307
 
279
308
  async def __aenter__(self) -> Self:
@@ -556,7 +585,7 @@ class HttpClient:
556
585
  status=None,
557
586
  elapsed_ms=(time.perf_counter() - started) * 1000,
558
587
  attempt=attempt,
559
- error=f"{type(exc).__name__}: {exc}",
588
+ error=_describe(exc),
560
589
  )
561
590
  )
562
591
  raise
@@ -564,7 +593,7 @@ class HttpClient:
564
593
  elapsed = (time.perf_counter() - started) * 1000
565
594
  budget.metrics.failures += 1
566
595
  budget.breaker.record_failure()
567
- budget.last_error = f"{type(exc).__name__}: {exc}"
596
+ budget.last_error = _describe(exc)
568
597
  self._log.append(
569
598
  RequestRecord(
570
599
  method=method,
@@ -572,7 +601,7 @@ class HttpClient:
572
601
  status=None,
573
602
  elapsed_ms=elapsed,
574
603
  attempt=attempt,
575
- error=f"{type(exc).__name__}: {exc}",
604
+ error=_describe(exc),
576
605
  )
577
606
  )
578
607
  raise
@@ -7,6 +7,7 @@ from stage.lexicon import LocationLexicon, fold, location_lexicon
7
7
 
8
8
  _SEGMENT_SPLIT = re.compile(r"[;/•|\n]+")
9
9
  _FIELD_SPLIT = re.compile(r"[,\-]+")
10
+ _LIST_SPLIT = re.compile(r",|\bor\b")
10
11
  _SUBDIVISION_CODE = re.compile(r"[A-Z]{1,3}")
11
12
  _COUNTRY_PREFIXES = frozenset({"can", "us", "usa"})
12
13
 
@@ -244,16 +245,34 @@ def display_location(raw: str) -> str:
244
245
  return ", ".join([canonical, *fields[1:]]) + more
245
246
 
246
247
 
248
+ @lru_cache(maxsize=1)
249
+ def _city_names() -> frozenset[str]:
250
+ lexicon = location_lexicon()
251
+ return (
252
+ lexicon.usa_cities | lexicon.canada_cities | lexicon.montreal | lexicon.international_cities
253
+ )
254
+
255
+
256
+ def _city_list(segment: str, lexicon: LocationLexicon) -> list[_Segment]:
257
+ fields = [field.strip() for field in _LIST_SPLIT.split(segment) if field.strip()]
258
+ cities = _city_names()
259
+ named = [field for field in fields if fold(field) in cities]
260
+ if len(named) < 2:
261
+ return []
262
+ return [_resolve_segment(field, lexicon) for field in named]
263
+
264
+
247
265
  def resolve_location(raw: str) -> ResolvedLocation:
248
266
  if not raw or not raw.strip():
249
267
  return ResolvedLocation()
250
268
 
251
269
  lexicon = location_lexicon()
252
- segments = [
253
- _resolve_segment(part, lexicon) for part in _SEGMENT_SPLIT.split(raw) if part.strip()
254
- ]
270
+ parts = [part for part in _SEGMENT_SPLIT.split(raw) if part.strip()]
271
+ segments = [_resolve_segment(part, lexicon) for part in parts]
255
272
  if not segments:
256
273
  return ResolvedLocation()
274
+ for part in parts:
275
+ segments.extend(_city_list(part, lexicon))
257
276
 
258
277
  scope = _scope(segments)
259
278
 
@@ -123,7 +123,7 @@ class StatsReport:
123
123
  def _live_boards(companies: Sequence[Company]) -> frozenset[str]:
124
124
  from stage.services.sync import _registry_boards
125
125
 
126
- return frozenset(_registry_boards(companies)[1])
126
+ return frozenset(_registry_boards([row for row in companies if row.enabled])[1])
127
127
 
128
128
 
129
129
  async def _board_health(
@@ -121,6 +121,36 @@ def test_a_vehicle_software_engineer_is_not_rejected_as_a_ui_role() -> None:
121
121
  assert screen_is_cs_role(_job(title)) is None
122
122
 
123
123
 
124
+ @pytest.mark.parametrize(
125
+ "title",
126
+ [
127
+ "Process Engineering Intern",
128
+ "Category Management Project Intern",
129
+ "Talent Acquisition Intern",
130
+ "Environmental Engineering Intern",
131
+ "Construction Management Intern",
132
+ "Investor Relations Intern",
133
+ ],
134
+ )
135
+ def test_a_named_non_cs_discipline_is_labelled_rather_than_left_unknown(title: str) -> None:
136
+ rejection = screen_is_cs_role(_job(title))
137
+ assert rejection is not None
138
+ assert rejection.reason is RejectionReason.NOT_A_CS_ROLE
139
+
140
+
141
+ @pytest.mark.parametrize(
142
+ "title",
143
+ [
144
+ "Supply Chain Applied AI Engineering Intern",
145
+ "Software Engineer Intern - Logistics",
146
+ "Staff Software Developer Manufacturing Engineering",
147
+ "System Design Engineer - AI Cluster Software Engineer",
148
+ ],
149
+ )
150
+ def test_a_cs_role_in_a_non_cs_domain_is_still_accepted(title: str) -> None:
151
+ assert screen_is_cs_role(_job(title)) is None
152
+
153
+
124
154
  def test_an_unknown_non_cs_title_is_quarantined_for_review() -> None:
125
155
  rejection = screen_is_cs_role(_job("Cashier Systems Software Intern"))
126
156
  assert rejection is not None
@@ -267,6 +267,39 @@ def test_the_breakdown_skips_a_column_that_is_always_unknown() -> None:
267
267
  assert "role" in COMPOSITION_COLUMNS
268
268
 
269
269
 
270
+ async def test_doctor_forgets_a_board_the_owner_disabled(db_path: Path) -> None:
271
+ from stage.domain import Company, CompanyVisit, Platform
272
+
273
+ async with open_repository(db_path) as repository:
274
+ await repository.apply_source_batch(
275
+ SourceBatch(
276
+ source="greenhouse",
277
+ run_started_at=NOW,
278
+ visits=(
279
+ CompanyVisit(
280
+ board="greenhouse:marqeta",
281
+ succeeded=False,
282
+ error="HttpStatusError: 404",
283
+ label="Marqeta",
284
+ ),
285
+ ),
286
+ )
287
+ )
288
+ off = Company(name="Marqeta", platform=Platform.GREENHOUSE, slug="marqeta", enabled=False)
289
+ live = Company(name="Acme", platform=Platform.GREENHOUSE, slug="acme")
290
+ report = await doctor(repository, now=NOW, companies=(off, live))
291
+
292
+ assert report.failing_boards == ()
293
+
294
+
295
+ def test_orphan_closing_still_sees_a_disabled_board() -> None:
296
+ from stage.domain import Company, Platform
297
+ from stage.services.sync import _registry_boards
298
+
299
+ off = Company(name="Marqeta", platform=Platform.GREENHOUSE, slug="marqeta", enabled=False)
300
+ assert "greenhouse:marqeta" in _registry_boards((off,))[1]
301
+
302
+
270
303
  async def test_doctor_forgets_a_board_the_registry_no_longer_names(db_path: Path) -> None:
271
304
  from stage.domain import Company, CompanyVisit, Platform
272
305
 
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import ssl
2
3
  import time
3
4
  from collections.abc import AsyncIterator
4
5
 
@@ -7,7 +8,7 @@ import pytest
7
8
  import respx
8
9
 
9
10
  from stage.http import HostBudgetExceededError, HttpClient, RatePosture
10
- from stage.http.client import USER_AGENT
11
+ from stage.http.client import USER_AGENT, _describe, _tls12_context
11
12
 
12
13
  ENDPOINT = "https://boards-api.greenhouse.io/v1/boards/acme/jobs"
13
14
 
@@ -328,3 +329,44 @@ async def test_a_plain_http_url_is_refused_before_it_is_sent() -> None:
328
329
  ) as client:
329
330
  with pytest.raises(RedirectNotAllowedError):
330
331
  await client.get_json("http://one.example.test/jobs")
332
+
333
+
334
+ def test_a_tls12_only_host_gets_a_capped_transport() -> None:
335
+ client = HttpClient(allowed_hosts=frozenset({"www.jobbank.gc.ca"}))
336
+ mounted = client._client._mounts
337
+ assert any(key.pattern.endswith("www.jobbank.gc.ca") for key in mounted)
338
+
339
+
340
+ def test_an_ordinary_host_keeps_the_default_transport() -> None:
341
+ client = HttpClient(allowed_hosts=frozenset({"boards-api.greenhouse.io"}))
342
+ assert not client._client._mounts
343
+
344
+
345
+ def test_the_capped_context_still_verifies_certificates() -> None:
346
+ context = _tls12_context()
347
+ assert context.maximum_version is ssl.TLSVersion.TLSv1_2
348
+ assert context.verify_mode is ssl.CERT_REQUIRED
349
+ assert context.check_hostname
350
+
351
+
352
+ def test_an_injected_transport_is_never_overridden() -> None:
353
+ transport = httpx.MockTransport(lambda _: httpx.Response(200))
354
+ client = HttpClient(allowed_hosts=frozenset({"www.jobbank.gc.ca"}), transport=transport)
355
+ assert not client._client._mounts
356
+
357
+
358
+ def test_an_empty_error_reports_its_underlying_cause() -> None:
359
+ try:
360
+ try:
361
+ raise ConnectionResetError(54, "Connection reset by peer")
362
+ except OSError as inner:
363
+ raise httpx.ConnectError("") from inner
364
+ except httpx.ConnectError as exc:
365
+ described = _describe(exc)
366
+ assert described == "ConnectError: ConnectionResetError: [Errno 54] Connection reset by peer"
367
+
368
+
369
+ def test_an_error_that_speaks_for_itself_is_unchanged() -> None:
370
+ assert _describe(httpx.ConnectError("all attempts failed")) == (
371
+ "ConnectError: all attempts failed"
372
+ )
@@ -390,3 +390,27 @@ def test_a_state_code_disambiguates_a_city_two_countries_share(
390
390
  raw: str, bucket: LocationBucket
391
391
  ) -> None:
392
392
  assert resolve_location(raw).bucket is bucket
393
+
394
+
395
+ @pytest.mark.parametrize(
396
+ ("raw", "bucket"),
397
+ [
398
+ ("London, New York", LocationBucket.USA),
399
+ ("New York, London", LocationBucket.USA),
400
+ ("New York, London, or Paris", LocationBucket.USA),
401
+ ("London, England, New York, New York", LocationBucket.USA),
402
+ ("New York, San Francisco, Munich or London", LocationBucket.USA),
403
+ ("London, Paris, New York, Singapore, Hong Kong", LocationBucket.USA),
404
+ ("Dubai, London, Paris, Hong Kong, Sydney, Toronto", LocationBucket.CANADA),
405
+ ("San Jose, Costa Rica", LocationBucket.INTERNATIONAL),
406
+ ("Costa Rica, San Jose", LocationBucket.INTERNATIONAL),
407
+ ("Los Angeles, Chile", LocationBucket.INTERNATIONAL),
408
+ ("San Francisco, Heredia, Costa Rica", LocationBucket.INTERNATIONAL),
409
+ ("Saint-Louis, Saint-Pierre, Reunion", LocationBucket.INTERNATIONAL),
410
+ ("Chicago, IL, United States", LocationBucket.USA),
411
+ ("Toronto, ON", LocationBucket.CANADA),
412
+ ("Bengaluru, KA, IN", LocationBucket.INTERNATIONAL),
413
+ ],
414
+ )
415
+ def test_a_list_of_cities_keeps_the_domestic_one(raw: str, bucket: LocationBucket) -> None:
416
+ assert resolve_location(raw).bucket is bucket
@@ -531,11 +531,11 @@ wheels = [
531
531
 
532
532
  [[package]]
533
533
  name = "platformdirs"
534
- version = "4.11.5"
534
+ version = "4.11.7"
535
535
  source = { registry = "https://pypi.org/simple" }
536
- sdist = { url = "https://files.pythonhosted.org/packages/ea/06/cf1564dcc2e2261c8c8c6c05628dc8b418943bdae2a4e58640ceb2f770fa/platformdirs-4.11.5.tar.gz", hash = "sha256:e8b31f4f8bcbbedef91a6b57a706255e4f148d2a4e01648382a0a47342539173", size = 34823, upload-time = "2026-08-27T21:36:37.46Z" }
536
+ sdist = { url = "https://files.pythonhosted.org/packages/69/b7/802a56eca9f2fac455b8bab5375a2647b0f0e14a2cd63ef077de3c4a7658/platformdirs-4.11.7.tar.gz", hash = "sha256:4f41487eeeeeb07f3a6625e61d9bc0ae6809f92d3386dbd74392fbb76108104d", size = 35127, upload-time = "2026-09-01T13:35:10.502Z" }
537
537
  wheels = [
538
- { url = "https://files.pythonhosted.org/packages/c7/12/6f3fcd5067a9cbf4f8664b32957973498da8b083455203c8d9cab83a725c/platformdirs-4.11.5-py3-none-any.whl", hash = "sha256:89f8d42695853b89c7170bd49bc3dc593f98a71e695ede88e06a3b247bc4563b", size = 23900, upload-time = "2026-08-27T21:36:36.227Z" },
538
+ { url = "https://files.pythonhosted.org/packages/27/6e/80993e10a0482f630cef528635789233224f36b1ffd11592aa15d13ff9ce/platformdirs-4.11.7-py3-none-any.whl", hash = "sha256:8a02cb259042c79d1cd0450facc2fe6dc9d303ae7901afbe33bf8ea0b188cef6", size = 23938, upload-time = "2026-09-01T13:35:09.02Z" },
539
539
  ]
540
540
 
541
541
  [[package]]
@@ -775,27 +775,27 @@ wheels = [
775
775
 
776
776
  [[package]]
777
777
  name = "ruff"
778
- version = "0.16.5"
779
- source = { registry = "https://pypi.org/simple" }
780
- sdist = { url = "https://files.pythonhosted.org/packages/f3/85/c8e12473c93018f92d19dd988a294202e1c27426c47ec4de53ffb847b8d8/ruff-0.16.5.tar.gz", hash = "sha256:1b88500f9ffbcab3dedb0082c9f9492e91ec3d618aac1236a3e0189938f7040b", size = 4912003, upload-time = "2026-08-27T16:34:18.258Z" }
781
- wheels = [
782
- { url = "https://files.pythonhosted.org/packages/c6/b6/77c90a970fe2dae17a723acbd011043ea97c98d7deacccefdc4ba74ec512/ruff-0.16.5-py3-none-linux_armv6l.whl", hash = "sha256:12e5f673e774c35fbb62f288809c7653b73445f8ecec6b6063fd6ea3521aa14b", size = 10011941, upload-time = "2026-08-27T16:33:41.287Z" },
783
- { url = "https://files.pythonhosted.org/packages/4b/46/6cf67cf6411885a1d6f7f6d801682f155536a85176d10b605e2ceffed8bd/ruff-0.16.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eda58a5802de40e7ed5b32b64e0b32539338cc6fcd2c78f61e3ad6a0d79f51c3", size = 10204049, upload-time = "2026-08-27T16:33:44.056Z" },
784
- { url = "https://files.pythonhosted.org/packages/46/fd/c8720ca7a090abf0c2fef4abe8a5ef6e5127ed15196d8886ff75a2b370e2/ruff-0.16.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5ae9a7b9a8875131f40f8fe967cc86abf899779efd663cb7ce3d572d01da7eb", size = 9809037, upload-time = "2026-08-27T16:33:46.257Z" },
785
- { url = "https://files.pythonhosted.org/packages/43/45/a684caacdedaca180f52bacccc40bf0789d2c5a7c75f25324853e9eaedb5/ruff-0.16.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b719b0a1f4d59710d283ab2965f621684a108a9e41da622e3b23f0326cd0025", size = 9964129, upload-time = "2026-08-27T16:33:48.352Z" },
786
- { url = "https://files.pythonhosted.org/packages/9e/f2/5d2bcdaca6b5b93d1b4dfc166cd2aebf7680143a1b38a28759df13a94d31/ruff-0.16.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2298f2780ed1be0c5cb1361e32ab7b1467f3cce7dabe101d2210a314f2fe42e9", size = 9821518, upload-time = "2026-08-27T16:33:50.57Z" },
787
- { url = "https://files.pythonhosted.org/packages/aa/ff/011cce29accf9257d5974145b733fc653a37985ed6825413a3987cefbfe0/ruff-0.16.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:258f29035a2dd021e7861e631b227a5b3f14e50c1184c9a6a122c5f4576154d7", size = 10534835, upload-time = "2026-08-27T16:33:52.522Z" },
788
- { url = "https://files.pythonhosted.org/packages/d7/5a/f0cf109bada9bba0e96c90c21c9f9251803f57225c32d293327a03c710d6/ruff-0.16.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9a4f0432966834019c74d1b7e5c51224305d7713f3d7faf3e7451f1a3be3cde", size = 11252550, upload-time = "2026-08-27T16:33:54.521Z" },
789
- { url = "https://files.pythonhosted.org/packages/63/4d/1d481aaea2046c6a7ed7c291f9004c669cce3c087b6b376ed5b08271e3fe/ruff-0.16.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5eb3a8c3d0ade9cea42b591fd530368e8798380e30e0a308b85a5cf718f09ea", size = 10777949, upload-time = "2026-08-27T16:33:56.88Z" },
790
- { url = "https://files.pythonhosted.org/packages/ee/34/ee245ca55f64443233034b3d02b03236b19242004281247c079390b7facd/ruff-0.16.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0f69e191a13a3c9816f63163c88790cb12cd157bbbb384e9c44745702ab105", size = 10311656, upload-time = "2026-08-27T16:33:59.12Z" },
791
- { url = "https://files.pythonhosted.org/packages/a7/4d/c33a333e341c0a2b96c715b52d89a606f5a34cd4ac493cd9b8d0187186b8/ruff-0.16.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0eeab41fbea2c42f98dfb9822cdccda9d24ba38d49f6dc945b5c236d48f0ef29", size = 10532125, upload-time = "2026-08-27T16:34:01.166Z" },
792
- { url = "https://files.pythonhosted.org/packages/30/e1/a64cef78b40192497bb98a27a8aa8f2c98ee9ee15bc97f7712d94ef32937/ruff-0.16.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f0768e9df4300713fff30733c87575f68b6f1d8de41184e505b7fdd9c0c95eaf", size = 10097648, upload-time = "2026-08-27T16:34:03.16Z" },
793
- { url = "https://files.pythonhosted.org/packages/cc/4e/4cdc9ed3c3e109d2f71e62572a37457298d7bc7501ec3138babb7ed32bbd/ruff-0.16.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:95cc70cdc7aa80c338de356279d2adbeb2de0f520b9ecd8aba75b94e95e02f91", size = 9829344, upload-time = "2026-08-27T16:34:05.134Z" },
794
- { url = "https://files.pythonhosted.org/packages/39/4a/31ed35ce31729955fc583ee0d176d6e784c1290cb0b0a75cb2134c1ab72a/ruff-0.16.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d185c8398ded1bfd91c0c2cb258346307571eccc473a8490af8c3977399c384a", size = 10277117, upload-time = "2026-08-27T16:34:07.425Z" },
795
- { url = "https://files.pythonhosted.org/packages/a8/a0/60356d86687b4b666d593df213f4dc3041750d024cb7bf2cfa81cfd65c2e/ruff-0.16.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fb8e3a3c4c6a784150a7ced53b015f4b253fc2bf97a610886419ead64b4756ef", size = 10711653, upload-time = "2026-08-27T16:34:09.712Z" },
796
- { url = "https://files.pythonhosted.org/packages/ed/20/656d67f5b25ca9bda4e02b1de25867b2954e1d19e03648060f167ad0f4cc/ruff-0.16.5-py3-none-win32.whl", hash = "sha256:288b0a5f080492fe5635db849f9e2e84aa3cce7b7f0e955997d416c507c76a26", size = 10034250, upload-time = "2026-08-27T16:34:11.8Z" },
797
- { url = "https://files.pythonhosted.org/packages/5b/42/ee8e68a207b9127fcde6c3d7e197def432f346cb1af159e1fa14ca0d1cdc/ruff-0.16.5-py3-none-win_amd64.whl", hash = "sha256:ddc6385fb2137f616357ca03d6c74f4be987f80fed4008566b754f6032b8546f", size = 10516714, upload-time = "2026-08-27T16:34:13.963Z" },
798
- { url = "https://files.pythonhosted.org/packages/73/e3/7df5a396e445b9ba49ce9a9437439a4d80042c61c0ade199abf8d16de1ac/ruff-0.16.5-py3-none-win_arm64.whl", hash = "sha256:a64abe90968719b851bb7cedffaa8753fbdbdadab483089682db623f3edc587e", size = 10391564, upload-time = "2026-08-27T16:34:16.064Z" },
778
+ version = "0.16.6"
779
+ source = { registry = "https://pypi.org/simple" }
780
+ sdist = { url = "https://files.pythonhosted.org/packages/a4/7c/6adb35d70e7c027e308274557901c7e00fb3407750faf3620c184ae058cb/ruff-0.16.6.tar.gz", hash = "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050", size = 4921251, upload-time = "2026-09-03T16:57:29.037Z" }
781
+ wheels = [
782
+ { url = "https://files.pythonhosted.org/packages/a4/28/9cc1b79639e284ec103f43c88c644db4eb58cbd0ea1ca11f1193435369ac/ruff-0.16.6-py3-none-linux_armv6l.whl", hash = "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8", size = 10015638, upload-time = "2026-09-03T16:56:40.986Z" },
783
+ { url = "https://files.pythonhosted.org/packages/71/11/627d342ef727ea7794edf74fe23d60a074b02c3acc2e9436684e782286ca/ruff-0.16.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32", size = 10220762, upload-time = "2026-09-03T16:56:44.681Z" },
784
+ { url = "https://files.pythonhosted.org/packages/43/d9/b75668ce41e4c8d073d18d6d08672ba6906ce45d5c06ea4fdb2e84ce3853/ruff-0.16.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c", size = 9835082, upload-time = "2026-09-03T16:56:47.142Z" },
785
+ { url = "https://files.pythonhosted.org/packages/99/97/123ab10b05cde889c107c20f5a9774955104b5552796a2a8584b089ae8eb/ruff-0.16.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876", size = 9949304, upload-time = "2026-09-03T16:56:49.813Z" },
786
+ { url = "https://files.pythonhosted.org/packages/3e/58/a4a2c59dd2e5b85929c912d9cac3056eb9ee8c7e75e9b9fe3e109174966b/ruff-0.16.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d", size = 9840612, upload-time = "2026-09-03T16:56:52.368Z" },
787
+ { url = "https://files.pythonhosted.org/packages/61/6a/ff8c8626a786c4f49d48ced4a752dadbca65f5263005f9c2416578194694/ruff-0.16.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1", size = 10543465, upload-time = "2026-09-03T16:56:55.089Z" },
788
+ { url = "https://files.pythonhosted.org/packages/ad/bb/c47535923365f337b82e28192e4e9eef2176511007cfd99a62fc22df5dad/ruff-0.16.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70", size = 11267576, upload-time = "2026-09-03T16:56:57.791Z" },
789
+ { url = "https://files.pythonhosted.org/packages/ba/50/e5119a5212b5cd63b51e1f4b25e7bd636a6668fc069a3160b108ad7e3c16/ruff-0.16.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3", size = 10781993, upload-time = "2026-09-03T16:57:00.666Z" },
790
+ { url = "https://files.pythonhosted.org/packages/8b/98/083d8b4ef3c51a0d19db84367791cbe9f44e4b53343d19dfa83556e1cd9a/ruff-0.16.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757", size = 10317748, upload-time = "2026-09-03T16:57:03.428Z" },
791
+ { url = "https://files.pythonhosted.org/packages/9a/29/68f7ff2c5ad95f19f00627ac2de95644e25fe47371ea60b2db1fd952315e/ruff-0.16.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e", size = 10540096, upload-time = "2026-09-03T16:57:06.182Z" },
792
+ { url = "https://files.pythonhosted.org/packages/c4/f9/79a8f6de85968641d68a7863aeec577551924ef066a990a48ff93167beab/ruff-0.16.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4", size = 10100494, upload-time = "2026-09-03T16:57:09.194Z" },
793
+ { url = "https://files.pythonhosted.org/packages/d9/e8/b81a22d9b90c00b892ccf2fa2ac36fa95de4c13ab85aea3e73795cfe4651/ruff-0.16.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88", size = 9843663, upload-time = "2026-09-03T16:57:12.168Z" },
794
+ { url = "https://files.pythonhosted.org/packages/39/aa/54f516ec5e5a11c4afdceb1c454ebb054ffb96e4f4a1705580b4346abd35/ruff-0.16.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953", size = 10282461, upload-time = "2026-09-03T16:57:15.077Z" },
795
+ { url = "https://files.pythonhosted.org/packages/52/0b/38d0aa8aa32372b96dc44f97b22e576c4147808271aab7b2cb1e353d4445/ruff-0.16.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718", size = 10728808, upload-time = "2026-09-03T16:57:17.797Z" },
796
+ { url = "https://files.pythonhosted.org/packages/5e/e5/9e274e24eeb027640ffc7442f21239f16d17f47acec15ae34f32e03a5c79/ruff-0.16.6-py3-none-win32.whl", hash = "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25", size = 10049212, upload-time = "2026-09-03T16:57:20.55Z" },
797
+ { url = "https://files.pythonhosted.org/packages/22/31/72472449414223ed1a2da236b992adbb1a2ae59e34794574810f60ce068e/ruff-0.16.6-py3-none-win_amd64.whl", hash = "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39", size = 10556402, upload-time = "2026-09-03T16:57:23.501Z" },
798
+ { url = "https://files.pythonhosted.org/packages/fc/07/d781f8f8e1ac24bef9f3269cf62ffb1407ca24c3a8f12e5e22874f90528c/ruff-0.16.6-py3-none-win_arm64.whl", hash = "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5", size = 10412850, upload-time = "2026-09-03T16:57:26.416Z" },
799
799
  ]
800
800
 
801
801
  [[package]]
@@ -1 +0,0 @@
1
- __version__ = "1.1.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes