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.
- {spanforge-2.0.0 → spanforge-2.0.2}/CONFORMANCE.md +4 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/PKG-INFO +4 -4
- {spanforge-2.0.0 → spanforge-2.0.2}/README.md +3 -3
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/redact.md +5 -5
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/changelog.md +40 -1
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/cli.md +2 -1
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/quickstart.md +25 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/pyproject.toml +1 -1
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/__init__.py +1 -1
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/core/compliance_mapping.py +10 -3
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/redact.py +138 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase11_security.py +1 -1
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_redact.py +277 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.gitattributes +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.github/CODEOWNERS +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.github/ISSUE_TEMPLATE/rfc.yml +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.github/pull_request_template.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.github/workflows/ci.yml +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.github/workflows/release.yml +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/.gitignore +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/CNAME +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/CODE_OF_CONDUCT.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/LICENSE +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/MAINTAINERS.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/PRICING.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/README.md.bak +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/RELEASE.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/SECURITY.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/Makefile +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/_static/.gitkeep +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/auto.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/cache.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/compliance.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/consumer.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/debug.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/deprecations.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/event.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/exceptions.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/export.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/governance.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/hooks.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/index.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/integrations.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/lint.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/metrics.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/migrate.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/models.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/normalizer.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/signing.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/store.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/stream.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/testing.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/trace.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/types.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/ulid.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/api/validate.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/conf.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/configuration.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/contributing.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/deployment/air-gapped.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/deployment/kubernetes.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/index.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/installation.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/integrations/crewai.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/make.bat +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/migrations/from-langfuse.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/migrations/from-langsmith.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/migrations/from-openllmetry.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/audit.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/cache.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/consent.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/cost.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/diff.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/eval.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/explanation.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/fence.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/guard.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/hitl.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/index.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/model_registry.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/prompt.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/redact_ns.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/template.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/namespaces/trace.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/rfc/rfc-0001.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/runbook.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/README.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/envelope.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/agent-run.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/agent-step.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/audit.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/cache.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/consent.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/cost.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/diff.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/eval.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/explanation.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/fence.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/guard.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/hitl.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/model-registry.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/prompt.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/redact.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/span.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/payloads/template.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema/types/common.schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/schema-versioning.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/cache.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/compliance.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/custom_exporters.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/debugging.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/events.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/export.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/governance.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/index.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/linting.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/metrics.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/migration.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/redaction.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/signing.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/docs/user_guide/tracing.md +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/agent_workflow.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/budget_alert.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/docker/Dockerfile +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/docker/docker-compose.yml +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/docker/otel-config.yaml +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/langchain_chain.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/multi_agent_rag.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/multi_tenant.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/openai_chat.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/otlp_grafana.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/production_multi_agent.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/secure_pipeline.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/examples/streaming_response.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/sonar-project.properties +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_batch_exporter.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_cli.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_hooks.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_server.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_span.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_store.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_stream.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_trace.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/_tracer.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/actor.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/alerts.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/auto.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/baseline.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/config.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/consent.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/consumer.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/core/__init__.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/cost.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/debug.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/drift.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/egress.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/eval.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/event.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/exceptions.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/explain.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/__init__.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/append_only.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/cloud.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/datadog.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/grafana.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/jsonl.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/otel_bridge.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/otlp.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/otlp_bridge.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/redis_backend.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/export/webhook.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/exporters/__init__.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/exporters/console.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/exporters/jsonl.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/hitl.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/inspect.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/__init__.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/_pricing.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/anthropic.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/bedrock.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/crewai.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/gemini.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/groq.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/langchain.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/llamaindex.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/ollama.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/openai.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/integrations/together.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/metrics.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/metrics_export.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/migrate.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/model_registry.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/models.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/__init__.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/audit.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/cache.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/chain.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/confidence.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/consent.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/cost.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/decision.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/diff.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/drift.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/eval_.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/fence.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/guard.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/hitl.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/latency.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/prompt.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/redact.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/template.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/tool_call.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/namespaces/trace.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/normalizer.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/presidio_backend.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/processor.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/prompt_registry.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/py.typed +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/sampling.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/schemas/v1.0/schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/schemas/v2.0/schema.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/signing.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/stream.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/testing.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/trace.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/types.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/ulid.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/src/spanforge/validate.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/test_agent.jsonl +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/test_events.jsonl +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/__init__.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/__init__.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/chain.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/compliance.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/key_security.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/migration.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/pii.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures/signing.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/fixtures.json +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/run_conformance.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conformance/test_conformance.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/conftest.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_actor.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_alerts.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_auto.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_baseline.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_benchmarks.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_budget_alert.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_cli.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_compliance_mapping.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_consent.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_consumer.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_cost_event_emission.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_cost_tracker.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_costguard_gaps.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_drift.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_event.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_exceptions.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_explain.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_cloud.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_datadog.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_grafana.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_jsonl.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_otel_bridge.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_otlp.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_redis_backend.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_export_webhook.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_hitl.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_inspect.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_integration.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_integrations.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_migrate.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_model_registry.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_models.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_namespaces.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_otlp_bridge.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase10_features.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase1_context_trace.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase2_observability.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase3_debug_sampling.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase4_agent_instrumentation.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase4_metrics_store.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase5_console_exporter.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase5_coverage.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase5_hooks_crewai.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_phase6_openai_integration.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_processor_coverage.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_properties.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_rfc_namespaces.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sampling_coverage.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_config.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_coverage_boost.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_exporters.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_final_coverage.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_gap_filler.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_openai_integration.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_phase7_integrations.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_precision_coverage.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_span.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_stream.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_tracer.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sdk_validation_coverage.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_server.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf11.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf12.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf13.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf14.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf15.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_sf16.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_signing.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_stream.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_trace_decorator.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_trace_pytest_fixtures.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_types.py +0 -0
- {spanforge-2.0.0 → spanforge-2.0.2}/tests/test_ulid.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|