classifyre-cli 0.4.134__tar.gz → 0.4.135__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 (333) hide show
  1. classifyre_cli-0.4.135/.turbo/turbo-build.log +3 -0
  2. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/PKG-INFO +1 -1
  3. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/package.json +1 -1
  4. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/pyproject.toml +1 -1
  5. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/scripts/generate_models.py +14 -6
  6. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/detector.py +16 -1
  7. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/__init__.py +2 -0
  8. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_factory.py +8 -0
  9. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_llm.py +22 -0
  10. classifyre_cli-0.4.135/src/detectors/custom/runners/_tag.py +85 -0
  11. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/engine_version.py +1 -1
  12. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/graph/edges.py +32 -2
  13. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/main.py +42 -3
  14. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/models/generated_detectors.py +99 -1
  15. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/contract.py +67 -0
  16. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/sdk.py +56 -0
  17. classifyre_cli-0.4.135/src/outputs/base.py +151 -0
  18. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/rest.py +19 -1
  19. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/content_provider.py +3 -1
  20. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/detector_pipeline.py +160 -0
  21. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/parsed_content_provider.py +4 -1
  22. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/base.py +10 -1
  23. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/custom/source.py +41 -6
  24. classifyre_cli-0.4.135/tests/detectors/test_tag_runner.py +89 -0
  25. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/pipeline/test_detector_pipeline.py +83 -0
  26. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_custom_source.py +43 -0
  27. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_custom_source_relationships.py +70 -0
  28. classifyre_cli-0.4.135/tests/test_custom_source_tags_end_to_end.py +137 -0
  29. classifyre_cli-0.4.135/tests/test_detector_scope.py +110 -0
  30. classifyre_cli-0.4.135/tests/test_lineage_loss_reporting.py +159 -0
  31. classifyre_cli-0.4.135/tests/test_notebook_asset_tags.py +50 -0
  32. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/uv.lock +87 -76
  33. classifyre_cli-0.4.134/.turbo/turbo-build.log +0 -3
  34. classifyre_cli-0.4.134/src/outputs/base.py +0 -76
  35. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/.gitignore +0 -0
  36. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/.python-version +0 -0
  37. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/README.md +0 -0
  38. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/main.py +0 -0
  39. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/scripts/generate_runtime_packages.py +0 -0
  40. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/scripts/generate_sdk_completions.py +0 -0
  41. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/__init__.py +0 -0
  42. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/config.py +0 -0
  43. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/__init__.py +0 -0
  44. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/base.py +0 -0
  45. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/broken_links/__init__.py +0 -0
  46. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/broken_links/detector.py +0 -0
  47. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/config.py +0 -0
  48. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/content/__init__.py +0 -0
  49. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/__init__.py +0 -0
  50. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/extractor.py +0 -0
  51. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_base.py +0 -0
  52. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_gliner2.py +0 -0
  53. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_image_classification.py +0 -0
  54. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_object_detection.py +0 -0
  55. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_regex.py +0 -0
  56. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_text_classification.py +0 -0
  57. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/trainer.py +0 -0
  58. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/dependencies.py +0 -0
  59. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/pii/__init__.py +0 -0
  60. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/pii/detector.py +0 -0
  61. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/secrets/__init__.py +0 -0
  62. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/secrets/detector.py +0 -0
  63. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/threat/__init__.py +0 -0
  64. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/threat/code_security_detector.py +0 -0
  65. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/threat/yara_detector.py +0 -0
  66. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/file_evaluation/__init__.py +0 -0
  67. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/file_evaluation/runner.py +0 -0
  68. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/graph/__init__.py +0 -0
  69. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/models/generated_input.py +0 -0
  70. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/models/generated_single_asset_scan_results.py +0 -0
  71. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/__init__.py +0 -0
  72. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/cli.py +0 -0
  73. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/execute.py +0 -0
  74. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/files.py +0 -0
  75. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/groups.py +0 -0
  76. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/outputs.py +0 -0
  77. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/packages.py +0 -0
  78. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/protocol.py +0 -0
  79. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/redact.py +0 -0
  80. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/scaffold.py +0 -0
  81. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/serialize.py +0 -0
  82. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/__init__.py +0 -0
  83. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/console.py +0 -0
  84. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/factory.py +0 -0
  85. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/file.py +0 -0
  86. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/__init__.py +0 -0
  87. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/payload_window.py +0 -0
  88. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/scan_cache.py +0 -0
  89. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/text_artifact.py +0 -0
  90. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/worker_pool.py +0 -0
  91. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/__init__.py +0 -0
  92. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/asset_metadata.py +0 -0
  93. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/atlassian_common.py +0 -0
  94. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/azure_blob_storage/__init__.py +0 -0
  95. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/azure_blob_storage/source.py +0 -0
  96. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/confluence/__init__.py +0 -0
  97. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/confluence/source.py +0 -0
  98. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/custom/__init__.py +0 -0
  99. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/custom/env.py +0 -0
  100. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/custom/runner.py +0 -0
  101. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/databricks/__init__.py +0 -0
  102. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/databricks/source.py +0 -0
  103. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/delta_lake/__init__.py +0 -0
  104. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/delta_lake/source.py +0 -0
  105. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/dependencies.py +0 -0
  106. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/dropbox/__init__.py +0 -0
  107. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/dropbox/auth.py +0 -0
  108. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/dropbox/source.py +0 -0
  109. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/elasticsearch/__init__.py +0 -0
  110. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/elasticsearch/source.py +0 -0
  111. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/email/__init__.py +0 -0
  112. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/email/source.py +0 -0
  113. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/git/__init__.py +0 -0
  114. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/git/source.py +0 -0
  115. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/google_cloud_storage/__init__.py +0 -0
  116. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/google_cloud_storage/source.py +0 -0
  117. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/google_workspace/__init__.py +0 -0
  118. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/google_workspace/source.py +0 -0
  119. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/hive/__init__.py +0 -0
  120. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/hive/source.py +0 -0
  121. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/hugging_face/__init__.py +0 -0
  122. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/hugging_face/source.py +0 -0
  123. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/iceberg/__init__.py +0 -0
  124. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/iceberg/source.py +0 -0
  125. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/jira/__init__.py +0 -0
  126. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/jira/source.py +0 -0
  127. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/kafka/__init__.py +0 -0
  128. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/kafka/rest.py +0 -0
  129. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/kafka/source.py +0 -0
  130. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/lakehouse_base.py +0 -0
  131. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/local_folder/__init__.py +0 -0
  132. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/local_folder/source.py +0 -0
  133. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/meilisearch/__init__.py +0 -0
  134. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/meilisearch/source.py +0 -0
  135. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/microsoft_365/__init__.py +0 -0
  136. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/microsoft_365/source.py +0 -0
  137. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mongodb/__init__.py +0 -0
  138. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mongodb/source.py +0 -0
  139. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mssql/__init__.py +0 -0
  140. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mssql/source.py +0 -0
  141. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mysql/__init__.py +0 -0
  142. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mysql/source.py +0 -0
  143. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/neo4j/__init__.py +0 -0
  144. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/neo4j/source.py +0 -0
  145. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/notion/__init__.py +0 -0
  146. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/notion/client.py +0 -0
  147. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/notion/source.py +0 -0
  148. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/object_storage/base.py +0 -0
  149. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/opensearch/__init__.py +0 -0
  150. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/opensearch/source.py +0 -0
  151. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/oracle/__init__.py +0 -0
  152. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/oracle/source.py +0 -0
  153. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/postgresql/__init__.py +0 -0
  154. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/postgresql/source.py +0 -0
  155. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/powerbi/__init__.py +0 -0
  156. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/powerbi/source.py +0 -0
  157. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/recipe_normalizer.py +0 -0
  158. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/reddit/__init__.py +0 -0
  159. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/reddit/source.py +0 -0
  160. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/s3_client.py +0 -0
  161. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/s3_compatible_storage/README.md +0 -0
  162. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/s3_compatible_storage/__init__.py +0 -0
  163. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/s3_compatible_storage/source.py +0 -0
  164. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/sandbox/__init__.py +0 -0
  165. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/sandbox/source.py +0 -0
  166. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/search_engine_base.py +0 -0
  167. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/servicedesk/__init__.py +0 -0
  168. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/servicedesk/source.py +0 -0
  169. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/slack/__init__.py +0 -0
  170. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/slack/source.py +0 -0
  171. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/snowflake/__init__.py +0 -0
  172. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/snowflake/source.py +0 -0
  173. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/sqlite/__init__.py +0 -0
  174. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/sqlite/source.py +0 -0
  175. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/tableau/__init__.py +0 -0
  176. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/tableau/source.py +0 -0
  177. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/tabular_base.py +0 -0
  178. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/tabular_utils.py +0 -0
  179. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/wordpress/__init__.py +0 -0
  180. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/wordpress/source.py +0 -0
  181. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/youtube/__init__.py +0 -0
  182. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/youtube/source.py +0 -0
  183. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/telemetry.py +0 -0
  184. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/__init__.py +0 -0
  185. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/archive_extraction.py +0 -0
  186. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/content_extraction.py +0 -0
  187. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/dependency_groups.py +0 -0
  188. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/embedded_files.py +0 -0
  189. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/external_video.py +0 -0
  190. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/file_metadata.py +0 -0
  191. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/file_parser.py +0 -0
  192. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/file_to_images.py +0 -0
  193. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/hashing.py +0 -0
  194. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/legacy_office.py +0 -0
  195. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/payload.py +0 -0
  196. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/range_reader.py +0 -0
  197. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/resources.py +0 -0
  198. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/source_files.py +0 -0
  199. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/sql_lineage.py +0 -0
  200. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/torch_runtime.py +0 -0
  201. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/transcription.py +0 -0
  202. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/urn.py +0 -0
  203. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/uv_sync.py +0 -0
  204. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/validation.py +0 -0
  205. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/video_processing.py +0 -0
  206. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/__init__.py +0 -0
  207. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/_lakehouse_fakes.py +0 -0
  208. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/conftest.py +0 -0
  209. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/__init__.py +0 -0
  210. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
  211. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/conftest.py +0 -0
  212. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/content/__init__.py +0 -0
  213. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/__init__.py +0 -0
  214. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/conftest.py +0 -0
  215. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_gliner2_classification.py +0 -0
  216. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_invoice_extraction.py +0 -0
  217. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_llm_runner.py +0 -0
  218. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_pipeline_integration.py +0 -0
  219. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_regex_runner.py +0 -0
  220. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_runner_hardening.py +0 -0
  221. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_transformer_runners.py +0 -0
  222. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/__init__.py +0 -0
  223. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/conftest.py +0 -0
  224. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/sample_invoice.pdf +0 -0
  225. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_chunking.py +0 -0
  226. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_detector.py +0 -0
  227. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
  228. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
  229. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_severity.py +0 -0
  230. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/secrets/__init__.py +0 -0
  231. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/secrets/test_secrets_detector.py +0 -0
  232. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
  233. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_base_detector.py +0 -0
  234. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
  235. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_detector_catalog_commercial.py +0 -0
  236. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_detector_pipeline_types.py +0 -0
  237. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_detector_schema_examples.py +0 -0
  238. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_detector_types.py +0 -0
  239. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_phase2_detectors.py +0 -0
  240. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_registry.py +0 -0
  241. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/threat/__init__.py +0 -0
  242. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/threat/test_code_security_detector.py +0 -0
  243. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/threat/test_yara_detector.py +0 -0
  244. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
  245. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/integration/test_wordpress_links_assets.py +0 -0
  246. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/pipeline/test_detector_outcomes.py +0 -0
  247. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/pipeline/test_worker_pool.py +0 -0
  248. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_arrow_ipc_payloads.py +0 -0
  249. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_assets_metadata_catalog.py +0 -0
  250. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_azure_blob_storage_source.py +0 -0
  251. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_base_source_attachment.py +0 -0
  252. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_base_source_sampling.py +0 -0
  253. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_byte_valued_fields.py +0 -0
  254. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_config.py +0 -0
  255. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_confluence_source.py +0 -0
  256. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_custom_extractor.py +0 -0
  257. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_databricks_source.py +0 -0
  258. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_delta_lake_source.py +0 -0
  259. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_dependency_groups.py +0 -0
  260. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_dropbox_auth_command.py +0 -0
  261. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_dropbox_source.py +0 -0
  262. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_elasticsearch_source.py +0 -0
  263. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_email_source.py +0 -0
  264. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_file_evaluation_runner.py +0 -0
  265. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_git_source.py +0 -0
  266. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_google_cloud_storage_source.py +0 -0
  267. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_google_workspace_source.py +0 -0
  268. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_graph_edges.py +0 -0
  269. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_hashing.py +0 -0
  270. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_hive_source.py +0 -0
  271. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_hugging_face_source.py +0 -0
  272. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_iceberg_source.py +0 -0
  273. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_jira_source.py +0 -0
  274. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_kafka_source.py +0 -0
  275. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_local_folder_source.py +0 -0
  276. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_main.py +0 -0
  277. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_meilisearch_source.py +0 -0
  278. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_microsoft_365_source.py +0 -0
  279. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_mongodb_source.py +0 -0
  280. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_mssql_source.py +0 -0
  281. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_mysql_source.py +0 -0
  282. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_neo4j_source.py +0 -0
  283. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_execute.py +0 -0
  284. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_files.py +0 -0
  285. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_groups.py +0 -0
  286. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_packages.py +0 -0
  287. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_serialize.py +0 -0
  288. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_templates.py +0 -0
  289. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notion_source.py +0 -0
  290. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_object_storage_download_efficiency.py +0 -0
  291. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_opensearch_source.py +0 -0
  292. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_oracle_source.py +0 -0
  293. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_outputs.py +0 -0
  294. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_payload_window.py +0 -0
  295. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_payload_window_parquet.py +0 -0
  296. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_postgresql_source.py +0 -0
  297. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_powerbi_source.py +0 -0
  298. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_range_reading.py +0 -0
  299. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_recipe_normalizer.py +0 -0
  300. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_reddit_source.py +0 -0
  301. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_rest_backpressure.py +0 -0
  302. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_runtime_packages.py +0 -0
  303. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_s3_compatible_storage_source.py +0 -0
  304. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_sampling_automatic.py +0 -0
  305. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_sandbox_source.py +0 -0
  306. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_scan_cache.py +0 -0
  307. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_sdk_completions.py +0 -0
  308. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_servicedesk_source.py +0 -0
  309. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_slack_source.py +0 -0
  310. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_snowflake_source.py +0 -0
  311. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_source_dependency_groups.py +0 -0
  312. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_source_strategy_conformance.py +0 -0
  313. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_sqlite_source.py +0 -0
  314. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_tableau_source.py +0 -0
  315. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_tabular_automatic_sampling.py +0 -0
  316. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_tabular_lineage.py +0 -0
  317. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_tabular_utils.py +0 -0
  318. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_torch_runtime.py +0 -0
  319. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_urn.py +0 -0
  320. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_uv_sync.py +0 -0
  321. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_wordpress_source.py +0 -0
  322. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_youtube_source.py +0 -0
  323. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_youtube_source_integration.py +0 -0
  324. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_archive_extraction.py +0 -0
  325. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_content_extraction.py +0 -0
  326. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_embedded_files.py +0 -0
  327. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_file_metadata.py +0 -0
  328. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_file_parser.py +0 -0
  329. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_file_to_images.py +0 -0
  330. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_legacy_office.py +0 -0
  331. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_payload.py +0 -0
  332. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_transcription.py +0 -0
  333. {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_video_processing.py +0 -0
@@ -0,0 +1,3 @@
1
+ $ uv sync
2
+ Resolved 323 packages in 217ms
3
+ Checked 51 packages in 4ms
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: classifyre-cli
3
- Version: 0.4.134
3
+ Version: 0.4.135
4
4
  Summary: Classifyre CLI — scan and classify unstructured data sources
5
5
  License: MIT
6
6
  Keywords: data,ingestion,metadata,pii,secrets,unstructured
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classifyre/cli",
3
- "version": "0.4.134",
3
+ "version": "0.4.135",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "build": "uv sync",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "classifyre-cli"
3
- version = "0.4.134"
3
+ version = "0.4.135"
4
4
  description = "Classifyre CLI — scan and classify unstructured data sources"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -14,14 +14,19 @@ _PIPELINE_TYPE_DEFAULTS: dict[str, str] = {
14
14
  "GLiNER2PipelineSchema": "GLINER2",
15
15
  "RegexPipelineSchema": "REGEX",
16
16
  "LLMPipelineSchema": "LLM",
17
+ "TagPipelineSchema": "TAG",
17
18
  }
18
19
 
19
20
  # Pipeline schema classes whose `severity` field has a string default from JSON schema
20
21
  # ('info') but must be an enum instance to avoid Pydantic serialization warnings
21
22
  # ("Expected `enum` - serialized value may not be as expected").
23
+ # Maps class name -> the severity member its JSON-schema default names. The enum
24
+ # type is whatever codegen assigned (``Severity``, ``Severity2``, ...), so it is
25
+ # captured from the emitted source rather than hardcoded here.
22
26
  _SEVERITY_ENUM_DEFAULT_CLASSES = {
23
- "LLMPipelineSchema",
24
- "TextClassificationPipelineSchema",
27
+ "LLMPipelineSchema": "info",
28
+ "TextClassificationPipelineSchema": "info",
29
+ "TagPipelineSchema": "medium",
25
30
  }
26
31
 
27
32
 
@@ -37,17 +42,20 @@ def _patch_pipeline_type_defaults(source: str) -> str:
37
42
 
38
43
 
39
44
  def _patch_severity_enum_defaults(source: str) -> str:
40
- """Replace string 'info' severity Field defaults with Severity.info enum instances.
45
+ """Replace string string severity Field defaults with enum instances.
41
46
 
42
47
  datamodel-codegen emits Field('info', ...) from the JSON schema default, but
43
48
  Pydantic v2 warns at serialization time when the stored value is a plain string
44
49
  rather than a Severity enum member. This patch rewrites only the severity field
45
50
  inside each affected class so the fix survives future codegen runs.
46
51
  """
47
- for cls_name in _SEVERITY_ENUM_DEFAULT_CLASSES:
52
+ for cls_name, member in _SEVERITY_ENUM_DEFAULT_CLASSES.items():
48
53
  # Match from the class definition up through the severity Field default string.
49
- pattern = rf"(class {re.escape(cls_name)}\(.*?severity: Severity \| None = Field\(\n\s+)'info'(\s*,)"
50
- replacement = rf"\1Severity.info\2"
54
+ pattern = (
55
+ rf"(class {re.escape(cls_name)}\(.*?severity: (Severity\d*) \| None = Field\(\n\s+)"
56
+ rf"'{re.escape(member)}'(\s*,)"
57
+ )
58
+ replacement = rf"\1\2.{member}\3"
51
59
  source = re.sub(pattern, replacement, source, flags=re.DOTALL)
52
60
  return source
53
61
 
@@ -17,7 +17,12 @@ logger = logging.getLogger(__name__)
17
17
 
18
18
 
19
19
  class CustomDetector(BaseDetector):
20
- """Schema-driven detector backed by a pluggable runner (GLINER2 | REGEX | LLM | transformer)."""
20
+ """Schema-driven detector backed by a pluggable runner.
21
+
22
+ GLINER2 | REGEX | LLM | transformer run over content. TAG does not: it
23
+ produces findings only from tags a CUSTOM connector attached to an asset,
24
+ which the pipeline applies through ``runner.tag_finding``.
25
+ """
21
26
 
22
27
  detector_type = "custom"
23
28
  detector_name = "custom"
@@ -47,5 +52,15 @@ class CustomDetector(BaseDetector):
47
52
  findings = findings[:max_findings]
48
53
  return findings
49
54
 
55
+ @property
56
+ def runner(self) -> BaseRunner:
57
+ """The pipeline strategy behind this detector.
58
+
59
+ Public because the tag pass has to ask what kind of runner this is: a
60
+ TAG detector never reaches ``detect()``, so recognising it is the only
61
+ way its findings get produced at all.
62
+ """
63
+ return self._runner
64
+
50
65
  def get_supported_content_types(self) -> list[str]:
51
66
  return self._runner.get_supported_content_types()
@@ -26,6 +26,7 @@ from ._image_classification import ImageClassificationRunner
26
26
  from ._llm import LLMRunner
27
27
  from ._object_detection import ObjectDetectionRunner
28
28
  from ._regex import RegexRunner, _load_regex_engine
29
+ from ._tag import TagRunner
29
30
  from ._text_classification import TextClassificationRunner, _chunk_text
30
31
 
31
32
  __all__ = [
@@ -39,6 +40,7 @@ __all__ = [
39
40
  "LLMRunner",
40
41
  "ObjectDetectionRunner",
41
42
  "RegexRunner",
43
+ "TagRunner",
42
44
  "TextClassificationRunner",
43
45
  "_apply_classification_validation",
44
46
  "_apply_entity_validation",
@@ -8,6 +8,7 @@ from ....models.generated_detectors import (
8
8
  LLMPipelineSchema,
9
9
  ObjectDetectionPipelineSchema,
10
10
  RegexPipelineSchema,
11
+ TagPipelineSchema,
11
12
  TextClassificationPipelineSchema,
12
13
  )
13
14
  from ._base import BaseRunner
@@ -16,6 +17,7 @@ from ._image_classification import ImageClassificationRunner
16
17
  from ._llm import LLMRunner
17
18
  from ._object_detection import ObjectDetectionRunner
18
19
  from ._regex import RegexRunner
20
+ from ._tag import TagRunner
19
21
  from ._text_classification import TextClassificationRunner
20
22
 
21
23
 
@@ -27,6 +29,7 @@ def create_runner(
27
29
  | TextClassificationPipelineSchema
28
30
  | ImageClassificationPipelineSchema
29
31
  | ObjectDetectionPipelineSchema
32
+ | TagPipelineSchema
30
33
  ),
31
34
  detector_key: str = "",
32
35
  detector_name: str = "",
@@ -42,5 +45,10 @@ def create_runner(
42
45
  return RegexRunner(schema, detector_key, detector_name)
43
46
  if isinstance(schema, LLMPipelineSchema):
44
47
  return LLMRunner(schema, detector_key, detector_name)
48
+ # Branched explicitly, not left to the fall-through below: a TAG schema that
49
+ # reached the GLiNER2 default would try to load a transformer model for a
50
+ # detector that is not supposed to run anything at all.
51
+ if isinstance(schema, TagPipelineSchema):
52
+ return TagRunner(schema, detector_key, detector_name)
45
53
  # GLiNER2PipelineSchema is the default / backward-compat path
46
54
  return GLiNER2Runner(schema, detector_key, detector_name)
@@ -326,12 +326,21 @@ class LLMRunner(BaseRunner):
326
326
  )
327
327
 
328
328
  results: list[DetectionResult] = []
329
+ # Answers the threshold threw away. Recorded because discarding them
330
+ # silently is indistinguishable from the model never answering: at the
331
+ # schema-ish default of 0.55 every call logged "clean" and produced
332
+ # nothing, and the model had in fact answered every time — a free
333
+ # router model is simply systematically under-confident. The finding
334
+ # already carries the model's own confidence, so a threshold applied
335
+ # here only hides that the model was asked at all.
336
+ suppressed: list[tuple[str, float]] = []
329
337
  for entry in label_entries:
330
338
  label = str(entry.get("name", "")).strip()
331
339
  if not label:
332
340
  continue
333
341
  confidence = float(entry.get("confidence", 1.0) or 0.0)
334
342
  if confidence < threshold:
343
+ suppressed.append((label, confidence))
335
344
  continue
336
345
  severity = _resolve_pipeline_severity(label, schema.severity_map, default_severity)
337
346
  matched = str(entry.get("matched_content") or "").strip() or snippet[:320]
@@ -361,6 +370,19 @@ class LLMRunner(BaseRunner):
361
370
  )
362
371
  )
363
372
 
373
+ if suppressed:
374
+ logger.warning(
375
+ "%s: confidence_threshold %.2f discarded %d model answer(s): %s%s. "
376
+ "The model answered — lower confidence_threshold (0 records every "
377
+ "answer with its confidence, to filter downstream) if this is not "
378
+ "what you meant.",
379
+ self._detector_key,
380
+ threshold,
381
+ len(suppressed),
382
+ ", ".join(f"{label}={score:.2f}" for label, score in suppressed[:5]),
383
+ " and more" if len(suppressed) > 5 else "",
384
+ )
385
+
364
386
  results.sort(key=lambda r: r.confidence, reverse=True)
365
387
  return results
366
388
 
@@ -0,0 +1,85 @@
1
+ """Tag pipeline runner — the one runner that never looks at content.
2
+
3
+ A tag is an assertion, not a detection. The source system already knows that a
4
+ table holds cardholder data or that a folder is under legal hold; running a
5
+ classifier over the content to re-derive that fact can only lose information.
6
+ So a TAG detector runs nothing. It exists to give the assertion a stable
7
+ identity — a key, a label, a severity — and a CUSTOM connector notebook applies
8
+ it by that key::
9
+
10
+ Asset(id="accounts", tags={"cardholder_data": "primary-account-numbers"})
11
+
12
+ ``detect()`` therefore returns nothing for any content it is handed. The finding
13
+ is built by ``tag_finding()``, called from the detector pipeline once per tag the
14
+ notebook attached to the asset.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from ....models.generated_detectors import (
20
+ PipelineResult,
21
+ Severity,
22
+ TagPipelineSchema,
23
+ )
24
+ from ....models.generated_single_asset_scan_results import DetectionResult
25
+ from ._base import BaseRunner
26
+
27
+ #: Matches the schema default. A tag has no confidence to derive a severity
28
+ #: from, so an unreadable one falls back here rather than being guessed at.
29
+ _DEFAULT_SEVERITY = Severity.medium
30
+
31
+
32
+ class TagRunner(BaseRunner):
33
+ """Placeholder pipeline: produces findings only from notebook-supplied tags."""
34
+
35
+ def __init__(
36
+ self, schema: TagPipelineSchema, detector_key: str = "", detector_name: str = ""
37
+ ) -> None:
38
+ self.schema = schema
39
+ self._detector_key = detector_key
40
+ self._detector_name = detector_name
41
+ # The label is what every finding is called. Falling back to the
42
+ # detector name keeps a detector created without one readable rather
43
+ # than producing findings typed "tag:".
44
+ self.label = (schema.label or "").strip() or detector_name or detector_key or "tag"
45
+ self.severity = _coerce_severity(schema.severity)
46
+
47
+ def run(self, text: str) -> PipelineResult:
48
+ """No pipeline to run. Present only to satisfy the runner interface."""
49
+ return PipelineResult(metadata={"runner": "TAG"})
50
+
51
+ def detect(self, content: str | bytes, content_type: str) -> list[DetectionResult]:
52
+ """Never fires on content. A tag comes from the connector, not the text."""
53
+ return []
54
+
55
+ def get_supported_content_types(self) -> list[str]:
56
+ """No content type is supported, so the pipeline never schedules this."""
57
+ return []
58
+
59
+ def tag_finding(self, value: str) -> DetectionResult:
60
+ """One finding for one value the notebook asserted under this key."""
61
+ text = str(value).strip()
62
+ return self._make_result(
63
+ finding_type=f"tag:{self.label}",
64
+ category="CLASSIFICATION",
65
+ severity=self.severity,
66
+ # Asserted by the connector, not inferred from content: there is
67
+ # nothing here for a confidence score to express.
68
+ confidence=1.0,
69
+ matched_content=text,
70
+ location=None,
71
+ metadata={
72
+ "runner": "TAG",
73
+ "label": self.label,
74
+ "tag_key": self._detector_key,
75
+ "tag_value": text,
76
+ },
77
+ )
78
+
79
+
80
+ def _coerce_severity(value: object) -> Severity:
81
+ if isinstance(value, Severity):
82
+ return value
83
+ if isinstance(value, str) and value in Severity.__members__:
84
+ return Severity(value)
85
+ return _DEFAULT_SEVERITY
@@ -30,7 +30,7 @@ DETECTOR_ENGINE_VERSION: Final[dict[str, int]] = {
30
30
  "YARA": 1,
31
31
  "BROKEN_LINKS": 1,
32
32
  "CODE_SECURITY": 1,
33
- "CUSTOM": 1,
33
+ "CUSTOM": 2,
34
34
  }
35
35
 
36
36
  # Detectors whose verdict depends on state outside the asset, so identical
@@ -33,7 +33,7 @@ from __future__ import annotations
33
33
  from collections.abc import Iterable, Sequence
34
34
  from dataclasses import dataclass, field
35
35
  from enum import StrEnum
36
- from typing import Any
36
+ from typing import Any, ClassVar
37
37
 
38
38
  from ..utils.urn import Urn
39
39
 
@@ -171,8 +171,38 @@ class FieldMapping:
171
171
  }
172
172
 
173
173
 
174
+ class _RefMeta(type):
175
+ """Turn a wrong constructor name into an instruction instead of a riddle.
176
+
177
+ ``Ref.id(...)`` is the natural guess and does not exist. The resulting
178
+ ``AttributeError`` was raised inside ``relationships()``, which the runner
179
+ caught as non-fatal -- so a notebook shipped 920 assets, zero edges, and a
180
+ COMPLETED run, with `type object 'Ref' has no attribute 'id'` visible only
181
+ in a Kubernetes job log. The visibility half of that is fixed elsewhere;
182
+ this half makes the message itself say what to write.
183
+ """
184
+
185
+ _SUGGESTIONS: ClassVar[dict[str, str]] = {
186
+ "id": "Ref.asset(<the id you gave Asset(id=...)>)",
187
+ "hash": "Ref.asset(<the id you gave Asset(id=...)>)",
188
+ "asset_id": "Ref.asset(<the id you gave Asset(id=...)>)",
189
+ "uri": "Ref.urn(<a urn like 'company://at/firmenbuchnummer/606601k'>)",
190
+ "url": "Ref.urn(<a urn like 'company://at/firmenbuchnummer/606601k'>)",
191
+ "external": "Ref.urn(<a urn naming the object in its own system>)",
192
+ }
193
+
194
+ def __getattr__(cls, name: str) -> Any:
195
+ suggestion = cls._SUGGESTIONS.get(name)
196
+ hint = f" Did you mean {suggestion}?" if suggestion else ""
197
+ raise AttributeError(
198
+ f"Ref has no '{name}'. An edge endpoint is one of "
199
+ f"Ref.asset(<asset id>), Ref.urn(<urn>) or Ref.finding(<finding id>)."
200
+ f"{hint}"
201
+ )
202
+
203
+
174
204
  @dataclass(frozen=True)
175
- class Ref:
205
+ class Ref(metaclass=_RefMeta):
176
206
  """One end of an edge.
177
207
 
178
208
  ``asset`` names something this run produced, by its hash. ``urn`` names an
@@ -175,12 +175,15 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
175
175
  Best-effort by design — a connector that cannot describe its relationships
176
176
  should still deliver its assets — but *loudly* best-effort. Edge loss used
177
177
  to be invisible at three separate layers, so a scan could report success
178
- having silently produced no lineage at all. Anything dropped is counted and
179
- logged here, and the count travels with the run.
178
+ having silently produced no lineage at all. Anything lost is counted here
179
+ and handed to the sink, which carries it into the run record so the run
180
+ stops reporting an unqualified success (see ``set_relationship_losses``).
180
181
  """
181
182
  if not hasattr(sink, "emit_edges"):
182
183
  return
183
184
 
185
+ report = sink.relationship_report if hasattr(sink, "relationship_report") else None
186
+
184
187
  edges: list[Any] = []
185
188
  try:
186
189
  if hasattr(source, "drain_edges"):
@@ -193,7 +196,21 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
193
196
  if collected:
194
197
  edges.extend(collected)
195
198
  except Exception as collect_error:
199
+ # The connector's relationship code raised. How many edges that cost is
200
+ # unknowable — it never got to say — so this is counted as one failed
201
+ # relationship pass rather than as N lost edges.
196
202
  logger.warning("Could not collect relationships (non-fatal): %s", collect_error)
203
+ if report is not None:
204
+ report.record_failure(
205
+ f"{type(collect_error).__name__}: {collect_error}",
206
+ )
207
+
208
+ # A connector that declares relationships() but produced nothing is not the
209
+ # same as one that has no lineage to declare, and the difference is exactly
210
+ # the failure mode this reporting exists for: an exception inside
211
+ # relationships() used to leave a green run with zero edges.
212
+ if report is not None and not edges and _declares_relationships(source):
213
+ report.record_empty()
197
214
 
198
215
  if not edges:
199
216
  return
@@ -206,9 +223,20 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
206
223
  len(edges),
207
224
  emit_error,
208
225
  )
226
+ if report is not None:
227
+ report.record_lost(
228
+ len(edges),
229
+ f"{type(emit_error).__name__}: {emit_error}",
230
+ )
209
231
  return
210
232
 
211
- unresolved = (result or {}).get("dropped", 0) if isinstance(result, dict) else 0
233
+ tally = result if isinstance(result, dict) else {}
234
+ unresolved = tally.get("dropped", 0) or 0
235
+ if report is not None:
236
+ report.record_emitted(
237
+ emitted=len(edges) - unresolved,
238
+ dropped=unresolved,
239
+ )
212
240
  logger.info(
213
241
  "Emitted %d relationship edge(s)%s%s",
214
242
  len(edges),
@@ -217,6 +245,17 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
217
245
  )
218
246
 
219
247
 
248
+ def _declares_relationships(source: Any) -> bool:
249
+ """Whether this connector claims to describe relationships at all."""
250
+ declares = getattr(source, "declares_relationships", None)
251
+ if callable(declares):
252
+ try:
253
+ return bool(declares())
254
+ except Exception: # pragma: no cover - a probe must never fail a scan
255
+ return False
256
+ return hasattr(source, "collect_relationships")
257
+
258
+
220
259
  async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) -> None:
221
260
  """Initialize the source and execute the specified command."""
222
261
  runner_id = args.runner_id or os.environ.get("RUNNER_ID") or "local-run"
@@ -932,6 +932,40 @@ class PipelineSeverityRule(BaseModel):
932
932
  severity: Severity
933
933
 
934
934
 
935
+ class AssetKind(StrEnum):
936
+ record = 'record'
937
+ document = 'document'
938
+ page = 'page'
939
+ file = 'file'
940
+ table = 'table'
941
+
942
+
943
+ class DetectorScope(BaseModel):
944
+ """
945
+ Restricts which of a source's assets this detector runs on.
946
+
947
+ A custom detector is attached to a SOURCE, so without this it runs on every asset that source produces. For an expensive engine that is not a tuning problem but a design problem: attaching an LLM detector to a documents source meant one model call per filed PDF and per raw XML blob — 3 to 25 seconds each, on inputs the model had no business reading — and the run spent its entire life in the detector pool. The only workaround was splitting one source in two, which is a defensible architecture but was forced rather than chosen.
948
+
949
+ Every field is optional and they AND together; an empty scope runs everywhere, which is the previous behaviour.
950
+ """
951
+
952
+ model_config = ConfigDict(
953
+ extra='forbid',
954
+ )
955
+ asset_kinds: list[AssetKind] | None = Field(
956
+ None,
957
+ description='Catalog kinds this detector applies to. ["record"] is the natural expression of "run on the derived summaries, not on the source documents they came from".',
958
+ )
959
+ content_types: list[str] | None = Field(
960
+ None,
961
+ description="MIME types or prefixes (application/pdf, text/). Narrower than the engine's own supported types, never wider: a detector still never sees content its runner cannot read.",
962
+ )
963
+ metadata: dict[str, Any] | None = Field(
964
+ None,
965
+ description='Asset-metadata equality predicate, e.g. {"doc_type": "jab"}. Every named key must be present on the asset and equal (compared as strings, so 4 and "4" match). A key the asset does not carry excludes it — an absent field is not a match.',
966
+ )
967
+
968
+
935
969
  class Type1(StrEnum):
936
970
  GLINER2 = 'GLINER2'
937
971
 
@@ -940,6 +974,10 @@ class GLiNER2PipelineSchema(BaseModel):
940
974
  model_config = ConfigDict(
941
975
  extra='forbid',
942
976
  )
977
+ scope: DetectorScope | None = Field(
978
+ None,
979
+ description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
980
+ )
943
981
  type: Literal['GLINER2'] = 'GLINER2'
944
982
  entities: dict[str, PipelineEntityDefinition] | None = None
945
983
  classification: dict[str, PipelineClassificationDefinition] | None = None
@@ -955,6 +993,10 @@ class RegexPipelineSchema(BaseModel):
955
993
  model_config = ConfigDict(
956
994
  extra='forbid',
957
995
  )
996
+ scope: DetectorScope | None = Field(
997
+ None,
998
+ description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
999
+ )
958
1000
  type: Literal['REGEX'] = 'REGEX'
959
1001
  patterns: dict[str, RegexPatternDefinition] | None = None
960
1002
  validation: PipelineValidationConfig | None = None
@@ -1059,6 +1101,10 @@ class LLMPipelineSchema(BaseModel):
1059
1101
  model_config = ConfigDict(
1060
1102
  extra='forbid',
1061
1103
  )
1104
+ scope: DetectorScope | None = Field(
1105
+ None,
1106
+ description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
1107
+ )
1062
1108
  type: Literal['LLM'] = 'LLM'
1063
1109
  system_prompt: str = Field(
1064
1110
  ...,
@@ -1095,7 +1141,7 @@ class LLMPipelineSchema(BaseModel):
1095
1141
  )
1096
1142
  confidence_threshold: float | None = Field(
1097
1143
  0.5,
1098
- description='Minimum model confidence to report a label as a finding (0-1).',
1144
+ description='Minimum model confidence to report a label as a finding (0-1). Applied BEFORE the finding is recorded, so anything below it leaves no trace but a count in the run log — and a systematically under-confident model (free routers are) then looks like a model that never answered. The finding already carries the confidence, so 0 records every answer and lets you filter downstream.',
1099
1145
  ge=0.0,
1100
1146
  le=1.0,
1101
1147
  )
@@ -1155,6 +1201,10 @@ class TextClassificationPipelineSchema(BaseModel):
1155
1201
  model_config = ConfigDict(
1156
1202
  extra='forbid',
1157
1203
  )
1204
+ scope: DetectorScope | None = Field(
1205
+ None,
1206
+ description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
1207
+ )
1158
1208
  type: Literal['TEXT_CLASSIFICATION']
1159
1209
  model: str = Field(
1160
1210
  ...,
@@ -1225,6 +1275,10 @@ class ImageClassificationPipelineSchema(BaseModel):
1225
1275
  model_config = ConfigDict(
1226
1276
  extra='forbid',
1227
1277
  )
1278
+ scope: DetectorScope | None = Field(
1279
+ None,
1280
+ description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
1281
+ )
1228
1282
  type: Literal['IMAGE_CLASSIFICATION']
1229
1283
  model: str | None = Field(
1230
1284
  None,
@@ -1277,6 +1331,10 @@ class ObjectDetectionPipelineSchema(BaseModel):
1277
1331
  model_config = ConfigDict(
1278
1332
  extra='forbid',
1279
1333
  )
1334
+ scope: DetectorScope | None = Field(
1335
+ None,
1336
+ description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
1337
+ )
1280
1338
  type: Literal['OBJECT_DETECTION']
1281
1339
  model: str = Field(
1282
1340
  ...,
@@ -1313,6 +1371,45 @@ class ObjectDetectionPipelineSchema(BaseModel):
1313
1371
  )
1314
1372
 
1315
1373
 
1374
+ class Type8(StrEnum):
1375
+ TAG = 'TAG'
1376
+
1377
+
1378
+ class Severity2(StrEnum):
1379
+ """
1380
+ Severity assigned to every finding this detector produces. There is no confidence to derive it from: a tag is asserted, not inferred.
1381
+ """
1382
+
1383
+ critical = 'critical'
1384
+ high = 'high'
1385
+ medium = 'medium'
1386
+ low = 'low'
1387
+ info = 'info'
1388
+
1389
+
1390
+ class TagPipelineSchema(BaseModel):
1391
+ """
1392
+ A placeholder detector that runs no classification and never reads content. It exists only to give a manual assertion a stable identity. A CUSTOM connector notebook applies it by the detector's key: Asset(tags={"<detector key>": "<value>"}). Not selectable in a source's detector list.
1393
+ """
1394
+
1395
+ model_config = ConfigDict(
1396
+ extra='forbid',
1397
+ )
1398
+ scope: DetectorScope | None = Field(
1399
+ None,
1400
+ description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
1401
+ )
1402
+ type: Literal['TAG'] = 'TAG'
1403
+ label: str | None = Field(
1404
+ None,
1405
+ description="Label carried by every finding this detector produces (finding_type is 'tag:<label>'). Defaults to the detector's name when omitted.",
1406
+ )
1407
+ severity: Severity2 | None = Field(
1408
+ Severity2.medium,
1409
+ description='Severity assigned to every finding this detector produces. There is no confidence to derive it from: a tag is asserted, not inferred.',
1410
+ )
1411
+
1412
+
1316
1413
  class CustomDetectorConfig(DetectorConfig):
1317
1414
  """
1318
1415
  Configuration for user-defined detector execution
@@ -1338,6 +1435,7 @@ class CustomDetectorConfig(DetectorConfig):
1338
1435
  | TextClassificationPipelineSchema
1339
1436
  | ImageClassificationPipelineSchema
1340
1437
  | ObjectDetectionPipelineSchema
1438
+ | TagPipelineSchema
1341
1439
  | None
1342
1440
  ) = Field(None, discriminator='type', title='AnyPipelineSchema')
1343
1441
  max_findings: int | None = Field(