fasterrag 0.1.0b1__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 (637) hide show
  1. fasterrag-0.1.0b1/.env.example +34 -0
  2. fasterrag-0.1.0b1/.github/scripts/check_licences.py +457 -0
  3. fasterrag-0.1.0b1/.github/scripts/release.py +226 -0
  4. fasterrag-0.1.0b1/.github/scripts/release_smoke.sh +103 -0
  5. fasterrag-0.1.0b1/.gitignore +47 -0
  6. fasterrag-0.1.0b1/LICENSE +202 -0
  7. fasterrag-0.1.0b1/NOTICE +4 -0
  8. fasterrag-0.1.0b1/PKG-INFO +513 -0
  9. fasterrag-0.1.0b1/README.md +382 -0
  10. fasterrag-0.1.0b1/config.yaml +215 -0
  11. fasterrag-0.1.0b1/docs/CHANGELOG.md +392 -0
  12. fasterrag-0.1.0b1/docs/CONTRIBUTING.md +106 -0
  13. fasterrag-0.1.0b1/docs/adr/ADR-0001-qdrant-as-reference-vector-db.md +36 -0
  14. fasterrag-0.1.0b1/docs/adr/ADR-0002-adapter-factory-pluggability.md +33 -0
  15. fasterrag-0.1.0b1/docs/adr/ADR-0003-config-yaml-env-split.md +34 -0
  16. fasterrag-0.1.0b1/docs/adr/ADR-0004-hybrid-search-plus-reranking.md +35 -0
  17. fasterrag-0.1.0b1/docs/adr/ADR-0005-api-cli-only-control-plane.md +32 -0
  18. fasterrag-0.1.0b1/docs/adr/ADR-0006-python-package-surface.md +34 -0
  19. fasterrag-0.1.0b1/docs/adr/ADR-0007-bm25-as-backend-sparse-vectors.md +40 -0
  20. fasterrag-0.1.0b1/docs/adr/ADR-0008-apache-2-license.md +46 -0
  21. fasterrag-0.1.0b1/docs/adr/ADR-0009-journal-stays-on-files.md +54 -0
  22. fasterrag-0.1.0b1/docs/adr/ADR-0010-reranker-off-by-default.md +47 -0
  23. fasterrag-0.1.0b1/docs/adr/ADR-0011-degradation-ladder-scope.md +45 -0
  24. fasterrag-0.1.0b1/docs/api-reference.md +410 -0
  25. fasterrag-0.1.0b1/docs/architecture.md +170 -0
  26. fasterrag-0.1.0b1/docs/archive-format.md +242 -0
  27. fasterrag-0.1.0b1/docs/assets/cistatus.svg +1 -0
  28. fasterrag-0.1.0b1/docs/assets/extra.css +5 -0
  29. fasterrag-0.1.0b1/docs/assets/favicon.png +0 -0
  30. fasterrag-0.1.0b1/docs/assets/help.svg +1 -0
  31. fasterrag-0.1.0b1/docs/assets/index.svg +1 -0
  32. fasterrag-0.1.0b1/docs/assets/intro.css +1203 -0
  33. fasterrag-0.1.0b1/docs/assets/logo-banner-dark.svg +1 -0
  34. fasterrag-0.1.0b1/docs/assets/logo-banner-light.svg +1 -0
  35. fasterrag-0.1.0b1/docs/assets/logo-mono.svg +1 -0
  36. fasterrag-0.1.0b1/docs/assets/logo.svg +1 -0
  37. fasterrag-0.1.0b1/docs/assets/social-card.png +0 -0
  38. fasterrag-0.1.0b1/docs/benchmarks.md +200 -0
  39. fasterrag-0.1.0b1/docs/blockers.md +86 -0
  40. fasterrag-0.1.0b1/docs/cli-reference.md +389 -0
  41. fasterrag-0.1.0b1/docs/code-notes.md +6453 -0
  42. fasterrag-0.1.0b1/docs/config-reference.md +537 -0
  43. fasterrag-0.1.0b1/docs/cookbook.md +302 -0
  44. fasterrag-0.1.0b1/docs/data-model.md +198 -0
  45. fasterrag-0.1.0b1/docs/deployment.md +100 -0
  46. fasterrag-0.1.0b1/docs/differentiators.md +136 -0
  47. fasterrag-0.1.0b1/docs/disaster-recovery.md +83 -0
  48. fasterrag-0.1.0b1/docs/engineering-audit.md +318 -0
  49. fasterrag-0.1.0b1/docs/engineering-notes.md +53 -0
  50. fasterrag-0.1.0b1/docs/engineering-scorecard.md +197 -0
  51. fasterrag-0.1.0b1/docs/failure-modes.md +211 -0
  52. fasterrag-0.1.0b1/docs/flow.md +118 -0
  53. fasterrag-0.1.0b1/docs/glossary.md +53 -0
  54. fasterrag-0.1.0b1/docs/how-to-use.md +112 -0
  55. fasterrag-0.1.0b1/docs/index.md +175 -0
  56. fasterrag-0.1.0b1/docs/integrations.md +154 -0
  57. fasterrag-0.1.0b1/docs/internals.md +79 -0
  58. fasterrag-0.1.0b1/docs/migration-guide.md +78 -0
  59. fasterrag-0.1.0b1/docs/observability.md +153 -0
  60. fasterrag-0.1.0b1/docs/overrides/main.html +58 -0
  61. fasterrag-0.1.0b1/docs/performance.md +121 -0
  62. fasterrag-0.1.0b1/docs/plugins.md +108 -0
  63. fasterrag-0.1.0b1/docs/prompts.md +189 -0
  64. fasterrag-0.1.0b1/docs/python-api.md +271 -0
  65. fasterrag-0.1.0b1/docs/quickstart.md +236 -0
  66. fasterrag-0.1.0b1/docs/rebuild-playbook.md +73 -0
  67. fasterrag-0.1.0b1/docs/references.md +30 -0
  68. fasterrag-0.1.0b1/docs/release-readiness.md +240 -0
  69. fasterrag-0.1.0b1/docs/release.md +67 -0
  70. fasterrag-0.1.0b1/docs/reliability.md +102 -0
  71. fasterrag-0.1.0b1/docs/robots.txt +3 -0
  72. fasterrag-0.1.0b1/docs/scope.md +113 -0
  73. fasterrag-0.1.0b1/docs/security.md +405 -0
  74. fasterrag-0.1.0b1/docs/slo.md +35 -0
  75. fasterrag-0.1.0b1/docs/structure.md +164 -0
  76. fasterrag-0.1.0b1/docs/task-briefs.md +470 -0
  77. fasterrag-0.1.0b1/docs/testing-strategy.md +382 -0
  78. fasterrag-0.1.0b1/docs/todo.md +704 -0
  79. fasterrag-0.1.0b1/docs/tour.md +277 -0
  80. fasterrag-0.1.0b1/docs/troubleshooting.md +163 -0
  81. fasterrag-0.1.0b1/docs/verification.md +117 -0
  82. fasterrag-0.1.0b1/examples/README.md +53 -0
  83. fasterrag-0.1.0b1/examples/plugin-vectordb/README.md +31 -0
  84. fasterrag-0.1.0b1/examples/plugin-vectordb/pyproject.toml +19 -0
  85. fasterrag-0.1.0b1/examples/plugin-vectordb/src/fasterrag_memory_vectordb/__init__.py +5 -0
  86. fasterrag-0.1.0b1/examples/plugin-vectordb/src/fasterrag_memory_vectordb/adapter.py +305 -0
  87. fasterrag-0.1.0b1/examples/plugin-vectordb/src/fasterrag_memory_vectordb/py.typed +0 -0
  88. fasterrag-0.1.0b1/examples/plugin-vectordb/tests/test_contract.py +55 -0
  89. fasterrag-0.1.0b1/examples/scripted-llm/README.md +12 -0
  90. fasterrag-0.1.0b1/examples/scripted-llm/pyproject.toml +19 -0
  91. fasterrag-0.1.0b1/examples/scripted-llm/src/fasterrag_scripted_llm/__init__.py +5 -0
  92. fasterrag-0.1.0b1/examples/scripted-llm/src/fasterrag_scripted_llm/adapter.py +79 -0
  93. fasterrag-0.1.0b1/examples/scripted-llm/src/fasterrag_scripted_llm/py.typed +0 -0
  94. fasterrag-0.1.0b1/pyproject.toml +312 -0
  95. fasterrag-0.1.0b1/scripts/_checkout.py +34 -0
  96. fasterrag-0.1.0b1/scripts/check_blockers.py +131 -0
  97. fasterrag-0.1.0b1/scripts/check_claims.py +291 -0
  98. fasterrag-0.1.0b1/scripts/check_comments.py +334 -0
  99. fasterrag-0.1.0b1/scripts/check_commit_message.py +143 -0
  100. fasterrag-0.1.0b1/scripts/check_contract_ran.py +254 -0
  101. fasterrag-0.1.0b1/scripts/check_doc_truth.py +144 -0
  102. fasterrag-0.1.0b1/scripts/check_error_taxonomy.py +678 -0
  103. fasterrag-0.1.0b1/scripts/check_layering.py +479 -0
  104. fasterrag-0.1.0b1/scripts/check_must_run.py +345 -0
  105. fasterrag-0.1.0b1/scripts/check_openapi_drift.py +114 -0
  106. fasterrag-0.1.0b1/scripts/check_package.py +376 -0
  107. fasterrag-0.1.0b1/scripts/check_task_ids.py +299 -0
  108. fasterrag-0.1.0b1/scripts/check_unbuilt_claims.py +935 -0
  109. fasterrag-0.1.0b1/scripts/compare_nightly.py +492 -0
  110. fasterrag-0.1.0b1/scripts/measure_cost_governor.py +418 -0
  111. fasterrag-0.1.0b1/scripts/measure_idempotency_reclaim.py +504 -0
  112. fasterrag-0.1.0b1/scripts/measure_rerank.py +967 -0
  113. fasterrag-0.1.0b1/scripts/mutation_run.py +1494 -0
  114. fasterrag-0.1.0b1/scripts/stamp_release.py +286 -0
  115. fasterrag-0.1.0b1/scripts/trim_docstrings.py +285 -0
  116. fasterrag-0.1.0b1/src/fasterrag/__init__.py +26 -0
  117. fasterrag-0.1.0b1/src/fasterrag/adapters/__init__.py +7 -0
  118. fasterrag-0.1.0b1/src/fasterrag/adapters/embeddings/__init__.py +36 -0
  119. fasterrag-0.1.0b1/src/fasterrag/adapters/embeddings/base.py +317 -0
  120. fasterrag-0.1.0b1/src/fasterrag/adapters/embeddings/cohere.py +153 -0
  121. fasterrag-0.1.0b1/src/fasterrag/adapters/embeddings/factory.py +83 -0
  122. fasterrag-0.1.0b1/src/fasterrag/adapters/embeddings/huggingface.py +174 -0
  123. fasterrag-0.1.0b1/src/fasterrag/adapters/embeddings/ollama.py +141 -0
  124. fasterrag-0.1.0b1/src/fasterrag/adapters/embeddings/openai.py +131 -0
  125. fasterrag-0.1.0b1/src/fasterrag/adapters/embeddings/sync.py +46 -0
  126. fasterrag-0.1.0b1/src/fasterrag/adapters/embeddings/tiering.py +135 -0
  127. fasterrag-0.1.0b1/src/fasterrag/adapters/llm/__init__.py +27 -0
  128. fasterrag-0.1.0b1/src/fasterrag/adapters/llm/anthropic.py +119 -0
  129. fasterrag-0.1.0b1/src/fasterrag/adapters/llm/base.py +233 -0
  130. fasterrag-0.1.0b1/src/fasterrag/adapters/llm/cohere.py +163 -0
  131. fasterrag-0.1.0b1/src/fasterrag/adapters/llm/factory.py +82 -0
  132. fasterrag-0.1.0b1/src/fasterrag/adapters/llm/ollama.py +126 -0
  133. fasterrag-0.1.0b1/src/fasterrag/adapters/llm/openai.py +136 -0
  134. fasterrag-0.1.0b1/src/fasterrag/adapters/plugin_api.py +66 -0
  135. fasterrag-0.1.0b1/src/fasterrag/adapters/vectordb/__init__.py +31 -0
  136. fasterrag-0.1.0b1/src/fasterrag/adapters/vectordb/base.py +339 -0
  137. fasterrag-0.1.0b1/src/fasterrag/adapters/vectordb/chroma.py +1169 -0
  138. fasterrag-0.1.0b1/src/fasterrag/adapters/vectordb/factory.py +110 -0
  139. fasterrag-0.1.0b1/src/fasterrag/adapters/vectordb/milvus.py +1251 -0
  140. fasterrag-0.1.0b1/src/fasterrag/adapters/vectordb/pgvector.py +1288 -0
  141. fasterrag-0.1.0b1/src/fasterrag/adapters/vectordb/qdrant.py +756 -0
  142. fasterrag-0.1.0b1/src/fasterrag/adapters/vectordb/weaviate.py +1409 -0
  143. fasterrag-0.1.0b1/src/fasterrag/api/__init__.py +6 -0
  144. fasterrag-0.1.0b1/src/fasterrag/api/admin.py +158 -0
  145. fasterrag-0.1.0b1/src/fasterrag/api/auth.py +695 -0
  146. fasterrag-0.1.0b1/src/fasterrag/api/collections.py +215 -0
  147. fasterrag-0.1.0b1/src/fasterrag/api/dependencies.py +129 -0
  148. fasterrag-0.1.0b1/src/fasterrag/api/health.py +110 -0
  149. fasterrag-0.1.0b1/src/fasterrag/api/idempotency.py +290 -0
  150. fasterrag-0.1.0b1/src/fasterrag/api/ingest.py +279 -0
  151. fasterrag-0.1.0b1/src/fasterrag/api/limits.py +106 -0
  152. fasterrag-0.1.0b1/src/fasterrag/api/main.py +393 -0
  153. fasterrag-0.1.0b1/src/fasterrag/api/metrics.py +95 -0
  154. fasterrag-0.1.0b1/src/fasterrag/api/problems.py +217 -0
  155. fasterrag-0.1.0b1/src/fasterrag/api/query.py +201 -0
  156. fasterrag-0.1.0b1/src/fasterrag/api/schemas.py +130 -0
  157. fasterrag-0.1.0b1/src/fasterrag/api/traces.py +133 -0
  158. fasterrag-0.1.0b1/src/fasterrag/chunking.py +34 -0
  159. fasterrag-0.1.0b1/src/fasterrag/cli/__init__.py +13 -0
  160. fasterrag-0.1.0b1/src/fasterrag/cli/commands/__init__.py +6 -0
  161. fasterrag-0.1.0b1/src/fasterrag/cli/commands/autopilot.py +141 -0
  162. fasterrag-0.1.0b1/src/fasterrag/cli/commands/backup.py +93 -0
  163. fasterrag-0.1.0b1/src/fasterrag/cli/commands/benchmark.py +229 -0
  164. fasterrag-0.1.0b1/src/fasterrag/cli/commands/diagnostics.py +360 -0
  165. fasterrag-0.1.0b1/src/fasterrag/cli/commands/documents.py +87 -0
  166. fasterrag-0.1.0b1/src/fasterrag/cli/commands/infrastructure.py +125 -0
  167. fasterrag-0.1.0b1/src/fasterrag/cli/commands/pipeline.py +865 -0
  168. fasterrag-0.1.0b1/src/fasterrag/cli/commands/portability.py +112 -0
  169. fasterrag-0.1.0b1/src/fasterrag/cli/commands/processes.py +159 -0
  170. fasterrag-0.1.0b1/src/fasterrag/cli/commands/replay.py +115 -0
  171. fasterrag-0.1.0b1/src/fasterrag/cli/commands/traces.py +55 -0
  172. fasterrag-0.1.0b1/src/fasterrag/cli/main.py +149 -0
  173. fasterrag-0.1.0b1/src/fasterrag/cli/output.py +82 -0
  174. fasterrag-0.1.0b1/src/fasterrag/cli/parser.py +508 -0
  175. fasterrag-0.1.0b1/src/fasterrag/cli/settings.py +39 -0
  176. fasterrag-0.1.0b1/src/fasterrag/cli/sources.py +96 -0
  177. fasterrag-0.1.0b1/src/fasterrag/config/__init__.py +13 -0
  178. fasterrag-0.1.0b1/src/fasterrag/config/loader.py +289 -0
  179. fasterrag-0.1.0b1/src/fasterrag/config/plugins.py +118 -0
  180. fasterrag-0.1.0b1/src/fasterrag/config/schema.py +1089 -0
  181. fasterrag-0.1.0b1/src/fasterrag/config/template.py +82 -0
  182. fasterrag-0.1.0b1/src/fasterrag/core/__init__.py +6 -0
  183. fasterrag-0.1.0b1/src/fasterrag/core/breaker.py +251 -0
  184. fasterrag-0.1.0b1/src/fasterrag/core/cache/__init__.py +127 -0
  185. fasterrag-0.1.0b1/src/fasterrag/core/cache/embedding.py +308 -0
  186. fasterrag-0.1.0b1/src/fasterrag/core/cache/redis.py +287 -0
  187. fasterrag-0.1.0b1/src/fasterrag/core/cache/semantic.py +235 -0
  188. fasterrag-0.1.0b1/src/fasterrag/core/cache/stats.py +71 -0
  189. fasterrag-0.1.0b1/src/fasterrag/core/cache/store.py +419 -0
  190. fasterrag-0.1.0b1/src/fasterrag/core/chunking/__init__.py +87 -0
  191. fasterrag-0.1.0b1/src/fasterrag/core/chunking/enrichment.py +157 -0
  192. fasterrag-0.1.0b1/src/fasterrag/core/chunking/fixed.py +63 -0
  193. fasterrag-0.1.0b1/src/fasterrag/core/chunking/late.py +56 -0
  194. fasterrag-0.1.0b1/src/fasterrag/core/chunking/layout.py +120 -0
  195. fasterrag-0.1.0b1/src/fasterrag/core/chunking/models.py +235 -0
  196. fasterrag-0.1.0b1/src/fasterrag/core/chunking/pooling.py +154 -0
  197. fasterrag-0.1.0b1/src/fasterrag/core/chunking/recursive.py +131 -0
  198. fasterrag-0.1.0b1/src/fasterrag/core/chunking/semantic.py +205 -0
  199. fasterrag-0.1.0b1/src/fasterrag/core/chunking/tokenizer.py +113 -0
  200. fasterrag-0.1.0b1/src/fasterrag/core/confinement.py +52 -0
  201. fasterrag-0.1.0b1/src/fasterrag/core/context.py +208 -0
  202. fasterrag-0.1.0b1/src/fasterrag/core/evals/__init__.py +33 -0
  203. fasterrag-0.1.0b1/src/fasterrag/core/evals/generator.py +241 -0
  204. fasterrag-0.1.0b1/src/fasterrag/core/evals/golden.py +138 -0
  205. fasterrag-0.1.0b1/src/fasterrag/core/evals/harness.py +149 -0
  206. fasterrag-0.1.0b1/src/fasterrag/core/evals/metrics.py +67 -0
  207. fasterrag-0.1.0b1/src/fasterrag/core/faithfulness.py +176 -0
  208. fasterrag-0.1.0b1/src/fasterrag/core/generation.py +175 -0
  209. fasterrag-0.1.0b1/src/fasterrag/core/identity.py +202 -0
  210. fasterrag-0.1.0b1/src/fasterrag/core/parsing/__init__.py +215 -0
  211. fasterrag-0.1.0b1/src/fasterrag/core/parsing/docx.py +210 -0
  212. fasterrag-0.1.0b1/src/fasterrag/core/parsing/html.py +118 -0
  213. fasterrag-0.1.0b1/src/fasterrag/core/parsing/markdown.py +120 -0
  214. fasterrag-0.1.0b1/src/fasterrag/core/parsing/models.py +211 -0
  215. fasterrag-0.1.0b1/src/fasterrag/core/parsing/options.py +55 -0
  216. fasterrag-0.1.0b1/src/fasterrag/core/parsing/pdf.py +242 -0
  217. fasterrag-0.1.0b1/src/fasterrag/core/parsing/plaintext.py +97 -0
  218. fasterrag-0.1.0b1/src/fasterrag/core/parsing/tabular.py +191 -0
  219. fasterrag-0.1.0b1/src/fasterrag/core/rate_limit.py +259 -0
  220. fasterrag-0.1.0b1/src/fasterrag/core/rerank/__init__.py +9 -0
  221. fasterrag-0.1.0b1/src/fasterrag/core/rerank/cross_encoder.py +226 -0
  222. fasterrag-0.1.0b1/src/fasterrag/core/retrieval/__init__.py +30 -0
  223. fasterrag-0.1.0b1/src/fasterrag/core/retrieval/bm25.py +175 -0
  224. fasterrag-0.1.0b1/src/fasterrag/core/retrieval/fusion.py +121 -0
  225. fasterrag-0.1.0b1/src/fasterrag/core/retrieval/models.py +51 -0
  226. fasterrag-0.1.0b1/src/fasterrag/core/tenancy.py +19 -0
  227. fasterrag-0.1.0b1/src/fasterrag/core/tracing.py +199 -0
  228. fasterrag-0.1.0b1/src/fasterrag/data/config.local.yaml +30 -0
  229. fasterrag-0.1.0b1/src/fasterrag/errors.py +321 -0
  230. fasterrag-0.1.0b1/src/fasterrag/evals.py +31 -0
  231. fasterrag-0.1.0b1/src/fasterrag/facade.py +432 -0
  232. fasterrag-0.1.0b1/src/fasterrag/observability/__init__.py +6 -0
  233. fasterrag-0.1.0b1/src/fasterrag/observability/dashboard.py +195 -0
  234. fasterrag-0.1.0b1/src/fasterrag/observability/langfuse_export.py +220 -0
  235. fasterrag-0.1.0b1/src/fasterrag/observability/logging.py +263 -0
  236. fasterrag-0.1.0b1/src/fasterrag/observability/metrics.py +466 -0
  237. fasterrag-0.1.0b1/src/fasterrag/observability/otel_export.py +267 -0
  238. fasterrag-0.1.0b1/src/fasterrag/observability/otel_metrics.py +248 -0
  239. fasterrag-0.1.0b1/src/fasterrag/parsing.py +28 -0
  240. fasterrag-0.1.0b1/src/fasterrag/py.typed +0 -0
  241. fasterrag-0.1.0b1/src/fasterrag/rerank.py +9 -0
  242. fasterrag-0.1.0b1/src/fasterrag/retrieval.py +21 -0
  243. fasterrag-0.1.0b1/src/fasterrag/services/__init__.py +6 -0
  244. fasterrag-0.1.0b1/src/fasterrag/services/archive.py +385 -0
  245. fasterrag-0.1.0b1/src/fasterrag/services/archive_import.py +1077 -0
  246. fasterrag-0.1.0b1/src/fasterrag/services/autopilot.py +864 -0
  247. fasterrag-0.1.0b1/src/fasterrag/services/backfill.py +407 -0
  248. fasterrag-0.1.0b1/src/fasterrag/services/backup.py +331 -0
  249. fasterrag-0.1.0b1/src/fasterrag/services/benchmark.py +305 -0
  250. fasterrag-0.1.0b1/src/fasterrag/services/budget.py +250 -0
  251. fasterrag-0.1.0b1/src/fasterrag/services/ci_status.py +533 -0
  252. fasterrag-0.1.0b1/src/fasterrag/services/composition.py +133 -0
  253. fasterrag-0.1.0b1/src/fasterrag/services/degradation.py +135 -0
  254. fasterrag-0.1.0b1/src/fasterrag/services/doctor.py +708 -0
  255. fasterrag-0.1.0b1/src/fasterrag/services/documents.py +300 -0
  256. fasterrag-0.1.0b1/src/fasterrag/services/embedding_router.py +68 -0
  257. fasterrag-0.1.0b1/src/fasterrag/services/estimation.py +489 -0
  258. fasterrag-0.1.0b1/src/fasterrag/services/evaluation.py +206 -0
  259. fasterrag-0.1.0b1/src/fasterrag/services/generation.py +1299 -0
  260. fasterrag-0.1.0b1/src/fasterrag/services/golden_sets.py +96 -0
  261. fasterrag-0.1.0b1/src/fasterrag/services/grafana.py +542 -0
  262. fasterrag-0.1.0b1/src/fasterrag/services/idempotency.py +616 -0
  263. fasterrag-0.1.0b1/src/fasterrag/services/ingestion.py +534 -0
  264. fasterrag-0.1.0b1/src/fasterrag/services/jobs.py +358 -0
  265. fasterrag-0.1.0b1/src/fasterrag/services/journal.py +1217 -0
  266. fasterrag-0.1.0b1/src/fasterrag/services/langfuse.py +465 -0
  267. fasterrag-0.1.0b1/src/fasterrag/services/lockfile.py +443 -0
  268. fasterrag-0.1.0b1/src/fasterrag/services/provision_registry.py +111 -0
  269. fasterrag-0.1.0b1/src/fasterrag/services/provisioning.py +452 -0
  270. fasterrag-0.1.0b1/src/fasterrag/services/querying.py +387 -0
  271. fasterrag-0.1.0b1/src/fasterrag/services/regression.py +410 -0
  272. fasterrag-0.1.0b1/src/fasterrag/services/reindex.py +424 -0
  273. fasterrag-0.1.0b1/src/fasterrag/services/replay.py +191 -0
  274. fasterrag-0.1.0b1/src/fasterrag/services/sources.py +523 -0
  275. fasterrag-0.1.0b1/src/fasterrag/services/tenancy.py +98 -0
  276. fasterrag-0.1.0b1/src/fasterrag/services/traces.py +333 -0
  277. fasterrag-0.1.0b1/src/fasterrag/sync.py +322 -0
  278. fasterrag-0.1.0b1/src/fasterrag/workers/__init__.py +34 -0
  279. fasterrag-0.1.0b1/src/fasterrag/workers/cpu_pool.py +925 -0
  280. fasterrag-0.1.0b1/src/fasterrag/workers/embed_pool.py +484 -0
  281. fasterrag-0.1.0b1/src/fasterrag/workers/indexer.py +211 -0
  282. fasterrag-0.1.0b1/src/fasterrag/workers/ledger.py +179 -0
  283. fasterrag-0.1.0b1/src/fasterrag/workers/queues.py +197 -0
  284. fasterrag-0.1.0b1/tests/__init__.py +0 -0
  285. fasterrag-0.1.0b1/tests/chaos/conftest.py +42 -0
  286. fasterrag-0.1.0b1/tests/chaos/disk_quota_probe.py +127 -0
  287. fasterrag-0.1.0b1/tests/chaos/interrupted_job_server.py +111 -0
  288. fasterrag-0.1.0b1/tests/chaos/test_chaos.py +397 -0
  289. fasterrag-0.1.0b1/tests/chaos/test_config_mutation.py +468 -0
  290. fasterrag-0.1.0b1/tests/chaos/test_generation_faults.py +290 -0
  291. fasterrag-0.1.0b1/tests/chaos/test_network_partition.py +375 -0
  292. fasterrag-0.1.0b1/tests/chaos/test_process_kill.py +124 -0
  293. fasterrag-0.1.0b1/tests/chaos/test_real_faults.py +370 -0
  294. fasterrag-0.1.0b1/tests/chaos/test_reindex_under_load.py +652 -0
  295. fasterrag-0.1.0b1/tests/chaos/test_reranker_down.py +373 -0
  296. fasterrag-0.1.0b1/tests/conftest.py +255 -0
  297. fasterrag-0.1.0b1/tests/contract/__init__.py +0 -0
  298. fasterrag-0.1.0b1/tests/contract/vectordb.py +711 -0
  299. fasterrag-0.1.0b1/tests/eval/__init__.py +0 -0
  300. fasterrag-0.1.0b1/tests/eval/datasets/handbook/baseline.json +35 -0
  301. fasterrag-0.1.0b1/tests/eval/datasets/handbook/corpus/expenses.md +8 -0
  302. fasterrag-0.1.0b1/tests/eval/datasets/handbook/corpus/incidents.md +9 -0
  303. fasterrag-0.1.0b1/tests/eval/datasets/handbook/corpus/leave-policy.md +9 -0
  304. fasterrag-0.1.0b1/tests/eval/datasets/handbook/corpus/onboarding.md +9 -0
  305. fasterrag-0.1.0b1/tests/eval/datasets/handbook/corpus/procurement.md +8 -0
  306. fasterrag-0.1.0b1/tests/eval/datasets/handbook/corpus/security.md +9 -0
  307. fasterrag-0.1.0b1/tests/eval/datasets/handbook/golden.jsonl +15 -0
  308. fasterrag-0.1.0b1/tests/eval/datasets/injection/cases.jsonl +8 -0
  309. fasterrag-0.1.0b1/tests/eval/datasets/policies/baseline.json +35 -0
  310. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/access-administrator.md +18 -0
  311. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/access-analyst.md +18 -0
  312. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/access-contractor.md +18 -0
  313. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/access-engineer.md +18 -0
  314. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/expenses-uk-2025.md +20 -0
  315. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/expenses-uk-2026.md +20 -0
  316. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/expenses-us-2025.md +20 -0
  317. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/expenses-us-2026.md +20 -0
  318. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/parental-leave-uk-2025.md +18 -0
  319. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/parental-leave-uk-2026.md +18 -0
  320. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/parental-leave-us-2025.md +18 -0
  321. fasterrag-0.1.0b1/tests/eval/datasets/policies/corpus/parental-leave-us-2026.md +18 -0
  322. fasterrag-0.1.0b1/tests/eval/datasets/policies/golden.jsonl +19 -0
  323. fasterrag-0.1.0b1/tests/eval/test_handbook_baseline.py +188 -0
  324. fasterrag-0.1.0b1/tests/eval/test_live_provider_gating.py +302 -0
  325. fasterrag-0.1.0b1/tests/eval/test_live_providers.py +537 -0
  326. fasterrag-0.1.0b1/tests/eval/test_prompt_injection_live.py +139 -0
  327. fasterrag-0.1.0b1/tests/eval/test_real_models.py +145 -0
  328. fasterrag-0.1.0b1/tests/integration/__init__.py +0 -0
  329. fasterrag-0.1.0b1/tests/integration/chroma/__init__.py +0 -0
  330. fasterrag-0.1.0b1/tests/integration/chroma/conftest.py +50 -0
  331. fasterrag-0.1.0b1/tests/integration/chroma/test_chroma_contract.py +394 -0
  332. fasterrag-0.1.0b1/tests/integration/conftest.py +122 -0
  333. fasterrag-0.1.0b1/tests/integration/milvus/__init__.py +0 -0
  334. fasterrag-0.1.0b1/tests/integration/milvus/conftest.py +107 -0
  335. fasterrag-0.1.0b1/tests/integration/milvus/test_milvus_contract.py +252 -0
  336. fasterrag-0.1.0b1/tests/integration/pgvector/__init__.py +0 -0
  337. fasterrag-0.1.0b1/tests/integration/pgvector/conftest.py +143 -0
  338. fasterrag-0.1.0b1/tests/integration/pgvector/test_pgvector_contract.py +217 -0
  339. fasterrag-0.1.0b1/tests/integration/pgvector/test_pgvector_pooling.py +290 -0
  340. fasterrag-0.1.0b1/tests/integration/test_archive_round_trip.py +451 -0
  341. fasterrag-0.1.0b1/tests/integration/test_doctor_fix.py +135 -0
  342. fasterrag-0.1.0b1/tests/integration/test_ingest_pipeline.py +354 -0
  343. fasterrag-0.1.0b1/tests/integration/test_keyword_leg_write_paths.py +113 -0
  344. fasterrag-0.1.0b1/tests/integration/test_qdrant_contract.py +97 -0
  345. fasterrag-0.1.0b1/tests/integration/test_redis_cache.py +200 -0
  346. fasterrag-0.1.0b1/tests/integration/test_redis_rate_limit.py +170 -0
  347. fasterrag-0.1.0b1/tests/integration/test_redis_rate_limit_server.py +261 -0
  348. fasterrag-0.1.0b1/tests/integration/weaviate/__init__.py +0 -0
  349. fasterrag-0.1.0b1/tests/integration/weaviate/conftest.py +140 -0
  350. fasterrag-0.1.0b1/tests/integration/weaviate/test_weaviate_contract.py +449 -0
  351. fasterrag-0.1.0b1/tests/load/__init__.py +6 -0
  352. fasterrag-0.1.0b1/tests/load/harness.py +936 -0
  353. fasterrag-0.1.0b1/tests/load/locustfile.py +332 -0
  354. fasterrag-0.1.0b1/tests/load/preflight.py +222 -0
  355. fasterrag-0.1.0b1/tests/load/requirements.txt +10 -0
  356. fasterrag-0.1.0b1/tests/load/run_load.py +323 -0
  357. fasterrag-0.1.0b1/tests/load/test_load_harness.py +399 -0
  358. fasterrag-0.1.0b1/tests/load/test_saturation.py +173 -0
  359. fasterrag-0.1.0b1/tests/soak/__init__.py +7 -0
  360. fasterrag-0.1.0b1/tests/soak/conftest.py +19 -0
  361. fasterrag-0.1.0b1/tests/soak/test_soak.py +148 -0
  362. fasterrag-0.1.0b1/tests/soak/test_trend.py +216 -0
  363. fasterrag-0.1.0b1/tests/soak/trend.py +191 -0
  364. fasterrag-0.1.0b1/tests/soak/workload.py +263 -0
  365. fasterrag-0.1.0b1/tests/support/__init__.py +1 -0
  366. fasterrag-0.1.0b1/tests/support/acme_plugin.py +266 -0
  367. fasterrag-0.1.0b1/tests/support/archive.py +38 -0
  368. fasterrag-0.1.0b1/tests/support/doc_examples.py +42 -0
  369. fasterrag-0.1.0b1/tests/support/plugins.py +48 -0
  370. fasterrag-0.1.0b1/tests/test_coverage_records_contexts.py +164 -0
  371. fasterrag-0.1.0b1/tests/test_git_environment_is_clean.py +50 -0
  372. fasterrag-0.1.0b1/tests/test_runs_record_the_tree_they_measured.py +65 -0
  373. fasterrag-0.1.0b1/tests/test_suite_targets_this_checkout.py +34 -0
  374. fasterrag-0.1.0b1/tests/unit/__init__.py +0 -0
  375. fasterrag-0.1.0b1/tests/unit/adapters/__init__.py +0 -0
  376. fasterrag-0.1.0b1/tests/unit/adapters/embeddings/__init__.py +0 -0
  377. fasterrag-0.1.0b1/tests/unit/adapters/embeddings/conftest.py +78 -0
  378. fasterrag-0.1.0b1/tests/unit/adapters/embeddings/test_base.py +128 -0
  379. fasterrag-0.1.0b1/tests/unit/adapters/embeddings/test_huggingface.py +159 -0
  380. fasterrag-0.1.0b1/tests/unit/adapters/embeddings/test_providers.py +110 -0
  381. fasterrag-0.1.0b1/tests/unit/adapters/embeddings/test_real_clients.py +245 -0
  382. fasterrag-0.1.0b1/tests/unit/adapters/embeddings/test_refusals.py +133 -0
  383. fasterrag-0.1.0b1/tests/unit/adapters/embeddings/test_tiering.py +179 -0
  384. fasterrag-0.1.0b1/tests/unit/adapters/embeddings/test_usage.py +319 -0
  385. fasterrag-0.1.0b1/tests/unit/adapters/llm/__init__.py +0 -0
  386. fasterrag-0.1.0b1/tests/unit/adapters/llm/test_call_paths.py +306 -0
  387. fasterrag-0.1.0b1/tests/unit/adapters/llm/test_call_paths_cohere_ollama.py +363 -0
  388. fasterrag-0.1.0b1/tests/unit/adapters/llm/test_llm.py +271 -0
  389. fasterrag-0.1.0b1/tests/unit/adapters/llm/test_real_clients.py +349 -0
  390. fasterrag-0.1.0b1/tests/unit/adapters/recordings.py +285 -0
  391. fasterrag-0.1.0b1/tests/unit/adapters/test_adapter_api_version.py +150 -0
  392. fasterrag-0.1.0b1/tests/unit/adapters/test_base.py +63 -0
  393. fasterrag-0.1.0b1/tests/unit/adapters/test_chroma.py +455 -0
  394. fasterrag-0.1.0b1/tests/unit/adapters/test_factory.py +100 -0
  395. fasterrag-0.1.0b1/tests/unit/adapters/test_pgvector.py +600 -0
  396. fasterrag-0.1.0b1/tests/unit/adapters/test_plugin_entry_points.py +86 -0
  397. fasterrag-0.1.0b1/tests/unit/adapters/test_qdrant.py +596 -0
  398. fasterrag-0.1.0b1/tests/unit/adapters/test_qdrant_iterate.py +194 -0
  399. fasterrag-0.1.0b1/tests/unit/adapters/vectordb/__init__.py +0 -0
  400. fasterrag-0.1.0b1/tests/unit/adapters/vectordb/test_milvus.py +1356 -0
  401. fasterrag-0.1.0b1/tests/unit/adapters/vectordb/test_weaviate.py +1767 -0
  402. fasterrag-0.1.0b1/tests/unit/api/__init__.py +0 -0
  403. fasterrag-0.1.0b1/tests/unit/api/conftest.py +125 -0
  404. fasterrag-0.1.0b1/tests/unit/api/test_admin_doctor.py +285 -0
  405. fasterrag-0.1.0b1/tests/unit/api/test_auth.py +765 -0
  406. fasterrag-0.1.0b1/tests/unit/api/test_body_limits.py +150 -0
  407. fasterrag-0.1.0b1/tests/unit/api/test_collections.py +313 -0
  408. fasterrag-0.1.0b1/tests/unit/api/test_documents.py +244 -0
  409. fasterrag-0.1.0b1/tests/unit/api/test_estimate_endpoint.py +90 -0
  410. fasterrag-0.1.0b1/tests/unit/api/test_health.py +203 -0
  411. fasterrag-0.1.0b1/tests/unit/api/test_idempotency.py +539 -0
  412. fasterrag-0.1.0b1/tests/unit/api/test_ingest.py +368 -0
  413. fasterrag-0.1.0b1/tests/unit/api/test_ingest_keyword_leg.py +186 -0
  414. fasterrag-0.1.0b1/tests/unit/api/test_ingest_lifecycle.py +267 -0
  415. fasterrag-0.1.0b1/tests/unit/api/test_ingest_tenancy.py +73 -0
  416. fasterrag-0.1.0b1/tests/unit/api/test_key_never_leaks.py +191 -0
  417. fasterrag-0.1.0b1/tests/unit/api/test_key_rotation.py +292 -0
  418. fasterrag-0.1.0b1/tests/unit/api/test_metrics_endpoint.py +96 -0
  419. fasterrag-0.1.0b1/tests/unit/api/test_portability_endpoints.py +300 -0
  420. fasterrag-0.1.0b1/tests/unit/api/test_problems.py +174 -0
  421. fasterrag-0.1.0b1/tests/unit/api/test_provisioning_endpoints.py +247 -0
  422. fasterrag-0.1.0b1/tests/unit/api/test_query.py +578 -0
  423. fasterrag-0.1.0b1/tests/unit/api/test_query_tenancy.py +433 -0
  424. fasterrag-0.1.0b1/tests/unit/api/test_response_headers.py +100 -0
  425. fasterrag-0.1.0b1/tests/unit/api/test_route_scopes.py +167 -0
  426. fasterrag-0.1.0b1/tests/unit/api/test_schema_fields_are_wired.py +55 -0
  427. fasterrag-0.1.0b1/tests/unit/api/test_shared_llm.py +149 -0
  428. fasterrag-0.1.0b1/tests/unit/api/test_shutdown_drain.py +223 -0
  429. fasterrag-0.1.0b1/tests/unit/api/test_startup_reaper.py +119 -0
  430. fasterrag-0.1.0b1/tests/unit/api/test_tenancy.py +155 -0
  431. fasterrag-0.1.0b1/tests/unit/api/test_traces_endpoints.py +246 -0
  432. fasterrag-0.1.0b1/tests/unit/cli/__init__.py +0 -0
  433. fasterrag-0.1.0b1/tests/unit/cli/conftest.py +97 -0
  434. fasterrag-0.1.0b1/tests/unit/cli/test_autopilot_commands.py +482 -0
  435. fasterrag-0.1.0b1/tests/unit/cli/test_backup_commands.py +269 -0
  436. fasterrag-0.1.0b1/tests/unit/cli/test_benchmark_command.py +445 -0
  437. fasterrag-0.1.0b1/tests/unit/cli/test_ci_status_command.py +285 -0
  438. fasterrag-0.1.0b1/tests/unit/cli/test_cli.py +377 -0
  439. fasterrag-0.1.0b1/tests/unit/cli/test_config_init.py +185 -0
  440. fasterrag-0.1.0b1/tests/unit/cli/test_config_show.py +100 -0
  441. fasterrag-0.1.0b1/tests/unit/cli/test_dispatch.py +170 -0
  442. fasterrag-0.1.0b1/tests/unit/cli/test_doctor_command.py +98 -0
  443. fasterrag-0.1.0b1/tests/unit/cli/test_document_listing.py +365 -0
  444. fasterrag-0.1.0b1/tests/unit/cli/test_documents_command.py +184 -0
  445. fasterrag-0.1.0b1/tests/unit/cli/test_flags_are_wired.py +100 -0
  446. fasterrag-0.1.0b1/tests/unit/cli/test_ingest_resume.py +151 -0
  447. fasterrag-0.1.0b1/tests/unit/cli/test_pipeline_commands.py +1095 -0
  448. fasterrag-0.1.0b1/tests/unit/cli/test_portability_commands.py +443 -0
  449. fasterrag-0.1.0b1/tests/unit/cli/test_processes_commands.py +367 -0
  450. fasterrag-0.1.0b1/tests/unit/cli/test_query_stream.py +195 -0
  451. fasterrag-0.1.0b1/tests/unit/cli/test_replay_command.py +228 -0
  452. fasterrag-0.1.0b1/tests/unit/cli/test_sources.py +179 -0
  453. fasterrag-0.1.0b1/tests/unit/cli/test_traces_command.py +127 -0
  454. fasterrag-0.1.0b1/tests/unit/config/__init__.py +0 -0
  455. fasterrag-0.1.0b1/tests/unit/config/conftest.py +33 -0
  456. fasterrag-0.1.0b1/tests/unit/config/test_canonical_config.py +26 -0
  457. fasterrag-0.1.0b1/tests/unit/config/test_loader.py +376 -0
  458. fasterrag-0.1.0b1/tests/unit/config/test_overrides.py +271 -0
  459. fasterrag-0.1.0b1/tests/unit/config/test_planned_providers.py +80 -0
  460. fasterrag-0.1.0b1/tests/unit/config/test_plugin_keys.py +128 -0
  461. fasterrag-0.1.0b1/tests/unit/config/test_plugins.py +67 -0
  462. fasterrag-0.1.0b1/tests/unit/config/test_provider_names.py +210 -0
  463. fasterrag-0.1.0b1/tests/unit/config/test_schema.py +335 -0
  464. fasterrag-0.1.0b1/tests/unit/config/test_settings_are_wired.py +110 -0
  465. fasterrag-0.1.0b1/tests/unit/config/test_tuning_settings.py +211 -0
  466. fasterrag-0.1.0b1/tests/unit/conftest.py +60 -0
  467. fasterrag-0.1.0b1/tests/unit/core/__init__.py +0 -0
  468. fasterrag-0.1.0b1/tests/unit/core/cache/test_embedding.py +584 -0
  469. fasterrag-0.1.0b1/tests/unit/core/cache/test_redis_store.py +633 -0
  470. fasterrag-0.1.0b1/tests/unit/core/cache/test_semantic.py +277 -0
  471. fasterrag-0.1.0b1/tests/unit/core/cache/test_semantic_properties.py +139 -0
  472. fasterrag-0.1.0b1/tests/unit/core/cache/test_store.py +531 -0
  473. fasterrag-0.1.0b1/tests/unit/core/chunking/__init__.py +0 -0
  474. fasterrag-0.1.0b1/tests/unit/core/chunking/test_boundaries.py +762 -0
  475. fasterrag-0.1.0b1/tests/unit/core/chunking/test_enrichment.py +223 -0
  476. fasterrag-0.1.0b1/tests/unit/core/chunking/test_invariants.py +315 -0
  477. fasterrag-0.1.0b1/tests/unit/core/chunking/test_pooling.py +489 -0
  478. fasterrag-0.1.0b1/tests/unit/core/chunking/test_strategies.py +186 -0
  479. fasterrag-0.1.0b1/tests/unit/core/chunking/test_tokenizer.py +338 -0
  480. fasterrag-0.1.0b1/tests/unit/core/evals/__init__.py +0 -0
  481. fasterrag-0.1.0b1/tests/unit/core/evals/test_generator.py +294 -0
  482. fasterrag-0.1.0b1/tests/unit/core/evals/test_harness.py +247 -0
  483. fasterrag-0.1.0b1/tests/unit/core/evals/test_metrics.py +78 -0
  484. fasterrag-0.1.0b1/tests/unit/core/parsing/__init__.py +0 -0
  485. fasterrag-0.1.0b1/tests/unit/core/parsing/conftest.py +233 -0
  486. fasterrag-0.1.0b1/tests/unit/core/parsing/golden/csv.json +24 -0
  487. fasterrag-0.1.0b1/tests/unit/core/parsing/golden/docx.json +62 -0
  488. fasterrag-0.1.0b1/tests/unit/core/parsing/golden/html.json +62 -0
  489. fasterrag-0.1.0b1/tests/unit/core/parsing/golden/json.json +57 -0
  490. fasterrag-0.1.0b1/tests/unit/core/parsing/golden/markdown.json +75 -0
  491. fasterrag-0.1.0b1/tests/unit/core/parsing/golden/pdf.json +47 -0
  492. fasterrag-0.1.0b1/tests/unit/core/parsing/test_golden.py +48 -0
  493. fasterrag-0.1.0b1/tests/unit/core/parsing/test_hostile_inputs.py +222 -0
  494. fasterrag-0.1.0b1/tests/unit/core/parsing/test_invariants.py +814 -0
  495. fasterrag-0.1.0b1/tests/unit/core/parsing/test_models.py +126 -0
  496. fasterrag-0.1.0b1/tests/unit/core/parsing/test_parser_correctness.py +211 -0
  497. fasterrag-0.1.0b1/tests/unit/core/parsing/test_parsers.py +210 -0
  498. fasterrag-0.1.0b1/tests/unit/core/parsing/test_registry.py +73 -0
  499. fasterrag-0.1.0b1/tests/unit/core/parsing/test_source_formats.py +201 -0
  500. fasterrag-0.1.0b1/tests/unit/core/rerank/__init__.py +0 -0
  501. fasterrag-0.1.0b1/tests/unit/core/rerank/test_cross_encoder.py +243 -0
  502. fasterrag-0.1.0b1/tests/unit/core/retrieval/__init__.py +0 -0
  503. fasterrag-0.1.0b1/tests/unit/core/retrieval/test_bm25.py +187 -0
  504. fasterrag-0.1.0b1/tests/unit/core/retrieval/test_bm25_properties.py +340 -0
  505. fasterrag-0.1.0b1/tests/unit/core/retrieval/test_fusion.py +182 -0
  506. fasterrag-0.1.0b1/tests/unit/core/retrieval/test_fusion_properties.py +356 -0
  507. fasterrag-0.1.0b1/tests/unit/core/retrieval/test_models.py +69 -0
  508. fasterrag-0.1.0b1/tests/unit/core/test_breaker.py +556 -0
  509. fasterrag-0.1.0b1/tests/unit/core/test_context.py +238 -0
  510. fasterrag-0.1.0b1/tests/unit/core/test_context_properties.py +119 -0
  511. fasterrag-0.1.0b1/tests/unit/core/test_faithfulness.py +184 -0
  512. fasterrag-0.1.0b1/tests/unit/core/test_generation.py +264 -0
  513. fasterrag-0.1.0b1/tests/unit/core/test_identity.py +249 -0
  514. fasterrag-0.1.0b1/tests/unit/core/test_identity_properties.py +199 -0
  515. fasterrag-0.1.0b1/tests/unit/core/test_prompt_injection.py +132 -0
  516. fasterrag-0.1.0b1/tests/unit/core/test_rate_limit.py +409 -0
  517. fasterrag-0.1.0b1/tests/unit/core/test_redis_timeouts.py +129 -0
  518. fasterrag-0.1.0b1/tests/unit/core/test_source_normalisation.py +67 -0
  519. fasterrag-0.1.0b1/tests/unit/docs/__init__.py +0 -0
  520. fasterrag-0.1.0b1/tests/unit/docs/test_cli_flag_parity.py +347 -0
  521. fasterrag-0.1.0b1/tests/unit/docs/test_counts_parity.py +287 -0
  522. fasterrag-0.1.0b1/tests/unit/docs/test_doc_examples.py +181 -0
  523. fasterrag-0.1.0b1/tests/unit/docs/test_error_table_parity.py +142 -0
  524. fasterrag-0.1.0b1/tests/unit/docs/test_fmea_parity.py +222 -0
  525. fasterrag-0.1.0b1/tests/unit/docs/test_internals_parity.py +222 -0
  526. fasterrag-0.1.0b1/tests/unit/docs/test_python_api_parity.py +615 -0
  527. fasterrag-0.1.0b1/tests/unit/docs/test_reference_parity.py +131 -0
  528. fasterrag-0.1.0b1/tests/unit/docs/test_site_pages.py +111 -0
  529. fasterrag-0.1.0b1/tests/unit/docs/test_superlative_scan.py +256 -0
  530. fasterrag-0.1.0b1/tests/unit/examples/__init__.py +0 -0
  531. fasterrag-0.1.0b1/tests/unit/examples/test_sample_plugin.py +87 -0
  532. fasterrag-0.1.0b1/tests/unit/observability/__init__.py +0 -0
  533. fasterrag-0.1.0b1/tests/unit/observability/test_dashboard.py +211 -0
  534. fasterrag-0.1.0b1/tests/unit/observability/test_langfuse_export.py +290 -0
  535. fasterrag-0.1.0b1/tests/unit/observability/test_logging.py +221 -0
  536. fasterrag-0.1.0b1/tests/unit/observability/test_metrics.py +191 -0
  537. fasterrag-0.1.0b1/tests/unit/observability/test_otel_export.py +342 -0
  538. fasterrag-0.1.0b1/tests/unit/observability/test_otel_metrics.py +296 -0
  539. fasterrag-0.1.0b1/tests/unit/scripts/test_check_blockers.py +103 -0
  540. fasterrag-0.1.0b1/tests/unit/scripts/test_check_claims.py +394 -0
  541. fasterrag-0.1.0b1/tests/unit/scripts/test_check_comments.py +333 -0
  542. fasterrag-0.1.0b1/tests/unit/scripts/test_check_contract_ran.py +165 -0
  543. fasterrag-0.1.0b1/tests/unit/scripts/test_check_doc_truth.py +142 -0
  544. fasterrag-0.1.0b1/tests/unit/scripts/test_check_error_taxonomy.py +735 -0
  545. fasterrag-0.1.0b1/tests/unit/scripts/test_check_layering.py +267 -0
  546. fasterrag-0.1.0b1/tests/unit/scripts/test_check_licences.py +141 -0
  547. fasterrag-0.1.0b1/tests/unit/scripts/test_check_must_run.py +223 -0
  548. fasterrag-0.1.0b1/tests/unit/scripts/test_check_openapi_drift.py +63 -0
  549. fasterrag-0.1.0b1/tests/unit/scripts/test_check_package.py +139 -0
  550. fasterrag-0.1.0b1/tests/unit/scripts/test_check_task_ids.py +300 -0
  551. fasterrag-0.1.0b1/tests/unit/scripts/test_check_unbuilt_claims.py +761 -0
  552. fasterrag-0.1.0b1/tests/unit/scripts/test_checkout_bootstrap.py +97 -0
  553. fasterrag-0.1.0b1/tests/unit/scripts/test_compare_nightly.py +379 -0
  554. fasterrag-0.1.0b1/tests/unit/scripts/test_measure_cost_governor.py +154 -0
  555. fasterrag-0.1.0b1/tests/unit/scripts/test_measure_rerank.py +506 -0
  556. fasterrag-0.1.0b1/tests/unit/scripts/test_mutation_run.py +1419 -0
  557. fasterrag-0.1.0b1/tests/unit/scripts/test_release_pipeline.py +307 -0
  558. fasterrag-0.1.0b1/tests/unit/scripts/test_stamp_release.py +185 -0
  559. fasterrag-0.1.0b1/tests/unit/scripts/test_trim_docstrings.py +129 -0
  560. fasterrag-0.1.0b1/tests/unit/services/__init__.py +0 -0
  561. fasterrag-0.1.0b1/tests/unit/services/test_archive_export.py +618 -0
  562. fasterrag-0.1.0b1/tests/unit/services/test_archive_format_spec.py +307 -0
  563. fasterrag-0.1.0b1/tests/unit/services/test_archive_import.py +1846 -0
  564. fasterrag-0.1.0b1/tests/unit/services/test_autopilot.py +905 -0
  565. fasterrag-0.1.0b1/tests/unit/services/test_backup.py +233 -0
  566. fasterrag-0.1.0b1/tests/unit/services/test_benchmark.py +177 -0
  567. fasterrag-0.1.0b1/tests/unit/services/test_budget.py +136 -0
  568. fasterrag-0.1.0b1/tests/unit/services/test_budget_accounting.py +157 -0
  569. fasterrag-0.1.0b1/tests/unit/services/test_budget_sharing.py +405 -0
  570. fasterrag-0.1.0b1/tests/unit/services/test_ci_status.py +846 -0
  571. fasterrag-0.1.0b1/tests/unit/services/test_comparability.py +251 -0
  572. fasterrag-0.1.0b1/tests/unit/services/test_composition.py +402 -0
  573. fasterrag-0.1.0b1/tests/unit/services/test_cost_metric.py +216 -0
  574. fasterrag-0.1.0b1/tests/unit/services/test_cost_window.py +187 -0
  575. fasterrag-0.1.0b1/tests/unit/services/test_dead_letter_retry.py +134 -0
  576. fasterrag-0.1.0b1/tests/unit/services/test_degradation.py +118 -0
  577. fasterrag-0.1.0b1/tests/unit/services/test_doctor.py +676 -0
  578. fasterrag-0.1.0b1/tests/unit/services/test_document_updates.py +380 -0
  579. fasterrag-0.1.0b1/tests/unit/services/test_documents.py +341 -0
  580. fasterrag-0.1.0b1/tests/unit/services/test_enrichment_estimate.py +94 -0
  581. fasterrag-0.1.0b1/tests/unit/services/test_estimation.py +183 -0
  582. fasterrag-0.1.0b1/tests/unit/services/test_faithfulness_eval.py +39 -0
  583. fasterrag-0.1.0b1/tests/unit/services/test_generation.py +1906 -0
  584. fasterrag-0.1.0b1/tests/unit/services/test_grafana.py +233 -0
  585. fasterrag-0.1.0b1/tests/unit/services/test_idempotency_reclaim.py +278 -0
  586. fasterrag-0.1.0b1/tests/unit/services/test_identity_drift.py +67 -0
  587. fasterrag-0.1.0b1/tests/unit/services/test_ingest_size_refusals.py +69 -0
  588. fasterrag-0.1.0b1/tests/unit/services/test_ingestion.py +699 -0
  589. fasterrag-0.1.0b1/tests/unit/services/test_job_interruption.py +355 -0
  590. fasterrag-0.1.0b1/tests/unit/services/test_job_reaper.py +181 -0
  591. fasterrag-0.1.0b1/tests/unit/services/test_jobs.py +187 -0
  592. fasterrag-0.1.0b1/tests/unit/services/test_journal.py +766 -0
  593. fasterrag-0.1.0b1/tests/unit/services/test_journal_crashes.py +372 -0
  594. fasterrag-0.1.0b1/tests/unit/services/test_journal_retention.py +240 -0
  595. fasterrag-0.1.0b1/tests/unit/services/test_langfuse.py +201 -0
  596. fasterrag-0.1.0b1/tests/unit/services/test_lockfile.py +258 -0
  597. fasterrag-0.1.0b1/tests/unit/services/test_policies_fixture.py +69 -0
  598. fasterrag-0.1.0b1/tests/unit/services/test_provisioning.py +365 -0
  599. fasterrag-0.1.0b1/tests/unit/services/test_query_edges.py +294 -0
  600. fasterrag-0.1.0b1/tests/unit/services/test_query_lock.py +197 -0
  601. fasterrag-0.1.0b1/tests/unit/services/test_querying.py +522 -0
  602. fasterrag-0.1.0b1/tests/unit/services/test_regression.py +393 -0
  603. fasterrag-0.1.0b1/tests/unit/services/test_reindex.py +461 -0
  604. fasterrag-0.1.0b1/tests/unit/services/test_replay.py +345 -0
  605. fasterrag-0.1.0b1/tests/unit/services/test_sources.py +959 -0
  606. fasterrag-0.1.0b1/tests/unit/services/test_tenancy_properties.py +129 -0
  607. fasterrag-0.1.0b1/tests/unit/services/test_tenancy_scoping.py +66 -0
  608. fasterrag-0.1.0b1/tests/unit/services/test_tenant_backfill.py +378 -0
  609. fasterrag-0.1.0b1/tests/unit/services/test_traces.py +181 -0
  610. fasterrag-0.1.0b1/tests/unit/test_archive_support.py +68 -0
  611. fasterrag-0.1.0b1/tests/unit/test_commit_message_rule.py +81 -0
  612. fasterrag-0.1.0b1/tests/unit/test_errors.py +100 -0
  613. fasterrag-0.1.0b1/tests/unit/test_facade.py +346 -0
  614. fasterrag-0.1.0b1/tests/unit/test_facade_documents.py +161 -0
  615. fasterrag-0.1.0b1/tests/unit/test_facade_tenancy.py +253 -0
  616. fasterrag-0.1.0b1/tests/unit/test_facade_traces.py +97 -0
  617. fasterrag-0.1.0b1/tests/unit/test_no_blocking_journal_in_async.py +129 -0
  618. fasterrag-0.1.0b1/tests/unit/test_no_network_clients.py +43 -0
  619. fasterrag-0.1.0b1/tests/unit/test_package.py +6 -0
  620. fasterrag-0.1.0b1/tests/unit/test_plugin_selection.py +145 -0
  621. fasterrag-0.1.0b1/tests/unit/test_public_surface.py +153 -0
  622. fasterrag-0.1.0b1/tests/unit/test_slim_core.py +148 -0
  623. fasterrag-0.1.0b1/tests/unit/test_sync_facade.py +180 -0
  624. fasterrag-0.1.0b1/tests/unit/workers/__init__.py +0 -0
  625. fasterrag-0.1.0b1/tests/unit/workers/process_targets.py +106 -0
  626. fasterrag-0.1.0b1/tests/unit/workers/test_cpu_pool.py +719 -0
  627. fasterrag-0.1.0b1/tests/unit/workers/test_embed_pool.py +445 -0
  628. fasterrag-0.1.0b1/tests/unit/workers/test_embed_size_refusals.py +221 -0
  629. fasterrag-0.1.0b1/tests/unit/workers/test_embed_usage_accounting.py +239 -0
  630. fasterrag-0.1.0b1/tests/unit/workers/test_enrichment_wiring.py +92 -0
  631. fasterrag-0.1.0b1/tests/unit/workers/test_hostile_documents.py +187 -0
  632. fasterrag-0.1.0b1/tests/unit/workers/test_indexer.py +357 -0
  633. fasterrag-0.1.0b1/tests/unit/workers/test_late_chunking.py +184 -0
  634. fasterrag-0.1.0b1/tests/unit/workers/test_ledger.py +264 -0
  635. fasterrag-0.1.0b1/tests/unit/workers/test_queue_depth_metric.py +56 -0
  636. fasterrag-0.1.0b1/tests/unit/workers/test_queues.py +114 -0
  637. fasterrag-0.1.0b1/tests/unit/workers/test_real_process_pool.py +449 -0
@@ -0,0 +1,34 @@
1
+ # fasterRag secrets template — copy to .env and fill in what your config.yaml references.
2
+ # NEVER commit .env. config.yaml refers to these by NAME only (e.g. api_key_env: OPENAI_API_KEY).
3
+ # Full policy: docs/security.md. Variable inventory must stay in sync with that document.
4
+
5
+ # Control-plane auth (required when security.auth: true). The placeholder below is refused at
6
+ # startup, as is any key under 16 characters: replace it before turning auth on.
7
+ FASTERRAG_API_KEY=change-me
8
+
9
+ # Vector DB — Qdrant reference adapter (server enforces via QDRANT__SERVICE__API_KEY)
10
+ QDRANT_API_KEY=change-me
11
+
12
+ # Embedding / LLM providers — only those referenced by config.yaml are required at startup
13
+ OPENAI_API_KEY=
14
+ ANTHROPIC_API_KEY=
15
+ COHERE_API_KEY=
16
+
17
+ # pgvector adapter DSN (only when vector_db.provider: pgvector)
18
+ PGVECTOR_DSN=
19
+
20
+ # Langfuse self-hosted stack (observability.langfuse: true).
21
+ # Generated ONCE by the provisioner if absent — preserve forever after creation:
22
+ # changing SALT/ENCRYPTION_KEY/NEXTAUTH_SECRET invalidates existing Langfuse credentials.
23
+ NEXTAUTH_SECRET=
24
+ SALT=
25
+ ENCRYPTION_KEY=
26
+ LANGFUSE_INIT_ORG_ID=
27
+ LANGFUSE_INIT_ORG_NAME=
28
+ LANGFUSE_INIT_PROJECT_ID=
29
+ LANGFUSE_INIT_PROJECT_NAME=
30
+ LANGFUSE_INIT_PROJECT_PUBLIC_KEY=
31
+ LANGFUSE_INIT_PROJECT_SECRET_KEY=
32
+ LANGFUSE_INIT_USER_EMAIL=
33
+ LANGFUSE_INIT_USER_PASSWORD=
34
+ LANGFUSE_INIT_USER_NAME=
@@ -0,0 +1,457 @@
1
+ """Fail when a locked dependency's licence is not one fasterRag has agreed to depend on.
2
+
3
+ fasterRag is Apache-2.0 (ADR-0008). Nothing checked what its dependencies were licensed
4
+ under: a transitive package could move to a licence the project cannot ship beside, the
5
+ lockfile would be refreshed, and every gate would stay green (TASK-0496).
6
+
7
+ The check reads the *lockfile*, as the dependency audit beside it does, and for the same
8
+ reason: an installed environment is whatever one platform resolved today, while ``uv.lock``
9
+ names every package any platform can be given. The Linux-only CUDA runtime that ``torch``
10
+ pulls in is the case that matters — it is absent from a Windows or macOS environment and is
11
+ the least permissively licensed thing in the set.
12
+
13
+ For each ``name==version`` in the export it asks PyPI what that release declares, in the
14
+ order a reader would trust it:
15
+
16
+ 1. ``License-Expression`` (PEP 639) — an SPDX expression, evaluated as written: every side
17
+ of an ``AND`` must be allowed, one side of an ``OR`` is enough, and a ``WITH`` exception
18
+ only ever adds permissions, so it is judged by the licence it modifies.
19
+ 2. the free-text ``License`` field, when it is an SPDX expression or a spelling in
20
+ ``ALIASES``. A whole licence text pasted into that field is neither, and falls through.
21
+ 3. the ``License ::`` Trove classifiers, all of which must be allowed. A classifier pair
22
+ does not say whether it means *and* or *or*, so it is read as *and*.
23
+
24
+ A package is then one of four things, and only the first two pass:
25
+
26
+ * **allowed** — everything it declares is in ``ALLOWED``;
27
+ * **reviewed** — named in ``REVIEWED`` with the licence it declared when it was reviewed
28
+ and the reason it is accepted. The exception holds only while the declaration is
29
+ unchanged, and an entry whose package has left the lockfile fails as stale;
30
+ * **refused** — it declares a licence that is not allowed;
31
+ * **undetermined** — it declares nothing this script can read. That is a failure, not a
32
+ pass: "the tool could not tell" is how an unlicensed package gets in.
33
+
34
+ ``ALLOWED`` is deliberately short. It holds what the current dependency set uses, not every
35
+ permissive licence that exists, so a licence nobody here has looked at arrives as a red
36
+ build and a one-line decision rather than silently.
37
+
38
+ Usage::
39
+
40
+ uv export --format requirements-txt --locked --all-extras --no-extra dev \
41
+ --no-emit-project --no-hashes -o locked.txt
42
+ python .github/scripts/check_licences.py locked.txt [--summary report.md]
43
+
44
+ Exit codes: ``0`` every package is allowed or reviewed, ``1`` at least one is refused,
45
+ undetermined or stale, ``2`` bad arguments, ``3`` PyPI could not be asked.
46
+ """
47
+
48
+ from __future__ import annotations
49
+
50
+ import argparse
51
+ import json
52
+ import re
53
+ import sys
54
+ import time
55
+ import urllib.error
56
+ import urllib.request
57
+ from collections import Counter
58
+ from concurrent.futures import ThreadPoolExecutor
59
+ from dataclasses import dataclass
60
+ from pathlib import Path
61
+ from typing import Any, Final
62
+
63
+ ALLOWED: Final[frozenset[str]] = frozenset(
64
+ {
65
+ "0BSD",
66
+ "Apache-2.0",
67
+ "BSD-2-Clause",
68
+ "BSD-3-Clause",
69
+ "BSL-1.0",
70
+ "CC0-1.0",
71
+ "CNRI-Python",
72
+ "ISC",
73
+ "MIT",
74
+ "MIT-0",
75
+ "MIT-CMU",
76
+ "MPL-2.0",
77
+ "PSF-2.0",
78
+ "Zlib",
79
+ }
80
+ )
81
+ """SPDX identifiers a dependency may carry.
82
+
83
+ All permissive except MPL-2.0, which is copyleft per *file*: it binds whoever modifies or
84
+ redistributes those files, and fasterRag does neither — it names the package as a
85
+ dependency and pip fetches it.
86
+ """
87
+
88
+ ALIASES: Final[dict[str, str]] = {
89
+ "3-clause bsd license": "BSD-3-Clause",
90
+ "apache 2.0": "Apache-2.0",
91
+ "apache 2.0 license": "Apache-2.0",
92
+ "apache license 2.0": "Apache-2.0",
93
+ "apache license version 2.0": "Apache-2.0",
94
+ "apache license, version 2.0": "Apache-2.0",
95
+ "bsd": "BSD-3-Clause",
96
+ "bsd 3-clause": "BSD-3-Clause",
97
+ "isc license": "ISC",
98
+ "mit license": "MIT",
99
+ }
100
+ """Free-text spellings of an allowed licence, lower-cased. Each one was seen in the lockfile.
101
+
102
+ A bare ``BSD`` does not say which; every variant it can mean is allowed.
103
+ """
104
+
105
+ CLASSIFIERS: Final[dict[str, str]] = {
106
+ "License :: OSI Approved :: Apache Software License": "Apache-2.0",
107
+ "License :: OSI Approved :: BSD License": "BSD-3-Clause",
108
+ "License :: OSI Approved :: ISC License (ISCL)": "ISC",
109
+ "License :: OSI Approved :: MIT License": "MIT",
110
+ "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)": "MPL-2.0",
111
+ "License :: OSI Approved :: Python Software Foundation License": "PSF-2.0",
112
+ }
113
+ """Trove classifiers and the SPDX identifier each is judged as.
114
+
115
+ ``BSD License`` does not say which BSD; every variant it can mean is allowed, so it is
116
+ judged as the three-clause one.
117
+ """
118
+
119
+ _NVIDIA: Final = (
120
+ "NVIDIA's proprietary CUDA runtime, pulled in by torch on Linux only and only by the "
121
+ "huggingface, rerank and all extras. fasterRag does not distribute it: pip downloads it "
122
+ "from PyPI under NVIDIA's own terms, and the published container image is built without "
123
+ "extras, so it holds none of it. Most of these packages declare no licence at all."
124
+ )
125
+ _PSYCOPG: Final = (
126
+ "LGPL-3.0, which is not on the allow-list. Accepted because it is reached only through "
127
+ "the optional pgvector extra, is imported and never copied or modified, and is not in "
128
+ "the published container image; the LGPL's terms fall on whoever redistributes psycopg."
129
+ )
130
+
131
+
132
+ @dataclass(frozen=True)
133
+ class Review:
134
+ """An accepted exception: what the package declared when reviewed, and why it passes."""
135
+
136
+ declared: frozenset[str]
137
+ reason: str
138
+
139
+
140
+ REVIEWED: Final[dict[str, Review]] = {
141
+ "psycopg": Review(frozenset({"LGPL-3.0-only"}), _PSYCOPG),
142
+ "psycopg-binary": Review(frozenset({"LGPL-3.0-only"}), _PSYCOPG),
143
+ "psycopg-pool": Review(frozenset({"LGPL-3.0-only"}), _PSYCOPG),
144
+ "pypdfium2": Review(
145
+ frozenset({"BSD-3-Clause, Apache-2.0, dependency licenses"}),
146
+ "Declared in prose rather than SPDX: the bindings are BSD-3-Clause or Apache-2.0, and "
147
+ "the PDFium binary in the wheel carries its own third-party notices, which this "
148
+ "script cannot read. A core dependency, through pdfplumber.",
149
+ ),
150
+ }
151
+ """Exceptions by exact package name. Delete an entry when its package leaves the lockfile."""
152
+
153
+ REVIEWED_FAMILIES: Final[dict[str, Review]] = {
154
+ "nvidia-": Review(
155
+ frozenset({"", "NVIDIA Proprietary Software", "LicenseRef-NVIDIA-Proprietary"}), _NVIDIA
156
+ ),
157
+ "cuda-toolkit": Review(frozenset({""}), _NVIDIA),
158
+ }
159
+ """Exceptions by name prefix, for a vendor family torch renames with every CUDA release."""
160
+
161
+ _REQUIREMENT: Final = re.compile(r"^(?P<name>[A-Za-z0-9][A-Za-z0-9._-]*)==(?P<version>[^\s;]+)")
162
+ _TOKEN: Final = re.compile(r"\s*(\(|\)|[A-Za-z0-9.+-]+)")
163
+ _OPERATORS: Final = frozenset({"AND", "OR", "WITH"})
164
+ _ATTEMPTS: Final = 4
165
+ _INDEX: Final = "https://pypi.org/pypi"
166
+
167
+
168
+ class UnreadableError(Exception):
169
+ """A string that is not an SPDX expression."""
170
+
171
+
172
+ class UnreachableError(Exception):
173
+ """PyPI could not be asked about a release, so nothing is known about it."""
174
+
175
+
176
+ @dataclass(frozen=True)
177
+ class Verdict:
178
+ """What one locked release declares and what the policy makes of it."""
179
+
180
+ name: str
181
+ version: str
182
+ declared: str
183
+ status: str
184
+ detail: str = ""
185
+
186
+ @property
187
+ def passed(self) -> bool:
188
+ """Whether this release may stay in the lockfile."""
189
+ return self.status in {"allowed", "reviewed"}
190
+
191
+
192
+ def normalise(name: str) -> str:
193
+ """Return a distribution name in its canonical form (PEP 503)."""
194
+ return re.sub(r"[-_.]+", "-", name).lower()
195
+
196
+
197
+ def locked_releases(export: str) -> list[tuple[str, str]]:
198
+ """Return every ``(name, version)`` a requirements export pins, once each."""
199
+ found = {
200
+ (normalise(match.group("name")), match.group("version"))
201
+ for match in map(_REQUIREMENT.match, export.splitlines())
202
+ if match is not None
203
+ }
204
+ return sorted(found)
205
+
206
+
207
+ def _tokens(expression: str) -> list[str]:
208
+ tokens = []
209
+ position = 0
210
+ while position < len(expression.rstrip()):
211
+ found = _TOKEN.match(expression, position)
212
+ if found is None:
213
+ raise UnreadableError(expression)
214
+ tokens.append(found.group(1))
215
+ position = found.end()
216
+ return tokens
217
+
218
+
219
+ def disallowed(expression: str) -> list[str]:
220
+ """Return the identifiers that stop an SPDX expression being allowed; empty when it is."""
221
+ tokens = _tokens(expression)
222
+ position = 0
223
+
224
+ def peek() -> str | None:
225
+ return tokens[position] if position < len(tokens) else None
226
+
227
+ def take() -> str:
228
+ nonlocal position
229
+ if position >= len(tokens):
230
+ raise UnreadableError(expression)
231
+ position += 1
232
+ return tokens[position - 1]
233
+
234
+ def licence() -> list[str]:
235
+ token = take()
236
+ if token == "(":
237
+ inner = either()
238
+ if take() != ")":
239
+ raise UnreadableError(expression)
240
+ return inner
241
+ if token == ")" or token.upper() in _OPERATORS:
242
+ raise UnreadableError(expression)
243
+ if (peek() or "").upper() == "WITH":
244
+ take()
245
+ if take().upper() in _OPERATORS | {"(", ")"}:
246
+ raise UnreadableError(expression)
247
+ return [] if token in ALLOWED else [token]
248
+
249
+ def every() -> list[str]:
250
+ problems = licence()
251
+ while (peek() or "").upper() == "AND":
252
+ take()
253
+ problems = [*problems, *licence()]
254
+ return problems
255
+
256
+ def either() -> list[str]:
257
+ problems = every()
258
+ while (peek() or "").upper() == "OR":
259
+ take()
260
+ alternative = every()
261
+ problems = [] if not problems or not alternative else [*problems, *alternative]
262
+ return problems
263
+
264
+ result = either()
265
+ if position != len(tokens) or not tokens:
266
+ raise UnreadableError(expression)
267
+ return result
268
+
269
+
270
+ def declaration(metadata: dict[str, Any]) -> tuple[str, str | None]:
271
+ """Return what a release declares, verbatim, and the SPDX expression it is judged as."""
272
+ expression = (metadata.get("license_expression") or "").strip()
273
+ if expression:
274
+ return expression, expression
275
+
276
+ text = (metadata.get("license") or "").strip()
277
+ classifiers = [
278
+ entry for entry in metadata.get("classifiers") or [] if entry.startswith("License ::")
279
+ ]
280
+ if text.lower() in ALIASES:
281
+ return text, ALIASES[text.lower()]
282
+ if text and "\n" not in text:
283
+ try:
284
+ disallowed(text)
285
+ except UnreadableError:
286
+ if not classifiers:
287
+ return text, None
288
+ else:
289
+ return text, text
290
+
291
+ if classifiers:
292
+ shown = "; ".join(classifiers)
293
+ if all(entry in CLASSIFIERS for entry in classifiers):
294
+ return shown, " AND ".join(CLASSIFIERS[entry] for entry in classifiers)
295
+ return shown, None
296
+ return (text.splitlines()[0][:60] + " ...", None) if text else ("", None)
297
+
298
+
299
+ def review_for(name: str) -> Review | None:
300
+ """Return the exception that covers a package, by exact name first and family second."""
301
+ if name in REVIEWED:
302
+ return REVIEWED[name]
303
+ return next(
304
+ (review for prefix, review in REVIEWED_FAMILIES.items() if name.startswith(prefix)), None
305
+ )
306
+
307
+
308
+ def judge(name: str, version: str, metadata: dict[str, Any]) -> Verdict:
309
+ """Return the verdict on one release from the metadata PyPI holds for it."""
310
+ declared, expression = declaration(metadata)
311
+ review = review_for(name)
312
+ if review is not None:
313
+ if declared in review.declared:
314
+ return Verdict(name, version, declared, "reviewed", review.reason)
315
+ return Verdict(
316
+ name,
317
+ version,
318
+ declared,
319
+ "refused",
320
+ f"reviewed when it declared {sorted(review.declared)}; it now declares something "
321
+ "else, so the exception no longer applies and the review must be repeated",
322
+ )
323
+ if expression is None:
324
+ return Verdict(
325
+ name,
326
+ version,
327
+ declared,
328
+ "undetermined",
329
+ "declares no licence this check can read; read the project's own licence file, "
330
+ "then add an alias or a reviewed exception",
331
+ )
332
+ try:
333
+ problems = disallowed(expression)
334
+ except UnreadableError:
335
+ return Verdict(name, version, declared, "undetermined", "not an SPDX expression")
336
+ if problems:
337
+ return Verdict(
338
+ name, version, declared, "refused", f"not on the allow-list: {sorted(set(problems))}"
339
+ )
340
+ return Verdict(name, version, declared, "allowed")
341
+
342
+
343
+ def stale_reviews(names: set[str]) -> list[str]:
344
+ """Return the exceptions that no longer match any locked package."""
345
+ stale = [name for name in REVIEWED if name not in names]
346
+ stale.extend(
347
+ prefix for prefix in REVIEWED_FAMILIES if not any(name.startswith(prefix) for name in names)
348
+ )
349
+ return sorted(stale)
350
+
351
+
352
+ def fetch_metadata(name: str, version: str) -> dict[str, Any]:
353
+ """Return the ``info`` mapping PyPI holds for one release, retrying a failed request."""
354
+ request = urllib.request.Request(
355
+ f"{_INDEX}/{name}/{version}/json", headers={"User-Agent": "licence-check"}
356
+ )
357
+ failure = ""
358
+ for attempt in range(_ATTEMPTS):
359
+ try:
360
+ with urllib.request.urlopen(request, timeout=30) as response:
361
+ info: Any = json.load(response)["info"]
362
+ except (urllib.error.URLError, TimeoutError, ValueError, KeyError) as error:
363
+ failure = f"{type(error).__name__}: {error}"
364
+ time.sleep(2**attempt)
365
+ else:
366
+ if isinstance(info, dict):
367
+ return info
368
+ failure = "the response carried no info mapping"
369
+ raise UnreachableError(f"{name}=={version}: {failure}")
370
+
371
+
372
+ def report(verdicts: list[Verdict], stale: list[str]) -> str:
373
+ """Return the Markdown report: counts, then every package that is not plainly allowed."""
374
+ counts = Counter(verdict.status for verdict in verdicts)
375
+ declared = Counter(verdict.declared or "(nothing declared)" for verdict in verdicts)
376
+ lines = [
377
+ "## Dependency licences",
378
+ "",
379
+ f"{len(verdicts)} locked releases: "
380
+ + ", ".join(f"{count} {status}" for status, count in sorted(counts.items())),
381
+ "",
382
+ "| Declared | Releases |",
383
+ "|---|---|",
384
+ *(f"| `{text}` | {count} |" for text, count in declared.most_common()),
385
+ "",
386
+ "| Package | Declared | Status | Why |",
387
+ "|---|---|---|---|",
388
+ ]
389
+ lines.extend(
390
+ f"| {verdict.name} {verdict.version} | `{verdict.declared or '(nothing declared)'}` "
391
+ f"| {verdict.status} | {verdict.detail} |"
392
+ for verdict in verdicts
393
+ if verdict.status != "allowed"
394
+ )
395
+ lines.extend(
396
+ f"| {name} | | stale | reviewed, but no longer in the lockfile |" for name in stale
397
+ )
398
+ return "\n".join(lines) + "\n"
399
+
400
+
401
+ def main(argv: list[str] | None = None) -> int:
402
+ """Judge every release in a requirements export; report, and exit per the docstring."""
403
+ parser = argparse.ArgumentParser(
404
+ description="Check locked dependencies against a licence list."
405
+ )
406
+ parser.add_argument("export", help="a `uv export --format requirements-txt` file")
407
+ parser.add_argument("--summary", help="append the Markdown report to this file")
408
+ arguments = parser.parse_args(argv)
409
+
410
+ releases = locked_releases(Path(arguments.export).read_text(encoding="utf-8"))
411
+ if not releases:
412
+ sys.stderr.write(f"check_licences: {arguments.export} pins nothing\n")
413
+ return 2
414
+ try:
415
+ with ThreadPoolExecutor(max_workers=8) as pool:
416
+ metadata = list(pool.map(lambda release: fetch_metadata(*release), releases))
417
+ except UnreachableError as error:
418
+ sys.stderr.write(f"check_licences: could not ask PyPI about {error}\n")
419
+ return 3
420
+
421
+ verdicts = [
422
+ judge(name, version, info) for (name, version), info in zip(releases, metadata, strict=True)
423
+ ]
424
+ stale = stale_reviews({name for name, _ in releases})
425
+ document = report(verdicts, stale)
426
+ if arguments.summary:
427
+ with Path(arguments.summary).open("a", encoding="utf-8") as handle:
428
+ handle.write(document)
429
+
430
+ failures = [verdict for verdict in verdicts if not verdict.passed]
431
+ for verdict in verdicts:
432
+ if verdict.status == "reviewed":
433
+ sys.stdout.write(
434
+ f"reviewed: {verdict.name} {verdict.version} "
435
+ f"declares {verdict.declared or 'nothing'!r}\n"
436
+ )
437
+ for verdict in failures:
438
+ sys.stderr.write(
439
+ f"check_licences: {verdict.status}: {verdict.name} {verdict.version} "
440
+ f"declares {verdict.declared or 'nothing'!r} -- {verdict.detail}\n"
441
+ )
442
+ for name in stale:
443
+ sys.stderr.write(
444
+ f"check_licences: stale: the reviewed exception for {name!r} matches nothing in "
445
+ "the lockfile; delete it\n"
446
+ )
447
+ counts = Counter(verdict.status for verdict in verdicts)
448
+ sys.stdout.write(
449
+ f"licences: {len(verdicts)} locked releases -- "
450
+ + ", ".join(f"{count} {status}" for status, count in sorted(counts.items()))
451
+ + "\n"
452
+ )
453
+ return 1 if failures or stale else 0
454
+
455
+
456
+ if __name__ == "__main__":
457
+ sys.exit(main())