spanforge 2.0.0__tar.gz → 2.0.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 (319) hide show
  1. {spanforge-2.0.0 → spanforge-2.0.2}/CONFORMANCE.md +4 -0
  2. {spanforge-2.0.0 → spanforge-2.0.2}/PKG-INFO +4 -4
  3. {spanforge-2.0.0 → spanforge-2.0.2}/README.md +3 -3
  4. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/redact.md +5 -5
  5. {spanforge-2.0.0 → spanforge-2.0.2}/docs/changelog.md +40 -1
  6. {spanforge-2.0.0 → spanforge-2.0.2}/docs/cli.md +2 -1
  7. {spanforge-2.0.0 → spanforge-2.0.2}/docs/quickstart.md +25 -0
  8. {spanforge-2.0.0 → spanforge-2.0.2}/pyproject.toml +1 -1
  9. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/__init__.py +1 -1
  10. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/core/compliance_mapping.py +10 -3
  11. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/redact.py +138 -0
  12. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase11_security.py +1 -1
  13. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_redact.py +277 -0
  14. {spanforge-2.0.0 → spanforge-2.0.2}/.gitattributes +0 -0
  15. {spanforge-2.0.0 → spanforge-2.0.2}/.github/CODEOWNERS +0 -0
  16. {spanforge-2.0.0 → spanforge-2.0.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  17. {spanforge-2.0.0 → spanforge-2.0.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  18. {spanforge-2.0.0 → spanforge-2.0.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  19. {spanforge-2.0.0 → spanforge-2.0.2}/.github/ISSUE_TEMPLATE/rfc.yml +0 -0
  20. {spanforge-2.0.0 → spanforge-2.0.2}/.github/pull_request_template.md +0 -0
  21. {spanforge-2.0.0 → spanforge-2.0.2}/.github/workflows/ci.yml +0 -0
  22. {spanforge-2.0.0 → spanforge-2.0.2}/.github/workflows/release.yml +0 -0
  23. {spanforge-2.0.0 → spanforge-2.0.2}/.gitignore +0 -0
  24. {spanforge-2.0.0 → spanforge-2.0.2}/CNAME +0 -0
  25. {spanforge-2.0.0 → spanforge-2.0.2}/CODE_OF_CONDUCT.md +0 -0
  26. {spanforge-2.0.0 → spanforge-2.0.2}/LICENSE +0 -0
  27. {spanforge-2.0.0 → spanforge-2.0.2}/MAINTAINERS.md +0 -0
  28. {spanforge-2.0.0 → spanforge-2.0.2}/PRICING.md +0 -0
  29. {spanforge-2.0.0 → spanforge-2.0.2}/README.md.bak +0 -0
  30. {spanforge-2.0.0 → spanforge-2.0.2}/RELEASE.md +0 -0
  31. {spanforge-2.0.0 → spanforge-2.0.2}/SECURITY.md +0 -0
  32. {spanforge-2.0.0 → spanforge-2.0.2}/docs/Makefile +0 -0
  33. {spanforge-2.0.0 → spanforge-2.0.2}/docs/_static/.gitkeep +0 -0
  34. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/auto.md +0 -0
  35. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/cache.md +0 -0
  36. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/compliance.md +0 -0
  37. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/consumer.md +0 -0
  38. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/debug.md +0 -0
  39. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/deprecations.md +0 -0
  40. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/event.md +0 -0
  41. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/exceptions.md +0 -0
  42. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/export.md +0 -0
  43. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/governance.md +0 -0
  44. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/hooks.md +0 -0
  45. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/index.md +0 -0
  46. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/integrations.md +0 -0
  47. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/lint.md +0 -0
  48. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/metrics.md +0 -0
  49. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/migrate.md +0 -0
  50. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/models.md +0 -0
  51. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/normalizer.md +0 -0
  52. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/signing.md +0 -0
  53. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/store.md +0 -0
  54. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/stream.md +0 -0
  55. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/testing.md +0 -0
  56. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/trace.md +0 -0
  57. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/types.md +0 -0
  58. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/ulid.md +0 -0
  59. {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/validate.md +0 -0
  60. {spanforge-2.0.0 → spanforge-2.0.2}/docs/conf.py +0 -0
  61. {spanforge-2.0.0 → spanforge-2.0.2}/docs/configuration.md +0 -0
  62. {spanforge-2.0.0 → spanforge-2.0.2}/docs/contributing.md +0 -0
  63. {spanforge-2.0.0 → spanforge-2.0.2}/docs/deployment/air-gapped.md +0 -0
  64. {spanforge-2.0.0 → spanforge-2.0.2}/docs/deployment/kubernetes.md +0 -0
  65. {spanforge-2.0.0 → spanforge-2.0.2}/docs/index.md +0 -0
  66. {spanforge-2.0.0 → spanforge-2.0.2}/docs/installation.md +0 -0
  67. {spanforge-2.0.0 → spanforge-2.0.2}/docs/integrations/crewai.md +0 -0
  68. {spanforge-2.0.0 → spanforge-2.0.2}/docs/make.bat +0 -0
  69. {spanforge-2.0.0 → spanforge-2.0.2}/docs/migrations/from-langfuse.md +0 -0
  70. {spanforge-2.0.0 → spanforge-2.0.2}/docs/migrations/from-langsmith.md +0 -0
  71. {spanforge-2.0.0 → spanforge-2.0.2}/docs/migrations/from-openllmetry.md +0 -0
  72. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/audit.md +0 -0
  73. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/cache.md +0 -0
  74. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/consent.md +0 -0
  75. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/cost.md +0 -0
  76. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/diff.md +0 -0
  77. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/eval.md +0 -0
  78. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/explanation.md +0 -0
  79. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/fence.md +0 -0
  80. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/guard.md +0 -0
  81. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/hitl.md +0 -0
  82. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/index.md +0 -0
  83. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/model_registry.md +0 -0
  84. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/prompt.md +0 -0
  85. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/redact_ns.md +0 -0
  86. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/template.md +0 -0
  87. {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/trace.md +0 -0
  88. {spanforge-2.0.0 → spanforge-2.0.2}/docs/rfc/rfc-0001.md +0 -0
  89. {spanforge-2.0.0 → spanforge-2.0.2}/docs/runbook.md +0 -0
  90. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/README.md +0 -0
  91. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/envelope.schema.json +0 -0
  92. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/agent-run.schema.json +0 -0
  93. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/agent-step.schema.json +0 -0
  94. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/audit.schema.json +0 -0
  95. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/cache.schema.json +0 -0
  96. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/consent.schema.json +0 -0
  97. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/cost.schema.json +0 -0
  98. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/diff.schema.json +0 -0
  99. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/eval.schema.json +0 -0
  100. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/explanation.schema.json +0 -0
  101. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/fence.schema.json +0 -0
  102. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/guard.schema.json +0 -0
  103. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/hitl.schema.json +0 -0
  104. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/model-registry.schema.json +0 -0
  105. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/prompt.schema.json +0 -0
  106. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/redact.schema.json +0 -0
  107. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/span.schema.json +0 -0
  108. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/template.schema.json +0 -0
  109. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/types/common.schema.json +0 -0
  110. {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema-versioning.md +0 -0
  111. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/cache.md +0 -0
  112. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/compliance.md +0 -0
  113. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/custom_exporters.md +0 -0
  114. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/debugging.md +0 -0
  115. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/events.md +0 -0
  116. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/export.md +0 -0
  117. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/governance.md +0 -0
  118. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/index.md +0 -0
  119. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/linting.md +0 -0
  120. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/metrics.md +0 -0
  121. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/migration.md +0 -0
  122. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/redaction.md +0 -0
  123. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/signing.md +0 -0
  124. {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/tracing.md +0 -0
  125. {spanforge-2.0.0 → spanforge-2.0.2}/examples/agent_workflow.py +0 -0
  126. {spanforge-2.0.0 → spanforge-2.0.2}/examples/budget_alert.py +0 -0
  127. {spanforge-2.0.0 → spanforge-2.0.2}/examples/docker/Dockerfile +0 -0
  128. {spanforge-2.0.0 → spanforge-2.0.2}/examples/docker/docker-compose.yml +0 -0
  129. {spanforge-2.0.0 → spanforge-2.0.2}/examples/docker/otel-config.yaml +0 -0
  130. {spanforge-2.0.0 → spanforge-2.0.2}/examples/langchain_chain.py +0 -0
  131. {spanforge-2.0.0 → spanforge-2.0.2}/examples/multi_agent_rag.py +0 -0
  132. {spanforge-2.0.0 → spanforge-2.0.2}/examples/multi_tenant.py +0 -0
  133. {spanforge-2.0.0 → spanforge-2.0.2}/examples/openai_chat.py +0 -0
  134. {spanforge-2.0.0 → spanforge-2.0.2}/examples/otlp_grafana.py +0 -0
  135. {spanforge-2.0.0 → spanforge-2.0.2}/examples/production_multi_agent.py +0 -0
  136. {spanforge-2.0.0 → spanforge-2.0.2}/examples/secure_pipeline.py +0 -0
  137. {spanforge-2.0.0 → spanforge-2.0.2}/examples/streaming_response.py +0 -0
  138. {spanforge-2.0.0 → spanforge-2.0.2}/sonar-project.properties +0 -0
  139. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_batch_exporter.py +0 -0
  140. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_cli.py +0 -0
  141. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_hooks.py +0 -0
  142. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_server.py +0 -0
  143. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_span.py +0 -0
  144. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_store.py +0 -0
  145. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_stream.py +0 -0
  146. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_trace.py +0 -0
  147. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_tracer.py +0 -0
  148. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/actor.py +0 -0
  149. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/alerts.py +0 -0
  150. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/auto.py +0 -0
  151. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/baseline.py +0 -0
  152. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/config.py +0 -0
  153. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/consent.py +0 -0
  154. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/consumer.py +0 -0
  155. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/core/__init__.py +0 -0
  156. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/cost.py +0 -0
  157. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/debug.py +0 -0
  158. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/drift.py +0 -0
  159. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/egress.py +0 -0
  160. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/eval.py +0 -0
  161. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/event.py +0 -0
  162. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/exceptions.py +0 -0
  163. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/explain.py +0 -0
  164. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/__init__.py +0 -0
  165. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/append_only.py +0 -0
  166. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/cloud.py +0 -0
  167. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/datadog.py +0 -0
  168. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/grafana.py +0 -0
  169. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/jsonl.py +0 -0
  170. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/otel_bridge.py +0 -0
  171. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/otlp.py +0 -0
  172. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/otlp_bridge.py +0 -0
  173. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/redis_backend.py +0 -0
  174. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/webhook.py +0 -0
  175. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/exporters/__init__.py +0 -0
  176. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/exporters/console.py +0 -0
  177. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/exporters/jsonl.py +0 -0
  178. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/hitl.py +0 -0
  179. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/inspect.py +0 -0
  180. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/__init__.py +0 -0
  181. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/_pricing.py +0 -0
  182. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/anthropic.py +0 -0
  183. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/bedrock.py +0 -0
  184. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/crewai.py +0 -0
  185. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/gemini.py +0 -0
  186. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/groq.py +0 -0
  187. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/langchain.py +0 -0
  188. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/llamaindex.py +0 -0
  189. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/ollama.py +0 -0
  190. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/openai.py +0 -0
  191. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/together.py +0 -0
  192. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/metrics.py +0 -0
  193. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/metrics_export.py +0 -0
  194. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/migrate.py +0 -0
  195. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/model_registry.py +0 -0
  196. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/models.py +0 -0
  197. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/__init__.py +0 -0
  198. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/audit.py +0 -0
  199. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/cache.py +0 -0
  200. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/chain.py +0 -0
  201. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/confidence.py +0 -0
  202. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/consent.py +0 -0
  203. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/cost.py +0 -0
  204. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/decision.py +0 -0
  205. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/diff.py +0 -0
  206. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/drift.py +0 -0
  207. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/eval_.py +0 -0
  208. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/fence.py +0 -0
  209. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/guard.py +0 -0
  210. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/hitl.py +0 -0
  211. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/latency.py +0 -0
  212. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/prompt.py +0 -0
  213. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/redact.py +0 -0
  214. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/template.py +0 -0
  215. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/tool_call.py +0 -0
  216. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/trace.py +0 -0
  217. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/normalizer.py +0 -0
  218. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/presidio_backend.py +0 -0
  219. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/processor.py +0 -0
  220. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/prompt_registry.py +0 -0
  221. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/py.typed +0 -0
  222. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/sampling.py +0 -0
  223. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/schemas/v1.0/schema.json +0 -0
  224. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/schemas/v2.0/schema.json +0 -0
  225. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/signing.py +0 -0
  226. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/stream.py +0 -0
  227. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/testing.py +0 -0
  228. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/trace.py +0 -0
  229. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/types.py +0 -0
  230. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/ulid.py +0 -0
  231. {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/validate.py +0 -0
  232. {spanforge-2.0.0 → spanforge-2.0.2}/test_agent.jsonl +0 -0
  233. {spanforge-2.0.0 → spanforge-2.0.2}/test_events.jsonl +0 -0
  234. {spanforge-2.0.0 → spanforge-2.0.2}/tests/__init__.py +0 -0
  235. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/__init__.py +0 -0
  236. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/chain.json +0 -0
  237. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/compliance.json +0 -0
  238. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/key_security.json +0 -0
  239. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/migration.json +0 -0
  240. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/pii.json +0 -0
  241. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/signing.json +0 -0
  242. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures.json +0 -0
  243. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/run_conformance.py +0 -0
  244. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/test_conformance.py +0 -0
  245. {spanforge-2.0.0 → spanforge-2.0.2}/tests/conftest.py +0 -0
  246. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_actor.py +0 -0
  247. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_alerts.py +0 -0
  248. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_auto.py +0 -0
  249. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_baseline.py +0 -0
  250. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_benchmarks.py +0 -0
  251. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_budget_alert.py +0 -0
  252. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_cli.py +0 -0
  253. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_compliance_mapping.py +0 -0
  254. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_consent.py +0 -0
  255. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_consumer.py +0 -0
  256. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_cost_event_emission.py +0 -0
  257. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_cost_tracker.py +0 -0
  258. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_costguard_gaps.py +0 -0
  259. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_drift.py +0 -0
  260. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_event.py +0 -0
  261. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_exceptions.py +0 -0
  262. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_explain.py +0 -0
  263. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_cloud.py +0 -0
  264. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_datadog.py +0 -0
  265. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_grafana.py +0 -0
  266. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_jsonl.py +0 -0
  267. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_otel_bridge.py +0 -0
  268. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_otlp.py +0 -0
  269. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_redis_backend.py +0 -0
  270. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_webhook.py +0 -0
  271. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_hitl.py +0 -0
  272. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_inspect.py +0 -0
  273. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_integration.py +0 -0
  274. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_integrations.py +0 -0
  275. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_migrate.py +0 -0
  276. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_model_registry.py +0 -0
  277. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_models.py +0 -0
  278. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_namespaces.py +0 -0
  279. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_otlp_bridge.py +0 -0
  280. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase10_features.py +0 -0
  281. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase1_context_trace.py +0 -0
  282. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase2_observability.py +0 -0
  283. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase3_debug_sampling.py +0 -0
  284. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase4_agent_instrumentation.py +0 -0
  285. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase4_metrics_store.py +0 -0
  286. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase5_console_exporter.py +0 -0
  287. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase5_coverage.py +0 -0
  288. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase5_hooks_crewai.py +0 -0
  289. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase6_openai_integration.py +0 -0
  290. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_processor_coverage.py +0 -0
  291. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_properties.py +0 -0
  292. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_rfc_namespaces.py +0 -0
  293. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sampling_coverage.py +0 -0
  294. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_config.py +0 -0
  295. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_coverage_boost.py +0 -0
  296. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_exporters.py +0 -0
  297. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_final_coverage.py +0 -0
  298. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_gap_filler.py +0 -0
  299. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_openai_integration.py +0 -0
  300. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_phase7_integrations.py +0 -0
  301. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_precision_coverage.py +0 -0
  302. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_span.py +0 -0
  303. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_stream.py +0 -0
  304. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_tracer.py +0 -0
  305. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_validation_coverage.py +0 -0
  306. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_server.py +0 -0
  307. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf11.py +0 -0
  308. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf12.py +0 -0
  309. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf13.py +0 -0
  310. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf14.py +0 -0
  311. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf15.py +0 -0
  312. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf16.py +0 -0
  313. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_signing.py +0 -0
  314. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_stream.py +0 -0
  315. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_trace_decorator.py +0 -0
  316. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_trace_pytest_fixtures.py +0 -0
  317. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_types.py +0 -0
  318. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_ulid.py +0 -0
  319. {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_validate.py +0 -0
@@ -45,6 +45,8 @@ Each fixture carries a `clause` field mapping to a numbered requirement:
45
45
  | GA-03-REQ-02 | scan_payload MUST detect SSN patterns |
46
46
  | GA-03-REQ-03 | PIIScanHit MUST include match_count and sensitivity fields |
47
47
  | GA-03-REQ-04 | Credit card detection MUST apply Luhn validation |
48
+ | GA-03-REQ-05 | SSN detection MUST reject invalid SSA ranges (area 000, 666, 900–999; group 00; serial 0000) |
49
+ | GA-03-REQ-06 | date_of_birth detection MUST reject calendar-invalid dates (e.g. Feb 30, month 13) |
48
50
  | GA-04-REQ-01 | GDPR erasure MUST produce tombstone events preserving chain integrity |
49
51
  | GA-05-REQ-01 | v1_to_v2 MUST rename 'model' to 'model_id' and bump schema_version |
50
52
  | GA-05-REQ-02 | md5-prefixed checksums MUST be rehashed to SHA-256 |
@@ -76,6 +78,8 @@ Each fixture carries a `clause` field mapping to a numbered requirement:
76
78
  | C013 | PII scan detects SSN | GA-03-REQ-02 | GA-03 |
77
79
  | C014 | PII hit match_count and sensitivity | GA-03-REQ-03 | GA-03 |
78
80
  | C015 | Credit card Luhn validation | GA-03-REQ-04 | GA-03 |
81
+ | C024 | SSN range validation rejects invalid ranges | GA-03-REQ-05 | GA-03 |
82
+ | C025 | date_of_birth rejects calendar-invalid dates | GA-03-REQ-06 | GA-03 |
79
83
  | C016 | Key expiry returns (status, days) tuple | GA-01-REQ-06 | GA-01 |
80
84
  | C017 | derive_key context isolation | GA-01-REQ-07 | GA-01 |
81
85
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spanforge
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: SpanForge — AI lifecycle and governance platform (RFC-0001 SPANFORGE)
5
5
  Project-URL: Homepage, https://github.com/veerarag1973/spanforge
6
6
  Project-URL: Documentation, https://github.com/veerarag1973/spanforge/blob/main/docs/index.md
@@ -350,7 +350,7 @@ assert result.valid # any tampering → False
350
350
 
351
351
  ### PII redaction
352
352
 
353
- Strip personal data before events leave your application boundary. Deep scanning with Luhn validation for credit card numbers.
353
+ Strip personal data before events leave your application boundary. Deep scanning with Luhn and Verhoeff validation for credit cards and Aadhaar numbers, SSN range validation (`_is_valid_ssn`), calendar validation for dates of birth (`_is_valid_date`), and built-in patterns for `date_of_birth` and street `address`.
354
354
 
355
355
  ```python
356
356
  from spanforge.redact import RedactionPolicy, Sensitivity
@@ -605,7 +605,7 @@ spanforge/
605
605
  </tr>
606
606
  <tr>
607
607
  <td><code>spanforge.redact</code></td>
608
- <td>PII detection, sensitivity levels, redaction policies, deep <code>scan_payload()</code> with Luhn validation, and <code>contains_pii()</code> / <code>assert_redacted()</code> with raw string scanning</td>
608
+ <td>PII detection, sensitivity levels, redaction policies, deep <code>scan_payload()</code> with Luhn / Verhoeff / SSN-range / date-calendar validation, built-in <code>date_of_birth</code> and <code>address</code> patterns, and <code>contains_pii()</code> / <code>assert_redacted()</code> with raw string scanning</td>
609
609
  <td>Data privacy / GDPR teams</td>
610
610
  </tr>
611
611
  <tr>
@@ -1430,7 +1430,7 @@ and opens it directly — useful for sharing trace snapshots offline.
1430
1430
  </tr>
1431
1431
  <tr>
1432
1432
  <td><code>spanforge.redact</code></td>
1433
- <td>PII detection, sensitivity levels, redaction policies, deep <code>scan_payload()</code> with Luhn validation, and <code>contains_pii()</code> / <code>assert_redacted()</code> with raw string scanning</td>
1433
+ <td>PII detection, sensitivity levels, redaction policies, deep <code>scan_payload()</code> with Luhn / Verhoeff / SSN-range / date-calendar validation, built-in <code>date_of_birth</code> and <code>address</code> patterns, and <code>contains_pii()</code> / <code>assert_redacted()</code> with raw string scanning</td>
1434
1434
  <td>Data privacy / GDPR teams</td>
1435
1435
  </tr>
1436
1436
  <tr>
@@ -252,7 +252,7 @@ assert result.valid # any tampering → False
252
252
 
253
253
  ### PII redaction
254
254
 
255
- Strip personal data before events leave your application boundary. Deep scanning with Luhn validation for credit card numbers.
255
+ Strip personal data before events leave your application boundary. Deep scanning with Luhn and Verhoeff validation for credit cards and Aadhaar numbers, SSN range validation (`_is_valid_ssn`), calendar validation for dates of birth (`_is_valid_date`), and built-in patterns for `date_of_birth` and street `address`.
256
256
 
257
257
  ```python
258
258
  from spanforge.redact import RedactionPolicy, Sensitivity
@@ -507,7 +507,7 @@ spanforge/
507
507
  </tr>
508
508
  <tr>
509
509
  <td><code>spanforge.redact</code></td>
510
- <td>PII detection, sensitivity levels, redaction policies, deep <code>scan_payload()</code> with Luhn validation, and <code>contains_pii()</code> / <code>assert_redacted()</code> with raw string scanning</td>
510
+ <td>PII detection, sensitivity levels, redaction policies, deep <code>scan_payload()</code> with Luhn / Verhoeff / SSN-range / date-calendar validation, built-in <code>date_of_birth</code> and <code>address</code> patterns, and <code>contains_pii()</code> / <code>assert_redacted()</code> with raw string scanning</td>
511
511
  <td>Data privacy / GDPR teams</td>
512
512
  </tr>
513
513
  <tr>
@@ -1332,7 +1332,7 @@ and opens it directly — useful for sharing trace snapshots offline.
1332
1332
  </tr>
1333
1333
  <tr>
1334
1334
  <td><code>spanforge.redact</code></td>
1335
- <td>PII detection, sensitivity levels, redaction policies, deep <code>scan_payload()</code> with Luhn validation, and <code>contains_pii()</code> / <code>assert_redacted()</code> with raw string scanning</td>
1335
+ <td>PII detection, sensitivity levels, redaction policies, deep <code>scan_payload()</code> with Luhn / Verhoeff / SSN-range / date-calendar validation, built-in <code>date_of_birth</code> and <code>address</code> patterns, and <code>contains_pii()</code> / <code>assert_redacted()</code> with raw string scanning</td>
1336
1336
  <td>Data privacy / GDPR teams</td>
1337
1337
  </tr>
1338
1338
  <tr>
@@ -183,7 +183,7 @@ The original event is **not** mutated.
183
183
 
184
184
  ## Module-level functions
185
185
 
186
- ### `contains_pii(event: Event, *, scan_raw: bool = False) -> bool`
186
+ ### `contains_pii(event: Event, *, scan_raw: bool = True) -> bool`
187
187
 
188
188
  Return `True` if any payload value is a `Redactable` with
189
189
  `sensitivity >= Sensitivity.PII`.
@@ -193,13 +193,13 @@ Return `True` if any payload value is a `Redactable` with
193
193
  | Parameter | Type | Description |
194
194
  |-----------|------|-------------|
195
195
  | `event` | `Event` | The event to inspect. |
196
- | `scan_raw` | `bool` | When `True`, also run regex-based PII scanning on payload strings (via `scan_payload()`), not just check for `Redactable` wrappers. Default `False`. |
196
+ | `scan_raw` | `bool` | When `True` (default), also run regex-based PII scanning on payload strings (via `scan_payload()`), not just check for `Redactable` wrappers. Pass `False` to check `Redactable` wrappers only. |
197
197
 
198
198
  **Returns:** `bool`
199
199
 
200
200
  ---
201
201
 
202
- ### `assert_redacted(event: Event, context: str = "", *, scan_raw: bool = False) -> None`
202
+ ### `assert_redacted(event: Event, context: str = "", *, scan_raw: bool = True) -> None`
203
203
 
204
204
  Raise `PIINotRedactedError` if the event still contains unredacted PII or PHI.
205
205
 
@@ -211,7 +211,7 @@ Use this as a guardrail before exporting events.
211
211
  |-----------|------|-------------|
212
212
  | `event` | `Event` | The event to check. |
213
213
  | `context` | `str` | Optional context string embedded in the exception message. |
214
- | `scan_raw` | `bool` | When `True`, also run regex-based PII scanning. Default `False`. |
214
+ | `scan_raw` | `bool` | When `True` (default), also run regex-based PII scanning. Pass `False` to check `Redactable` wrappers only. |
215
215
 
216
216
  **Raises:** `PIINotRedactedError` — if any `Redactable` PII/PHI values or raw PII patterns remain in the payload.
217
217
 
@@ -287,7 +287,7 @@ Scan a payload dictionary for PII using regex detectors.
287
287
  Walks the entire payload recursively (up to `max_depth`), testing every string
288
288
  value against the built-in pattern set plus any caller-supplied patterns.
289
289
 
290
- **Built-in detectors:** `email`, `phone`, `ssn`, `credit_card` (with Luhn validation), `ip_address`, `uk_national_insurance`.
290
+ **Built-in detectors:** `email`, `phone`, `ssn` (with SSA range validation via `_is_valid_ssn`), `credit_card` (with Luhn validation), `ip_address`, `uk_national_insurance`, `date_of_birth` (global formats — ISO, US MDY, day-first DMY, written-month DMY/MDY — with calendar validation via `_is_valid_date`), `address`.
291
291
 
292
292
  **Args:**
293
293
 
@@ -6,7 +6,7 @@ this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ---
8
8
 
9
- ## 2.1.0 — 2026-06-XX
9
+ ## 2.0.2 — 2026-04-14
10
10
 
11
11
  **Compliance Integration Hardening & CostGuard Enhancements**
12
12
 
@@ -106,6 +106,45 @@ this project adheres to [Semantic Versioning](https://semver.org/).
106
106
  - Both types mapped to sensitivity `"high"` in `_SENSITIVITY_MAP`.
107
107
  - Exported from the top-level `spanforge` package.
108
108
 
109
+ ### Added — Extended PII Pattern Coverage
110
+
111
+ - **`date_of_birth` pattern** — detects dates of birth across all major global
112
+ formats (centuries 1900–2099):
113
+ - ISO / year-first: `YYYY-MM-DD`, `YYYY/MM/DD`, `YYYY.MM.DD`
114
+ - US month-first: `MM/DD/YYYY`, `MM-DD-YYYY`, `MM.DD.YYYY`
115
+ - Day-first (UK, EU, Germany, Asia, Australia, Latin America): `DD/MM/YYYY`,
116
+ `DD-MM-YYYY`, `DD.MM.YYYY`
117
+ - Written day-first: `15 Jan 2000`, `15-Jan-2000`, `15 January 2000`
118
+ - Written month-first: `Jan 15, 2000`, `January 15 2000`
119
+
120
+ Secondary calendar validation via `_is_valid_date()` rejects impossible dates
121
+ (e.g. `02/30/1990`, `31/04/1990`). Mapped to sensitivity `"high"`.
122
+ - **`address` pattern** — detects street addresses (`<number> <name> <suffix>`)
123
+ with a curated suffix list (Street/St, Avenue/Ave, Road/Rd, Boulevard/Blvd,
124
+ Drive/Dr, Lane/Ln, Court/Ct, Way, Place/Pl, Circle/Cir, Trail/Trl,
125
+ Terrace/Ter, Parkway/Pkwy, Highway/Hwy, Route/Rte). Mapped to sensitivity
126
+ `"medium"`.
127
+ - **`_is_valid_ssn(ssn_str)`** — SSA range validator applied post-regex to every
128
+ SSN match in `scan_payload()`. Rejects area `000`, area `666`, areas
129
+ `900–999` (ITIN-reserved), group `00`, and serial `0000`, eliminating the
130
+ most common false-positive ranges.
131
+ - **`_is_valid_date(date_str)`** — calendar correctness validator applied
132
+ post-regex to every `date_of_birth` match. Tries 15 `strptime` format
133
+ strings covering all numeric and written-month orderings; delegates to
134
+ `datetime.strptime` for accurate month-length and leap-year enforcement.
135
+ - Both validators follow the same pattern as existing `_luhn_check()` and
136
+ `_verhoeff_check()` — applied inside `scan_payload._walk()` after the regex
137
+ pass.
138
+
139
+ ### Fixed — Compliance Attestation with Missing Signing Key
140
+
141
+ - `generate_evidence_package()`, `to_pdf()`, and `verify_attestation_signature()`
142
+ previously raised `ValueError` when `SPANFORGE_SIGNING_KEY` was not set in
143
+ the environment. They now emit a `logging.WARNING` and fall back to an
144
+ insecure internal default (`_INSECURE_DEFAULT_KEY`). **Production
145
+ deployments must always set `SPANFORGE_SIGNING_KEY`; the default key exists
146
+ only for development and CI environments.**
147
+
109
148
  ### Added — Compliance Dashboard in SPA Viewer
110
149
 
111
150
  - **Clause pass/fail table** — clicking the compliance chip in the
@@ -794,7 +794,8 @@ result = scan_payload(event.payload, extra_patterns=DPDP_PATTERNS)
794
794
  ```
795
795
 
796
796
  Built-in types: `email`, `phone`, `ssn`, `credit_card`, `ip_address`,
797
- `uk_national_insurance`. DPDP add-on types: `aadhaar` (high), `pan` (high).
797
+ `uk_national_insurance`, `date_of_birth` (global formats: ISO, US, day-first
798
+ DMY, written-month), `address`. DPDP add-on types: `aadhaar` (high), `pan` (high).
798
799
 
799
800
  ---
800
801
 
@@ -163,6 +163,31 @@ for hit in result.hits:
163
163
  # pan: pan (sensitivity=high)
164
164
  ```
165
165
 
166
+ ### Date-of-birth and address detection
167
+
168
+ `scan_payload()` also detects dates of birth and US street addresses out of the
169
+ box — no extra patterns required:
170
+
171
+ ```python
172
+ from spanforge.redact import scan_payload
173
+
174
+ result = scan_payload({
175
+ "dob": "04/15/1990",
176
+ "home": "123 Maple Street",
177
+ })
178
+ for hit in result.hits:
179
+ print(f"{hit.pii_type}: {hit.path} (sensitivity={hit.sensitivity})")
180
+ # date_of_birth: dob (sensitivity=high)
181
+ # address: home (sensitivity=medium)
182
+ ```
183
+
184
+ The `date_of_birth` detector recognises all major global formats: ISO
185
+ (`YYYY-MM-DD`, `YYYY.MM.DD`), US month-first (`MM/DD/YYYY`, `MM.DD.YYYY`),
186
+ day-first (UK/EU/Asia: `DD/MM/YYYY`, `DD.MM.YYYY`), and written-month forms
187
+ (`15 Jan 2000`, `January 15, 2000`). Calendar-invalid dates (e.g.
188
+ `02/30/1990`, `31/04/1990`) and SSNs in reserved ranges (area `000`, `666`,
189
+ `900–999`) are automatically filtered out to reduce false positives.
190
+
166
191
  ## Exporting events
167
192
 
168
193
  ```python
@@ -7,7 +7,7 @@ build-backend = "hatchling.build"
7
7
  # ---------------------------------------------------------------------------
8
8
  [project]
9
9
  name = "spanforge"
10
- version = "2.0.0"
10
+ version = "2.0.2"
11
11
  description = "SpanForge — AI lifecycle and governance platform (RFC-0001 SPANFORGE)"
12
12
  readme = "README.md"
13
13
  license = { text = "MIT" }
@@ -399,7 +399,7 @@ from spanforge.explain import (
399
399
  )
400
400
  from spanforge.namespaces.consent import ConsentPayload
401
401
  from spanforge.namespaces.hitl import HITLPayload
402
- __version__: str = "2.0.0"
402
+ __version__: str = "2.0.2"
403
403
  #: RFC-0001 SPANFORGE conformance profile label.
404
404
  from typing import Final as _Final
405
405
  CONFORMANCE_PROFILE: _Final[str] = "SPANFORGE-Enterprise-2.0"
@@ -39,6 +39,10 @@ __all__ = [
39
39
 
40
40
  _log = logging.getLogger("spanforge.core.compliance_mapping")
41
41
 
42
+ # Fallback signing key used when SPANFORGE_SIGNING_KEY is absent. Only
43
+ # safe for development / CI — never use in production.
44
+ _INSECURE_DEFAULT_KEY: str = "spanforge-insecure-default-do-not-use-in-production"
45
+
42
46
  # ---------------------------------------------------------------------------
43
47
  # Framework enum
44
48
  # ---------------------------------------------------------------------------
@@ -506,11 +510,12 @@ class ComplianceEvidencePackage:
506
510
  pdf_hash = hashlib.sha256(pdf_bytes).hexdigest()
507
511
  signing_key = os.environ.get("SPANFORGE_SIGNING_KEY", "")
508
512
  if not signing_key or signing_key == "spanforge-default":
509
- raise ValueError(
513
+ _log.warning(
510
514
  "SPANFORGE_SIGNING_KEY is not set or uses the insecure default value. "
511
515
  "Set a strong secret before generating PDF attestations for production. "
512
516
  "Example: export SPANFORGE_SIGNING_KEY=$(openssl rand -hex 32)"
513
517
  )
518
+ signing_key = _INSECURE_DEFAULT_KEY
514
519
  pdf_hmac = _hmac.new(
515
520
  signing_key.encode(),
516
521
  pdf_hash.encode(),
@@ -688,11 +693,12 @@ class ComplianceMappingEngine:
688
693
  )
689
694
  signing_key = os.environ.get("SPANFORGE_SIGNING_KEY", "")
690
695
  if not signing_key or signing_key == "spanforge-default":
691
- raise ValueError(
696
+ _log.warning(
692
697
  "SPANFORGE_SIGNING_KEY is not set or uses the insecure default value. "
693
698
  "Set a strong secret before generating compliance attestations for production. "
694
699
  "Example: export SPANFORGE_SIGNING_KEY=$(openssl rand -hex 32)"
695
700
  )
701
+ signing_key = _INSECURE_DEFAULT_KEY
696
702
  hmac_sig = _hmac.new(
697
703
  signing_key.encode(),
698
704
  sig_payload.encode(),
@@ -1004,11 +1010,12 @@ def verify_attestation_signature(attestation: ComplianceAttestation) -> bool:
1004
1010
  )
1005
1011
  signing_key = os.environ.get("SPANFORGE_SIGNING_KEY", "")
1006
1012
  if not signing_key or signing_key == "spanforge-default":
1007
- raise ValueError(
1013
+ _log.warning(
1008
1014
  "SPANFORGE_SIGNING_KEY is not set or uses the insecure default value. "
1009
1015
  "Attestation verification requires the same key used at signing time. "
1010
1016
  "Example: export SPANFORGE_SIGNING_KEY=<your-secret>"
1011
1017
  )
1018
+ signing_key = _INSECURE_DEFAULT_KEY
1012
1019
  expected = _hmac.new(
1013
1020
  signing_key.encode(),
1014
1021
  sig_payload.encode(),
@@ -586,6 +586,41 @@ _PII_PATTERNS: Final[dict[str, re.Pattern[str]]] = {
586
586
  r"\b[A-CEGHJ-PR-TW-Z]{2}\s?\d{2}\s?\d{2}\s?\d{2}\s?[A-D]\b",
587
587
  re.IGNORECASE,
588
588
  ),
589
+ # Date of birth — numeric (/, -, .) and written-month forms covering
590
+ # ISO/YMD, US MDY, day-first DMY (Europe/Asia/Australia/etc.), and
591
+ # long/short written-month variants. Years restricted to 19xx–20xx to
592
+ # limit false positives. _is_valid_date() provides secondary calendar-
593
+ # correctness check (leap-year rules, month lengths, etc.).
594
+ "date_of_birth": re.compile(
595
+ # ISO / YMD: YYYY-MM-DD, YYYY/MM/DD, YYYY.MM.DD
596
+ r"\b(?:19|20)\d{2}[-/.](?:0?[1-9]|1[0-2])[-/.](?:0?[1-9]|[12]\d|3[01])\b"
597
+ r"|"
598
+ # US MDY: MM/DD/YYYY, MM-DD-YYYY, MM.DD.YYYY
599
+ r"\b(?:0?[1-9]|1[0-2])[-/.](?:0?[1-9]|[12]\d|3[01])[-/.](?:19|20)\d{2}\b"
600
+ r"|"
601
+ # Day-first DMY: DD/MM/YYYY, DD-MM-YYYY, DD.MM.YYYY (UK, EU, Asia, etc.)
602
+ r"\b(?:0?[1-9]|[12]\d|3[01])[-/.](?:0?[1-9]|1[0-2])[-/.](?:19|20)\d{2}\b"
603
+ r"|"
604
+ # Written DMY: "15 Jan 2000", "15-Jan-2000", "15 January 2000"
605
+ r"\b(?:0?[1-9]|[12]\d|3[01])[\s\-]"
606
+ r"(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?"
607
+ r"|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)"
608
+ r"[\s\-](?:19|20)\d{2}\b"
609
+ r"|"
610
+ # Written MDY: "Jan 15, 2000", "January 15 2000"
611
+ r"\b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?"
612
+ r"|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)"
613
+ r"\s+(?:0?[1-9]|[12]\d|3[01]),?\s+(?:19|20)\d{2}\b",
614
+ re.IGNORECASE,
615
+ ),
616
+ # Street address — house number + street name + recognised suffix
617
+ "address": re.compile(
618
+ r"\b\d{1,5}\s+(?:[A-Za-z0-9'.#\-]+\s+){1,5}"
619
+ r"(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Drive|Dr|Lane|Ln|"
620
+ r"Court|Ct|Way|Place|Pl|Circle|Cir|Trail|Trl|Terrace|Ter|"
621
+ r"Parkway|Pkwy|Highway|Hwy|Route|Rte)\.?\b",
622
+ re.IGNORECASE,
623
+ ),
589
624
  }
590
625
 
591
626
 
@@ -663,8 +698,10 @@ _SENSITIVITY_MAP: dict[str, str] = {
663
698
  "credit_card": "high",
664
699
  "aadhaar": "high",
665
700
  "pan": "high",
701
+ "date_of_birth": "high",
666
702
  "email": "medium",
667
703
  "phone": "medium",
704
+ "address": "medium",
668
705
  "ip_address": "low",
669
706
  "uk_national_insurance": "low",
670
707
  }
@@ -703,6 +740,89 @@ def _luhn_check(number_str: str) -> bool:
703
740
  return total % 10 == 0
704
741
 
705
742
 
743
+ def _is_valid_ssn(ssn_str: str) -> bool:
744
+ """Return ``False`` for SSNs in known-invalid SSA number ranges.
745
+
746
+ Filters out the following ranges that the SSA has *never* assigned:
747
+
748
+ * Area ``000`` — never issued.
749
+ * Area ``666`` — explicitly excluded by SSA policy.
750
+ * Areas ``900``–``999`` — reserved for Individual Taxpayer
751
+ Identification Numbers (ITINs); never used as SSNs.
752
+ * Group ``00`` — never issued within any valid area.
753
+ * Serial ``0000`` — never issued within any valid area/group.
754
+
755
+ Args:
756
+ ssn_str: Raw match string from :data:`_PII_PATTERNS` ``"ssn"``
757
+ regex (e.g. ``"123-45-6789"``).
758
+
759
+ Returns:
760
+ ``True`` if the SSN passes all range checks; ``False`` otherwise.
761
+ """
762
+ digits = "".join(c for c in ssn_str if c.isdigit())
763
+ if len(digits) != 9:
764
+ return False
765
+ area = int(digits[:3])
766
+ group = int(digits[3:5])
767
+ serial = int(digits[5:])
768
+ if area == 0 or area == 666 or area >= 900:
769
+ return False
770
+ if group == 0:
771
+ return False
772
+ if serial == 0:
773
+ return False
774
+ return True
775
+
776
+
777
+ def _is_valid_date(date_str: str) -> bool:
778
+ """Return ``True`` if *date_str* is a valid calendar date.
779
+
780
+ Accepts all numeric and written-month formats produced by the
781
+ ``"date_of_birth"`` regex in :data:`_PII_PATTERNS`.
782
+
783
+ Numeric formats (separators ``/``, ``-``, ``.``):
784
+
785
+ * ``YYYY/MM/DD``, ``YYYY-MM-DD``, ``YYYY.MM.DD`` — ISO / year-first
786
+ * ``MM/DD/YYYY``, ``MM-DD-YYYY``, ``MM.DD.YYYY`` — US month-first
787
+ * ``DD/MM/YYYY``, ``DD-MM-YYYY``, ``DD.MM.YYYY`` — day-first (Europe,
788
+ Asia, Australia, Latin America, etc.)
789
+
790
+ Written-month formats:
791
+
792
+ * ``DD Mon YYYY``, ``DD-Mon-YYYY``, ``DD Month YYYY`` (e.g. 15 Jan 2000)
793
+ * ``Mon DD, YYYY``, ``Mon DD YYYY``, ``Month DD, YYYY`` (e.g. Jan 15, 2000)
794
+
795
+ Delegates to :func:`datetime.datetime.strptime` so leap-year rules and
796
+ month-length limits are enforced (e.g. ``31/04/1990`` is rejected).
797
+
798
+ Args:
799
+ date_str: Raw match string from the ``"date_of_birth"`` regex.
800
+
801
+ Returns:
802
+ ``True`` if the string represents a real calendar date in any of the
803
+ recognised formats; ``False`` otherwise.
804
+ """
805
+ _FORMATS = (
806
+ # ISO / YMD
807
+ "%Y/%m/%d", "%Y-%m-%d", "%Y.%m.%d",
808
+ # US MDY
809
+ "%m/%d/%Y", "%m-%d-%Y", "%m.%d.%Y",
810
+ # Day-first DMY (Europe, Asia, Australia, Latin America, etc.)
811
+ "%d/%m/%Y", "%d-%m-%Y", "%d.%m.%Y",
812
+ # Written DMY: "15 Jan 2000", "15-Jan-2000", "15 January 2000"
813
+ "%d %b %Y", "%d-%b-%Y", "%d %B %Y", "%d-%B-%Y",
814
+ # Written MDY: "Jan 15, 2000", "Jan 15 2000", "January 15, 2000"
815
+ "%b %d, %Y", "%b %d %Y", "%B %d, %Y", "%B %d %Y",
816
+ )
817
+ for fmt in _FORMATS:
818
+ try:
819
+ datetime.datetime.strptime(date_str.strip(), fmt)
820
+ return True
821
+ except ValueError:
822
+ continue
823
+ return False
824
+
825
+
706
826
  def scan_payload(
707
827
  payload: dict[str, Any],
708
828
  *,
@@ -762,6 +882,24 @@ def scan_payload(
762
882
  if not valid_matches:
763
883
  continue
764
884
  matches = valid_matches
885
+ # SSN range validation — drop known-invalid SSA ranges
886
+ if label == "ssn":
887
+ valid_matches = [
888
+ m for m in matches
889
+ if _is_valid_ssn(m.group())
890
+ ]
891
+ if not valid_matches:
892
+ continue
893
+ matches = valid_matches
894
+ # Calendar validation for date_of_birth patterns
895
+ if label == "date_of_birth":
896
+ valid_matches = [
897
+ m for m in matches
898
+ if _is_valid_date(m.group())
899
+ ]
900
+ if not valid_matches:
901
+ continue
902
+ matches = valid_matches
765
903
  sensitivity = _SENSITIVITY_MAP.get(label, "medium")
766
904
  hits.append(PIIScanHit(
767
905
  pii_type=label,
@@ -252,4 +252,4 @@ class TestConfigureIntegration:
252
252
 
253
253
  def test_version_is_1_0_0(self):
254
254
  """spanforge.__version__ must match the current release."""
255
- assert spanforge.__version__ == "2.0.0"
255
+ assert spanforge.__version__ == "2.0.2"