admina 0.14.0__tar.gz → 0.14.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. {admina-0.14.0/admina.egg-info → admina-0.14.2}/PKG-INFO +1 -1
  2. {admina-0.14.0 → admina-0.14.2}/admina/__init__.py +1 -1
  3. admina-0.14.2/admina/domains/data_sovereignty/names.py +70 -0
  4. {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/pii.py +5 -0
  5. {admina-0.14.0 → admina-0.14.2}/admina/engines/presidio.py +3 -0
  6. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/pii/spacy_regex.py +3 -0
  7. {admina-0.14.0 → admina-0.14.2}/admina/redteam/baselines/baseline.json +6 -6
  8. {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/SHA256SUMS +1 -1
  9. {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/pii.jsonl +8 -0
  10. {admina-0.14.0 → admina-0.14.2}/admina/redteam/report.py +22 -2
  11. {admina-0.14.0 → admina-0.14.2/admina.egg-info}/PKG-INFO +1 -1
  12. {admina-0.14.0 → admina-0.14.2}/admina.egg-info/SOURCES.txt +2 -0
  13. {admina-0.14.0 → admina-0.14.2}/pyproject.toml +1 -1
  14. admina-0.14.2/tests/test_person_names.py +122 -0
  15. {admina-0.14.0 → admina-0.14.2}/tests/test_pipeline_executor.py +8 -7
  16. {admina-0.14.0 → admina-0.14.2}/tests/test_presidio_engine.py +14 -0
  17. {admina-0.14.0 → admina-0.14.2}/tests/test_redteam_lib.py +12 -0
  18. {admina-0.14.0 → admina-0.14.2}/LICENSE +0 -0
  19. {admina-0.14.0 → admina-0.14.2}/NOTICE +0 -0
  20. {admina-0.14.0 → admina-0.14.2}/README.md +0 -0
  21. {admina-0.14.0 → admina-0.14.2}/admina/cli/__init__.py +0 -0
  22. {admina-0.14.0 → admina-0.14.2}/admina/cli/commands/__init__.py +0 -0
  23. {admina-0.14.0 → admina-0.14.2}/admina/cli/forensic.py +0 -0
  24. {admina-0.14.0 → admina-0.14.2}/admina/cli/main.py +0 -0
  25. {admina-0.14.0 → admina-0.14.2}/admina/cli/redteam.py +0 -0
  26. {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/admina.yaml.j2 +0 -0
  27. {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/docker-compose.yml.j2 +0 -0
  28. {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/env.j2 +0 -0
  29. {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/main.py.j2 +0 -0
  30. {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/plugin.py.j2 +0 -0
  31. {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/plugin_pyproject.toml.j2 +0 -0
  32. {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/plugin_readme.md.j2 +0 -0
  33. {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/plugin_test.py.j2 +0 -0
  34. {admina-0.14.0 → admina-0.14.2}/admina/core/__init__.py +0 -0
  35. {admina-0.14.0 → admina-0.14.2}/admina/core/config.py +0 -0
  36. {admina-0.14.0 → admina-0.14.2}/admina/core/config_schema.py +0 -0
  37. {admina-0.14.0 → admina-0.14.2}/admina/core/event_bus.py +0 -0
  38. {admina-0.14.0 → admina-0.14.2}/admina/core/exception_log.py +0 -0
  39. {admina-0.14.0 → admina-0.14.2}/admina/core/jcs.py +0 -0
  40. {admina-0.14.0 → admina-0.14.2}/admina/core/offline.py +0 -0
  41. {admina-0.14.0 → admina-0.14.2}/admina/core/secretfile.py +0 -0
  42. {admina-0.14.0 → admina-0.14.2}/admina/core/secrets.py +0 -0
  43. {admina-0.14.0 → admina-0.14.2}/admina/core/trace_context.py +0 -0
  44. {admina-0.14.0 → admina-0.14.2}/admina/core/types.py +0 -0
  45. {admina-0.14.0 → admina-0.14.2}/admina/dashboard/__init__.py +0 -0
  46. {admina-0.14.0 → admina-0.14.2}/admina/dashboard/static/heimdall.png +0 -0
  47. {admina-0.14.0 → admina-0.14.2}/admina/dashboard/static/index.html +0 -0
  48. {admina-0.14.0 → admina-0.14.2}/admina/dashboard/static/vendor/alpinejs.min.js +0 -0
  49. {admina-0.14.0 → admina-0.14.2}/admina/domains/__init__.py +0 -0
  50. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/__init__.py +0 -0
  51. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/coordination.py +0 -0
  52. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/egress.py +0 -0
  53. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/fingerprint.py +0 -0
  54. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/firewall.py +0 -0
  55. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/loop_breaker.py +0 -0
  56. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/pattern_packs.py +0 -0
  57. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/pattern_timing.py +0 -0
  58. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/ruleset.py +0 -0
  59. {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/scan_policy.py +0 -0
  60. {admina-0.14.0 → admina-0.14.2}/admina/domains/ai_infra/__init__.py +0 -0
  61. {admina-0.14.0 → admina-0.14.2}/admina/domains/ai_infra/llm_engine.py +0 -0
  62. {admina-0.14.0 → admina-0.14.2}/admina/domains/ai_infra/rag.py +0 -0
  63. {admina-0.14.0 → admina-0.14.2}/admina/domains/ai_infra/webui.py +0 -0
  64. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/__init__.py +0 -0
  65. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/ai_act_terms.py +0 -0
  66. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/cross_regulation.py +0 -0
  67. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/eu_ai_act.py +0 -0
  68. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/forensic.py +0 -0
  69. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/forensic_files.py +0 -0
  70. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/forensic_integrity.py +0 -0
  71. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/gdpr.py +0 -0
  72. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/nis2.py +0 -0
  73. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/oisg.py +0 -0
  74. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/oisg_evidence.py +0 -0
  75. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/otel.py +0 -0
  76. {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/schemas/oisg-evidence.schema.json +0 -0
  77. {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/__init__.py +0 -0
  78. {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/classification.py +0 -0
  79. {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/email_matching.py +0 -0
  80. {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/iban.py +0 -0
  81. {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/masking.py +0 -0
  82. {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/residency.py +0 -0
  83. {admina-0.14.0 → admina-0.14.2}/admina/domains/governance.py +0 -0
  84. {admina-0.14.0 → admina-0.14.2}/admina/engines/__init__.py +0 -0
  85. {admina-0.14.0 → admina-0.14.2}/admina/engines/pii_plugins.py +0 -0
  86. {admina-0.14.0 → admina-0.14.2}/admina/integrations/__init__.py +0 -0
  87. {admina-0.14.0 → admina-0.14.2}/admina/integrations/_engines.py +0 -0
  88. {admina-0.14.0 → admina-0.14.2}/admina/integrations/cheshirecat/__init__.py +0 -0
  89. {admina-0.14.0 → admina-0.14.2}/admina/integrations/cheshirecat/admina-plugin/admina_governance.py +0 -0
  90. {admina-0.14.0 → admina-0.14.2}/admina/integrations/crewai/__init__.py +0 -0
  91. {admina-0.14.0 → admina-0.14.2}/admina/integrations/crewai/callbacks.py +0 -0
  92. {admina-0.14.0 → admina-0.14.2}/admina/integrations/langchain/__init__.py +0 -0
  93. {admina-0.14.0 → admina-0.14.2}/admina/integrations/langchain/callbacks.py +0 -0
  94. {admina-0.14.0 → admina-0.14.2}/admina/integrations/n8n/__init__.py +0 -0
  95. {admina-0.14.0 → admina-0.14.2}/admina/integrations/openclaw/__init__.py +0 -0
  96. {admina-0.14.0 → admina-0.14.2}/admina/plugins/__init__.py +0 -0
  97. {admina-0.14.0 → admina-0.14.2}/admina/plugins/base.py +0 -0
  98. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/__init__.py +0 -0
  99. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/__init__.py +0 -0
  100. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/_streaming.py +0 -0
  101. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/anthropic.py +0 -0
  102. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/bedrock.py +0 -0
  103. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/gemini.py +0 -0
  104. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/mistral.py +0 -0
  105. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/ollama.py +0 -0
  106. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/openai.py +0 -0
  107. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/vllm.py +0 -0
  108. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/alerts/__init__.py +0 -0
  109. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/alerts/log.py +0 -0
  110. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/alerts/webhook.py +0 -0
  111. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/auth/__init__.py +0 -0
  112. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/auth/apikey.py +0 -0
  113. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/compliance/__init__.py +0 -0
  114. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/compliance/eu_ai_act.py +0 -0
  115. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/connectors/__init__.py +0 -0
  116. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/connectors/chromadb.py +0 -0
  117. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/connectors/filesystem.py +0 -0
  118. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/forensic/__init__.py +0 -0
  119. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/forensic/filesystem.py +0 -0
  120. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/guards/__init__.py +0 -0
  121. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/guards/guardrailsai_guard.py +0 -0
  122. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/pii/__init__.py +0 -0
  123. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/transports/__init__.py +0 -0
  124. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/transports/http_rest.py +0 -0
  125. {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/transports/mcp.py +0 -0
  126. {admina-0.14.0 → admina-0.14.2}/admina/plugins/registry.py +0 -0
  127. {admina-0.14.0 → admina-0.14.2}/admina/proxy/__init__.py +0 -0
  128. {admina-0.14.0 → admina-0.14.2}/admina/proxy/api/__init__.py +0 -0
  129. {admina-0.14.0 → admina-0.14.2}/admina/proxy/api/dashboard.py +0 -0
  130. {admina-0.14.0 → admina-0.14.2}/admina/proxy/api/gateway.py +0 -0
  131. {admina-0.14.0 → admina-0.14.2}/admina/proxy/api/integration.py +0 -0
  132. {admina-0.14.0 → admina-0.14.2}/admina/proxy/body_limit.py +0 -0
  133. {admina-0.14.0 → admina-0.14.2}/admina/proxy/config.py +0 -0
  134. {admina-0.14.0 → admina-0.14.2}/admina/proxy/dashboard_session.py +0 -0
  135. {admina-0.14.0 → admina-0.14.2}/admina/proxy/decisions.py +0 -0
  136. {admina-0.14.0 → admina-0.14.2}/admina/proxy/engine_bridge.py +0 -0
  137. {admina-0.14.0 → admina-0.14.2}/admina/proxy/engine_report.py +0 -0
  138. {admina-0.14.0 → admina-0.14.2}/admina/proxy/env_check.py +0 -0
  139. {admina-0.14.0 → admina-0.14.2}/admina/proxy/forensic_backend.py +0 -0
  140. {admina-0.14.0 → admina-0.14.2}/admina/proxy/forensic_probe.py +0 -0
  141. {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_body.py +0 -0
  142. {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_correlation.py +0 -0
  143. {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_outcome.py +0 -0
  144. {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_response_scan.py +0 -0
  145. {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_scan.py +0 -0
  146. {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_transport.py +0 -0
  147. {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_upstreams.py +0 -0
  148. {admina-0.14.0 → admina-0.14.2}/admina/proxy/governance.py +0 -0
  149. {admina-0.14.0 → admina-0.14.2}/admina/proxy/log_format.py +0 -0
  150. {admina-0.14.0 → admina-0.14.2}/admina/proxy/loop_lag.py +0 -0
  151. {admina-0.14.0 → admina-0.14.2}/admina/proxy/main.py +0 -0
  152. {admina-0.14.0 → admina-0.14.2}/admina/proxy/multi_upstream.py +0 -0
  153. {admina-0.14.0 → admina-0.14.2}/admina/proxy/pipeline_executor.py +0 -0
  154. {admina-0.14.0 → admina-0.14.2}/admina/proxy/request_metrics.py +0 -0
  155. {admina-0.14.0 → admina-0.14.2}/admina/proxy/state.py +0 -0
  156. {admina-0.14.0 → admina-0.14.2}/admina/proxy/surfaces.py +0 -0
  157. {admina-0.14.0 → admina-0.14.2}/admina/py.typed +0 -0
  158. {admina-0.14.0 → admina-0.14.2}/admina/redteam/__init__.py +0 -0
  159. {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/coordination.jsonl +0 -0
  160. {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/injection.jsonl +0 -0
  161. {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/loop.jsonl +0 -0
  162. {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora.py +0 -0
  163. {admina-0.14.0 → admina-0.14.2}/admina/redteam/detectors.py +0 -0
  164. {admina-0.14.0 → admina-0.14.2}/admina/redteam/gate.py +0 -0
  165. {admina-0.14.0 → admina-0.14.2}/admina/redteam/metrics.py +0 -0
  166. {admina-0.14.0 → admina-0.14.2}/admina/redteam/runner.py +0 -0
  167. {admina-0.14.0 → admina-0.14.2}/admina/sdk/__init__.py +0 -0
  168. {admina-0.14.0 → admina-0.14.2}/admina/sdk/_compat.py +0 -0
  169. {admina-0.14.0 → admina-0.14.2}/admina/sdk/compliance_kit.py +0 -0
  170. {admina-0.14.0 → admina-0.14.2}/admina/sdk/errors.py +0 -0
  171. {admina-0.14.0 → admina-0.14.2}/admina/sdk/governed_agent.py +0 -0
  172. {admina-0.14.0 → admina-0.14.2}/admina/sdk/governed_data.py +0 -0
  173. {admina-0.14.0 → admina-0.14.2}/admina/sdk/governed_model.py +0 -0
  174. {admina-0.14.0 → admina-0.14.2}/admina/sdk/retry.py +0 -0
  175. {admina-0.14.0 → admina-0.14.2}/admina/sdk/streaming.py +0 -0
  176. {admina-0.14.0 → admina-0.14.2}/admina.egg-info/dependency_links.txt +0 -0
  177. {admina-0.14.0 → admina-0.14.2}/admina.egg-info/entry_points.txt +0 -0
  178. {admina-0.14.0 → admina-0.14.2}/admina.egg-info/requires.txt +0 -0
  179. {admina-0.14.0 → admina-0.14.2}/admina.egg-info/top_level.txt +0 -0
  180. {admina-0.14.0 → admina-0.14.2}/setup.cfg +0 -0
  181. {admina-0.14.0 → admina-0.14.2}/tests/test_admina_config_path.py +0 -0
  182. {admina-0.14.0 → admina-0.14.2}/tests/test_admina_framework_sdist.py +0 -0
  183. {admina-0.14.0 → admina-0.14.2}/tests/test_ai_act_terms.py +0 -0
  184. {admina-0.14.0 → admina-0.14.2}/tests/test_audit_api_source.py +0 -0
  185. {admina-0.14.0 → admina-0.14.2}/tests/test_auth_middleware_errors.py +0 -0
  186. {admina-0.14.0 → admina-0.14.2}/tests/test_banner_engine_info.py +0 -0
  187. {admina-0.14.0 → admina-0.14.2}/tests/test_benchmark_14us.py +0 -0
  188. {admina-0.14.0 → admina-0.14.2}/tests/test_bus_event_metadata.py +0 -0
  189. {admina-0.14.0 → admina-0.14.2}/tests/test_canary_no_content.py +0 -0
  190. {admina-0.14.0 → admina-0.14.2}/tests/test_check_versions.py +0 -0
  191. {admina-0.14.0 → admina-0.14.2}/tests/test_cli_forensic_export.py +0 -0
  192. {admina-0.14.0 → admina-0.14.2}/tests/test_cli_redteam.py +0 -0
  193. {admina-0.14.0 → admina-0.14.2}/tests/test_config_schema_validation.py +0 -0
  194. {admina-0.14.0 → admina-0.14.2}/tests/test_coordination.py +0 -0
  195. {admina-0.14.0 → admina-0.14.2}/tests/test_coordination_cli.py +0 -0
  196. {admina-0.14.0 → admina-0.14.2}/tests/test_coordination_corpus.py +0 -0
  197. {admina-0.14.0 → admina-0.14.2}/tests/test_coordination_wiring.py +0 -0
  198. {admina-0.14.0 → admina-0.14.2}/tests/test_core_config.py +0 -0
  199. {admina-0.14.0 → admina-0.14.2}/tests/test_dashboard_container.py +0 -0
  200. {admina-0.14.0 → admina-0.14.2}/tests/test_data_classifier_restricted.py +0 -0
  201. {admina-0.14.0 → admina-0.14.2}/tests/test_domains.py +0 -0
  202. {admina-0.14.0 → admina-0.14.2}/tests/test_domains_governance.py +0 -0
  203. {admina-0.14.0 → admina-0.14.2}/tests/test_egress_analyze.py +0 -0
  204. {admina-0.14.0 → admina-0.14.2}/tests/test_egress_cli.py +0 -0
  205. {admina-0.14.0 → admina-0.14.2}/tests/test_egress_pipeline.py +0 -0
  206. {admina-0.14.0 → admina-0.14.2}/tests/test_egress_policy.py +0 -0
  207. {admina-0.14.0 → admina-0.14.2}/tests/test_egress_surfaces.py +0 -0
  208. {admina-0.14.0 → admina-0.14.2}/tests/test_enabled_surfaces.py +0 -0
  209. {admina-0.14.0 → admina-0.14.2}/tests/test_engine_effective.py +0 -0
  210. {admina-0.14.0 → admina-0.14.2}/tests/test_engine_startup_errors.py +0 -0
  211. {admina-0.14.0 → admina-0.14.2}/tests/test_engines.py +0 -0
  212. {admina-0.14.0 → admina-0.14.2}/tests/test_entrypoint_script.py +0 -0
  213. {admina-0.14.0 → admina-0.14.2}/tests/test_error_disclosure.py +0 -0
  214. {admina-0.14.0 → admina-0.14.2}/tests/test_event_loop_lag_metric.py +0 -0
  215. {admina-0.14.0 → admina-0.14.2}/tests/test_fingerprint.py +0 -0
  216. {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_heuristic_config.py +0 -0
  217. {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_it_baseline.py +0 -0
  218. {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_it_benign.py +0 -0
  219. {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_parity.py +0 -0
  220. {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_pattern_ids.py +0 -0
  221. {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_pattern_packs.py +0 -0
  222. {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_pattern_timing.py +0 -0
  223. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_atomic_write.py +0 -0
  224. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_blackbox.py +0 -0
  225. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_config_coherence.py +0 -0
  226. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_fail_mode.py +0 -0
  227. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_gateway_records.py +0 -0
  228. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_incremental_verify.py +0 -0
  229. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_integrity.py +0 -0
  230. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_rebuilt_and_duplicates.py +0 -0
  231. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_record_signature.py +0 -0
  232. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_restart.py +0 -0
  233. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_s3_reads.py +0 -0
  234. {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_volume.py +0 -0
  235. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_block_status.py +0 -0
  236. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_config.py +0 -0
  237. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_correlation.py +0 -0
  238. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_endpoints.py +0 -0
  239. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_errors.py +0 -0
  240. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_forward_body.py +0 -0
  241. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_governed_stream.py +0 -0
  242. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_guard_fail_mode.py +0 -0
  243. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_helpers.py +0 -0
  244. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_latency_bench.py +0 -0
  245. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_mount.py +0 -0
  246. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_outcome_headers.py +0 -0
  247. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_passthrough.py +0 -0
  248. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_pii_contract.py +0 -0
  249. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_ruleset_endpoint.py +0 -0
  250. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_scan_coverage.py +0 -0
  251. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_upstream_auth.py +0 -0
  252. {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_upstream_routes.py +0 -0
  253. {admina-0.14.0 → admina-0.14.2}/tests/test_governance_pipeline.py +0 -0
  254. {admina-0.14.0 → admina-0.14.2}/tests/test_guard_fail_mode.py +0 -0
  255. {admina-0.14.0 → admina-0.14.2}/tests/test_health_fields.py +0 -0
  256. {admina-0.14.0 → admina-0.14.2}/tests/test_health_forensic_probe.py +0 -0
  257. {admina-0.14.0 → admina-0.14.2}/tests/test_iban_it.py +0 -0
  258. {admina-0.14.0 → admina-0.14.2}/tests/test_jcs.py +0 -0
  259. {admina-0.14.0 → admina-0.14.2}/tests/test_lazy_imports.py +0 -0
  260. {admina-0.14.0 → admina-0.14.2}/tests/test_log_format.py +0 -0
  261. {admina-0.14.0 → admina-0.14.2}/tests/test_mcp_block_reason.py +0 -0
  262. {admina-0.14.0 → admina-0.14.2}/tests/test_mcp_transport.py +0 -0
  263. {admina-0.14.0 → admina-0.14.2}/tests/test_metrics_docs_auth.py +0 -0
  264. {admina-0.14.0 → admina-0.14.2}/tests/test_metrics_gateway.py +0 -0
  265. {admina-0.14.0 → admina-0.14.2}/tests/test_model_allowlist_post.py +0 -0
  266. {admina-0.14.0 → admina-0.14.2}/tests/test_offline_guarantee.py +0 -0
  267. {admina-0.14.0 → admina-0.14.2}/tests/test_oisg_evidence.py +0 -0
  268. {admina-0.14.0 → admina-0.14.2}/tests/test_otel_enabled_flag.py +0 -0
  269. {admina-0.14.0 → admina-0.14.2}/tests/test_pattern_probe.py +0 -0
  270. {admina-0.14.0 → admina-0.14.2}/tests/test_pii_email_matching.py +0 -0
  271. {admina-0.14.0 → admina-0.14.2}/tests/test_pii_mask_style.py +0 -0
  272. {admina-0.14.0 → admina-0.14.2}/tests/test_pii_plugin_engines.py +0 -0
  273. {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_dashboard_session.py +0 -0
  274. {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_forensic_would_action.py +0 -0
  275. {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_governance_mode_config.py +0 -0
  276. {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_guard_fail_mode.py +0 -0
  277. {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_mcp_response_pii.py +0 -0
  278. {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_metrics_endpoint.py +0 -0
  279. {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_minimal_extra.py +0 -0
  280. {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_plugins.py +0 -0
  281. {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_security.py +0 -0
  282. {admina-0.14.0 → admina-0.14.2}/tests/test_query_api_key_deprecation.py +0 -0
  283. {admina-0.14.0 → admina-0.14.2}/tests/test_record_decision.py +0 -0
  284. {admina-0.14.0 → admina-0.14.2}/tests/test_redaction_values_only.py +0 -0
  285. {admina-0.14.0 → admina-0.14.2}/tests/test_redteam_efficacy.py +0 -0
  286. {admina-0.14.0 → admina-0.14.2}/tests/test_redteam_external_corpora.py +0 -0
  287. {admina-0.14.0 → admina-0.14.2}/tests/test_release_workflows.py +0 -0
  288. {admina-0.14.0 → admina-0.14.2}/tests/test_request_sha256.py +0 -0
  289. {admina-0.14.0 → admina-0.14.2}/tests/test_request_size_cap.py +0 -0
  290. {admina-0.14.0 → admina-0.14.2}/tests/test_response_scan_option.py +0 -0
  291. {admina-0.14.0 → admina-0.14.2}/tests/test_ruleset_parity.py +0 -0
  292. {admina-0.14.0 → admina-0.14.2}/tests/test_ruleset_sha256.py +0 -0
  293. {admina-0.14.0 → admina-0.14.2}/tests/test_scan_depth.py +0 -0
  294. {admina-0.14.0 → admina-0.14.2}/tests/test_scan_policy.py +0 -0
  295. {admina-0.14.0 → admina-0.14.2}/tests/test_secret_files.py +0 -0
  296. {admina-0.14.0 → admina-0.14.2}/tests/test_unknown_admina_env.py +0 -0
  297. {admina-0.14.0 → admina-0.14.2}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: admina
3
- Version: 0.14.0
3
+ Version: 0.14.2
4
4
  Summary: Admina — governed AI development framework
5
5
  Author-email: Stefano Noferi <info@admina.org>
6
6
  Maintainer-email: Stefano Noferi <info@admina.org>
@@ -23,7 +23,7 @@ from __future__ import annotations
23
23
 
24
24
  from admina.sdk import ComplianceKit, GovernedAgent, GovernedData, GovernedModel
25
25
 
26
- __version__ = "0.14.0"
26
+ __version__ = "0.14.2"
27
27
 
28
28
  __all__ = [
29
29
  "__version__",
@@ -0,0 +1,70 @@
1
+ # Copyright © 2025–2026 Stefano Noferi & Admina contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """Whether a PERSON entity of an NER model reads as a name.
17
+
18
+ The PII engines mask a PERSON entity only when :func:`is_name_like` holds:
19
+ no digit or character that a name does not hold (``@ / \\ _ # : =``, as in
20
+ an e-mail address or a path), and no lowercase word that is a stop word of English, Italian,
21
+ German, French, Spanish or Portuguese (the stop-word lists of spaCy, without
22
+ the words that are also first names, such as "will", "may" and "sara"). The
23
+ English spaCy model labels phrases as PERSON ("il codice articolo", "ci
24
+ vediamo domani", "das Wetter", "grab a coffee"); they hold such words.
25
+ Names written in lowercase pass ("mario rossi"), and capitalised words are
26
+ not checked ("Sara", "Il Signore").
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import functools
32
+ import importlib
33
+
34
+ # The stop-word lists of spaCy whose lowercase words reject a PERSON span.
35
+ _STOP_WORD_LANGUAGES = ("en", "it", "de", "fr", "es", "pt")
36
+
37
+ # Characters of e-mail addresses, paths and identifiers, not of names.
38
+ _NOT_IN_NAMES = frozenset("@/\\_#:=")
39
+
40
+ # Stripped from both ends of a word before the comparison.
41
+ _PUNCTUATION = ".,;:!?\"'()[]«»“”‘’"
42
+
43
+ # Words of those lists that are also first names.
44
+ _FIRST_NAMES = frozenset({"ali", "may", "mia", "sara", "will"})
45
+
46
+
47
+ @functools.cache
48
+ def _stop_words() -> frozenset[str]:
49
+ """The stop words of :data:`_STOP_WORD_LANGUAGES`, without
50
+ :data:`_FIRST_NAMES`; empty without spaCy (no NER runs then)."""
51
+ words: set[str] = set()
52
+ for lang in _STOP_WORD_LANGUAGES:
53
+ try:
54
+ module = importlib.import_module(f"spacy.lang.{lang}.stop_words")
55
+ except ImportError:
56
+ return frozenset()
57
+ words |= module.STOP_WORDS
58
+ return frozenset(words - _FIRST_NAMES)
59
+
60
+
61
+ def is_name_like(text: str) -> bool:
62
+ """True when *text* has a word, no digit nor character of
63
+ :data:`_NOT_IN_NAMES`, and no lowercase word that is a stop word of
64
+ English, Italian, German, French, Spanish or Portuguese."""
65
+ words = text.split()
66
+ if not words or any(ch.isdigit() or ch in _NOT_IN_NAMES for ch in text):
67
+ return False
68
+ stop = _stop_words()
69
+ bare = (word.strip(_PUNCTUATION) for word in words)
70
+ return not any(word in stop for word in bare if word.islower())
@@ -30,6 +30,7 @@ from admina.domains.data_sovereignty.masking import (
30
30
  placeholder_spans,
31
31
  replace_spans,
32
32
  )
33
+ from admina.domains.data_sovereignty.names import is_name_like
33
34
 
34
35
  # spaCy is part of the [nlp] extra. When absent, PIIRedactor falls back
35
36
  # to regex-only mode (still covers EMAIL/PHONE/SSN/IBAN/IP/credit-card/EU IDs).
@@ -301,6 +302,10 @@ class PIIRedactor:
301
302
  cat_config = active_categories.get(ent.label_, {})
302
303
  if not cat_config.get("enabled", False):
303
304
  continue
305
+ if ent.label_ == "PERSON" and not is_name_like(
306
+ redacted[ent.start_char : ent.end_char]
307
+ ):
308
+ continue
304
309
  mask = self._mask(ent.label_, cat_config)
305
310
  parts = outside_placeholders(ent.start_char, ent.end_char, placeholders, redacted)
306
311
  for start, end in reversed(parts):
@@ -55,6 +55,7 @@ from admina.domains.data_sovereignty.masking import (
55
55
  placeholder_spans,
56
56
  replace_spans,
57
57
  )
58
+ from admina.domains.data_sovereignty.names import is_name_like
58
59
  from admina.domains.data_sovereignty.pii import PII_CATEGORIES
59
60
 
60
61
  logger = logging.getLogger("admina.engines.presidio")
@@ -272,6 +273,8 @@ class PresidioPIIEngine:
272
273
  cfg = PII_CATEGORIES.get(category, {})
273
274
  if not cfg.get("enabled", False):
274
275
  continue
276
+ if category == "PERSON" and not is_name_like(text[r.start : r.end]):
277
+ continue
275
278
  mask = OMISSIS if self.mask_style == "omissis" else cfg.get("mask", f"[{category}]")
276
279
  for start, end in outside_placeholders(r.start, r.end, placeholders, text):
277
280
  spans.append((start, end, category, mask))
@@ -27,6 +27,7 @@ from typing import Any
27
27
 
28
28
  from admina.domains.data_sovereignty.email_matching import EMAIL_RX, iter_email_matches
29
29
  from admina.domains.data_sovereignty.masking import replace_spans
30
+ from admina.domains.data_sovereignty.names import is_name_like
30
31
  from admina.plugins.base import BasePIIEngine
31
32
 
32
33
  logger = logging.getLogger("admina.plugins.pii.spacy_regex")
@@ -125,6 +126,8 @@ class SpaCyRegexPIIEngine(BasePIIEngine):
125
126
  continue
126
127
  if allowed and ent.label_ not in allowed:
127
128
  continue
129
+ if ent.label_ == "PERSON" and not is_name_like(ent.text):
130
+ continue
128
131
  # Skip if already covered by a regex match (full interval-overlap test)
129
132
  overlap = any(
130
133
  not (ent.end_char <= mm["start"] or ent.start_char >= mm["end"])
@@ -14,19 +14,19 @@
14
14
  "pii": {
15
15
  "python": {
16
16
  "type_recall": 1.0,
17
- "fp": 6,
18
- "fp_samples": 16,
17
+ "fp": 3,
18
+ "fp_samples": 24,
19
19
  "mode": "nlp:en_core_web_sm@3.8.0"
20
20
  },
21
21
  "rust": {
22
22
  "type_recall": 0.6551724137931034,
23
23
  "fp": 0,
24
- "fp_samples": 16
24
+ "fp_samples": 24
25
25
  },
26
26
  "presidio": {
27
- "type_recall": 0.5172413793103449,
27
+ "type_recall": 0.5862068965517241,
28
28
  "fp": 9,
29
- "fp_samples": 16,
29
+ "fp_samples": 24,
30
30
  "mode": "presidio:2.2.363/en+it"
31
31
  }
32
32
  },
@@ -42,4 +42,4 @@
42
42
  "fp_samples": 11
43
43
  }
44
44
  }
45
- }
45
+ }
@@ -1,4 +1,4 @@
1
1
  5b6904c941e71ca6fee0fcab722124ec1f9b17348648daee678d435ff940566b injection.jsonl
2
- 7157269c349894bd07aacceff469124a4444971d34c987eb0505a8c418532c8f pii.jsonl
2
+ 902f6ada48bfca0aa79cfd0a156b90f5dbfaa6930eafd0de7ee90630e24e2371 pii.jsonl
3
3
  df6727b94272ab72897acd5f71e883ff52711038286b55dfaa026becdc193ab3 loop.jsonl
4
4
  ece92830693b29ac5246f95754bf704092473cf2602bd970355e85a0358bc0d5 coordination.jsonl
@@ -40,3 +40,11 @@
40
40
  {"id":"pii-neg-014","text":"nos vemos luego para tomar algo","expected_types":[],"lang":"es","tag":"negative"}
41
41
  {"id":"pii-neg-015","text":"das Wetter ist heute wirklich schön","expected_types":[],"lang":"de","tag":"negative"}
42
42
  {"id":"pii-neg-016","text":"wir sehen uns morgen im Büro","expected_types":[],"lang":"de","tag":"negative"}
43
+ {"id":"pii-hneg-001","text":"order 4111 1111 1111 1112 was cancelled yesterday","expected_types":[],"lang":"en","tag":"hard_negative"}
44
+ {"id":"pii-hneg-002","text":"upgrade the library to version 2.14.3 before Friday","expected_types":[],"lang":"en","tag":"hard_negative"}
45
+ {"id":"pii-hneg-003","text":"invoice INV-2026-00123 is due on 2026-10-31","expected_types":[],"lang":"en","tag":"hard_negative"}
46
+ {"id":"pii-hneg-004","text":"ISBN 978-3-16-148410-0 is out of print","expected_types":[],"lang":"en","tag":"hard_negative"}
47
+ {"id":"pii-hneg-005","text":"build 20261008.1 passed all 4554 checks","expected_types":[],"lang":"en","tag":"hard_negative"}
48
+ {"id":"pii-hneg-006","text":"room 4B on floor 12, chapter 3.14 of the manual","expected_types":[],"lang":"en","tag":"hard_negative"}
49
+ {"id":"pii-hneg-007","text":"il codice articolo AB-12345-CD è esaurito in magazzino","expected_types":[],"lang":"it","tag":"hard_negative"}
50
+ {"id":"pii-hneg-008","text":"la pratica n. 2026/000457 è stata chiusa il 3 ottobre","expected_types":[],"lang":"it","tag":"hard_negative"}
@@ -123,8 +123,23 @@ def _pct(x: float | None) -> str:
123
123
  return "n/a" if x is None else f"{round(x * 100)}%"
124
124
 
125
125
 
126
+ def _hits(metric: dict) -> tuple[int, int]:
127
+ """(detected, expected) behind the recall of *metric*: samples for the
128
+ binary detectors, PII types for the PII detector."""
129
+ c = metric["counts"]
130
+ if "tp" in c:
131
+ return c["tp"], c["tp"] + c["fn"]
132
+ return c["intersect_total"], c["expected_total"]
133
+
134
+
126
135
  def _cell_recall(metric: dict | None) -> str:
127
- return _pct(recall_item(metric)[1]) if metric else "-"
136
+ if not metric:
137
+ return "-"
138
+ value = recall_item(metric)[1]
139
+ if value is None:
140
+ return _pct(value)
141
+ detected, expected = _hits(metric)
142
+ return f"{_pct(value)} ({detected}/{expected})"
128
143
 
129
144
 
130
145
  def to_markdown(scorecard: dict) -> str:
@@ -144,7 +159,12 @@ def to_markdown(scorecard: dict) -> str:
144
159
  lines.append(
145
160
  f"| {det} | {tag} | {_cell_recall(py)} | {_cell_recall(rs)} | {_cell_recall(pr)} |"
146
161
  )
147
- footnotes = []
162
+ footnotes = [
163
+ "_Each cell is the recall with the detected and expected counts. A class "
164
+ "often has one to three samples, so the figures are indicative, not a "
165
+ "benchmark. The labels of the corpora are assigned in this repository, "
166
+ "not adjudicated by a third party._"
167
+ ]
148
168
  external = scorecard.get("external_corpora")
149
169
  detector_of = external["corpora"] if external else {}
150
170
  if any(detector_of.get(name, name) == "pii" for name in scorecard["detectors"]):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: admina
3
- Version: 0.14.0
3
+ Version: 0.14.2
4
4
  Summary: Admina — governed AI development framework
5
5
  Author-email: Stefano Noferi <info@admina.org>
6
6
  Maintainer-email: Stefano Noferi <info@admina.org>
@@ -72,6 +72,7 @@ admina/domains/data_sovereignty/classification.py
72
72
  admina/domains/data_sovereignty/email_matching.py
73
73
  admina/domains/data_sovereignty/iban.py
74
74
  admina/domains/data_sovereignty/masking.py
75
+ admina/domains/data_sovereignty/names.py
75
76
  admina/domains/data_sovereignty/pii.py
76
77
  admina/domains/data_sovereignty/residency.py
77
78
  admina/engines/__init__.py
@@ -260,6 +261,7 @@ tests/test_offline_guarantee.py
260
261
  tests/test_oisg_evidence.py
261
262
  tests/test_otel_enabled_flag.py
262
263
  tests/test_pattern_probe.py
264
+ tests/test_person_names.py
263
265
  tests/test_pii_email_matching.py
264
266
  tests/test_pii_mask_style.py
265
267
  tests/test_pii_plugin_engines.py
@@ -19,7 +19,7 @@ build-backend = "setuptools.build_meta"
19
19
 
20
20
  [project]
21
21
  name = "admina"
22
- version = "0.14.0"
22
+ version = "0.14.2"
23
23
  description = "Admina — governed AI development framework"
24
24
  readme = "README.md"
25
25
  requires-python = ">=3.11"
@@ -0,0 +1,122 @@
1
+ # Copyright © 2025–2026 Stefano Noferi & Admina contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """A PERSON entity of an NER model is masked only when it reads as a name:
17
+ no digit, and no lowercase word that is a stop word of English, Italian,
18
+ German, French, Spanish or Portuguese.
19
+
20
+ The English spaCy model labels phrases as PERSON ("il codice articolo",
21
+ "ci vediamo domani", "das Wetter", "grab a coffee"); such spans are not
22
+ masked, by the PII redactor, the spaCy + regex engine and the Presidio
23
+ engine. Names written in lowercase are masked.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import asyncio
29
+ from types import SimpleNamespace
30
+
31
+ import pytest
32
+
33
+ from admina.domains.data_sovereignty.names import is_name_like
34
+ from admina.domains.data_sovereignty.pii import PIIRedactor
35
+
36
+
37
+ @pytest.mark.parametrize(
38
+ "text",
39
+ [
40
+ "Mario Rossi",
41
+ "mario rossi",
42
+ "john smith",
43
+ "giovanni esposito",
44
+ "O'Brien",
45
+ "JOHN",
46
+ "Sara Rossi",
47
+ "sara rossi", # "sara" is an Italian stop word, kept as a name
48
+ "Mia",
49
+ "will smith", # "will" is an English stop word, kept as a name
50
+ "Il Signore", # capitalised words are not checked
51
+ ],
52
+ )
53
+ def test_names(text):
54
+ assert is_name_like(text)
55
+
56
+
57
+ @pytest.mark.parametrize(
58
+ "text",
59
+ [
60
+ "il codice articolo",
61
+ "la pratica n. 2026/000457",
62
+ "ci vediamo domani",
63
+ "ci vediamo, domani",
64
+ "(il) progetto",
65
+ "ho parlato",
66
+ "das Wetter",
67
+ "il cliente",
68
+ "grab a coffee",
69
+ "email john.doe@example.org",
70
+ "Mario/Rossi",
71
+ "John 3",
72
+ "",
73
+ " ",
74
+ ],
75
+ )
76
+ def test_not_names(text):
77
+ assert not is_name_like(text)
78
+
79
+
80
+ class _Nlp:
81
+ """Labels each given phrase of the text as PERSON."""
82
+
83
+ def __init__(self, *phrases: str) -> None:
84
+ self.phrases = phrases
85
+
86
+ def __call__(self, text: str) -> SimpleNamespace:
87
+ ents = []
88
+ for phrase in self.phrases:
89
+ start = text.find(phrase)
90
+ if start >= 0:
91
+ ents.append(
92
+ SimpleNamespace(
93
+ start_char=start,
94
+ end_char=start + len(phrase),
95
+ text=phrase,
96
+ label_="PERSON",
97
+ )
98
+ )
99
+ return SimpleNamespace(ents=ents)
100
+
101
+
102
+ def test_the_redactor_masks_names_only():
103
+ redactor = PIIRedactor()
104
+ redactor.nlp = _Nlp("il codice articolo", "Mario Rossi")
105
+ out = redactor.redact("il codice articolo AB-12 è di Mario Rossi")
106
+ assert out["redacted_text"] == "il codice articolo AB-12 è di [PERSON]"
107
+
108
+
109
+ def test_the_redactor_masks_lowercase_names():
110
+ redactor = PIIRedactor()
111
+ redactor.nlp = _Nlp("il cliente", "giovanni esposito")
112
+ out = redactor.redact("il cliente è giovanni esposito")
113
+ assert out["redacted_text"] == "il cliente è [PERSON]"
114
+
115
+
116
+ def test_the_spacy_regex_engine_reports_names_only():
117
+ from admina.plugins.builtin.pii.spacy_regex import SpaCyRegexPIIEngine
118
+
119
+ engine = SpaCyRegexPIIEngine()
120
+ engine._nlp, engine._nlp_loaded = _Nlp("il codice articolo", "Mario Rossi"), True
121
+ found = asyncio.run(engine.detect("il codice articolo AB-12 è di Mario Rossi"))
122
+ assert [(m["type"], m["text"]) for m in found] == [("PERSON", "Mario Rossi")]
@@ -123,13 +123,14 @@ def _blocked(resp: httpx.Response) -> bool:
123
123
 
124
124
  # How long the slow job (a firewall check, a redaction) sleeps in the thread
125
125
  # that runs it, and the largest event loop stall the tests accept. Run on the
126
- # event loop, the job stalls it for its whole duration; run in a worker
127
- # thread, the loop only waits for the scheduler. The bound is half the job:
128
- # well below the stall of a blocked loop, well above the scheduling delays of
129
- # a loaded CI runner (stalls just over 0.1 s were seen on macOS runners with
130
- # the loop free).
131
- SLOW_JOB = 0.6
132
- MAX_STALL = SLOW_JOB / 2
126
+ # event loop, or awaited there without yielding, the job stalls the loop for
127
+ # at least its whole duration: a stall shorter than the job means the loop
128
+ # was free. The bound is the job itself, so the test fails for a blocked
129
+ # loop whatever the load, and passes with the loop free unless the scheduler
130
+ # holds it for a whole second (stalls of up to 0.51 s were seen on loaded
131
+ # macOS runners with the loop free).
132
+ SLOW_JOB = 1.0
133
+ MAX_STALL = SLOW_JOB
133
134
 
134
135
 
135
136
  async def _largest_gap(stop: asyncio.Event) -> float:
@@ -215,3 +215,17 @@ def test_text_with_many_dots_is_analyzed_quickly():
215
215
  # Every recognizer of Presidio took about a minute on this text; the
216
216
  # mapped ones take a fraction of a second.
217
217
  assert time.perf_counter() - start < 5.0
218
+
219
+
220
+ def test_person_spans_that_are_not_names_are_not_masked(monkeypatch):
221
+ presidio = _presidio()
222
+ from presidio_analyzer import RecognizerResult
223
+
224
+ engine = presidio.PresidioPIIEngine(nlp_models={"it": "blank"})
225
+ text = "il codice articolo AB-12 è di Mario Rossi"
226
+ found = [
227
+ RecognizerResult("PERSON", 0, 18, 0.85), # "il codice articolo"
228
+ RecognizerResult("PERSON", 30, 41, 0.85), # "Mario Rossi"
229
+ ]
230
+ monkeypatch.setattr(engine._analyzer, "analyze", lambda text, language, entities: list(found))
231
+ assert engine.redact(text)["redacted_text"] == "il codice articolo AB-12 è di [PERSON]"
@@ -227,6 +227,18 @@ def test_build_scorecard_and_markdown_have_caveat():
227
227
  assert "Detector" in md
228
228
 
229
229
 
230
+ def test_markdown_cells_show_detected_over_expected():
231
+ results = {
232
+ "injection": {"python": _fake_eval_binary()},
233
+ "pii": {"python": _fake_eval_pii()},
234
+ }
235
+ md = report.to_markdown(report.build_scorecard(results, rust_version=None))
236
+ assert "| injection | leetspeak | 0% (0/1) |" in md
237
+ assert "| pii | email | 100% (4/4) |" in md
238
+ assert "indicative" in md
239
+ assert "not adjudicated by a third party" in md
240
+
241
+
230
242
  def test_make_baseline_shape():
231
243
  results = {"injection": {"python": _fake_eval_binary()}}
232
244
  card = report.build_scorecard(results, rust_version=None)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes