diff --git a/docs/TENANCY_MODULE_BOUNDARY.md b/docs/TENANCY_MODULE_BOUNDARY.md index 6b12e34..08487dd 100644 --- a/docs/TENANCY_MODULE_BOUNDARY.md +++ b/docs/TENANCY_MODULE_BOUNDARY.md @@ -28,6 +28,13 @@ Tenant lifecycle planning uses registered tenant summary providers and delete veto providers. Modules that own tenant-scoped data must contribute summaries so destructive deletion cannot silently miss their rows. +Delete veto providers are registered through module manifests and receive +`(session, tenant_id, resource_id)`. Providers should return a structured +`DeleteVetoIssue`, a list of issues, or `None`; legacy providers that raise an +exception are treated as blocking module vetoes. Tenancy exposes those issues in +the deletion plan with module attribution and resource details, so operators can +see which module blocks or qualifies the lifecycle action. + ## Lifecycle Events `govoplan-tenancy.backend.lifecycle` is the module-local contract for tenant diff --git a/pyproject.toml b/pyproject.toml index a261145..cb0e8ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-tenancy" -version = "0.1.6" +version = "0.1.7" description = "GovOPlaN tenancy platform module." readme = "README.md" requires-python = ">=3.12" authors = [{ name = "GovOPlaN" }] dependencies = [ - "govoplan-core>=0.1.6", + "govoplan-core>=0.1.7", ] [tool.setuptools.packages.find] diff --git a/src/govoplan_tenancy/backend/api/v1/routes.py b/src/govoplan_tenancy/backend/api/v1/routes.py index 0bbe5a4..ca4bacb 100644 --- a/src/govoplan_tenancy/backend/api/v1/routes.py +++ b/src/govoplan_tenancy/backend/api/v1/routes.py @@ -156,7 +156,16 @@ def _tenant_deletion_plan(session: Session, tenant: Tenant, principal: ApiPrinci )) registry = get_registry() - if registry is not None and hasattr(registry, "delete_veto_providers"): + if registry is not None and hasattr(registry, "collect_delete_veto_issues"): + for issue in registry.collect_delete_veto_issues("tenant", session, tenant.id, tenant.id): + issues.append(TenantLifecycleIssue( + severity=issue.severity, + code=issue.code, + message=issue.message, + module_id=issue.module_id, + details=dict(issue.details), + )) + elif registry is not None and hasattr(registry, "delete_veto_providers"): for provider in registry.delete_veto_providers("tenant"): try: provider(session, tenant.id, tenant.id) diff --git a/src/govoplan_tenancy/backend/api/v1/schemas.py b/src/govoplan_tenancy/backend/api/v1/schemas.py index 809e0a1..4fdca24 100644 --- a/src/govoplan_tenancy/backend/api/v1/schemas.py +++ b/src/govoplan_tenancy/backend/api/v1/schemas.py @@ -79,6 +79,7 @@ class TenantLifecycleIssue(BaseModel): code: str message: str module_id: str | None = None + details: dict[str, Any] = Field(default_factory=dict) class TenantDeletionPlanResponse(BaseModel): diff --git a/src/govoplan_tenancy/backend/manifest.py b/src/govoplan_tenancy/backend/manifest.py index 88054ca..24179fc 100644 --- a/src/govoplan_tenancy/backend/manifest.py +++ b/src/govoplan_tenancy/backend/manifest.py @@ -30,7 +30,7 @@ def _route_factory(context: ModuleContext): manifest = ModuleManifest( id="tenancy", name="Tenancy", - version="0.1.6", + version="0.1.7", required_capabilities=( CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR,