From 64d6638c6069836b16299e42ebc6246b6ecf10d9 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 5 Aug 2026 19:52:13 +0200 Subject: [PATCH] Release v0.1.16 --- package.json | 4 ++-- pyproject.toml | 4 ++-- src/govoplan_tenancy/backend/api/v1/routes.py | 3 ++- src/govoplan_tenancy/backend/api/v1/schemas.py | 15 ++++++++++++--- src/govoplan_tenancy/backend/manifest.py | 6 +++--- tests/test_tenant_lifecycle.py | 17 +++++++++++++++-- webui/package.json | 4 ++-- .../src/features/admin/TenantSettingsPanel.tsx | 10 +++++----- webui/src/features/admin/TenantsPanel.tsx | 4 ++-- 9 files changed, 45 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 1183cb4..e7b5ec4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/tenancy-webui", - "version": "0.1.15", + "version": "0.1.16", "private": true, "type": "module", "main": "webui/src/index.ts", @@ -17,7 +17,7 @@ "README.md" ], "peerDependencies": { - "@govoplan/core-webui": "^0.1.15", + "@govoplan/core-webui": "^0.1.16", "lucide-react": "^1.23.0", "react": ">=19.2.7 <20", "react-dom": ">=19.2.7 <20" diff --git a/pyproject.toml b/pyproject.toml index f069b0d..ccc5eb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-tenancy" -version = "0.1.15" +version = "0.1.16" description = "GovOPlaN tenancy platform module." readme = "README.md" requires-python = ">=3.12" authors = [{ name = "GovOPlaN" }] dependencies = [ - "govoplan-core>=0.1.15", + "govoplan-core>=0.1.16", ] [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 a9a40d4..2220567 100644 --- a/src/govoplan_tenancy/backend/api/v1/routes.py +++ b/src/govoplan_tenancy/backend/api/v1/routes.py @@ -23,6 +23,7 @@ from govoplan_core.core.principal_cache import invalidate_auth_principals from govoplan_core.core.runtime import get_registry from govoplan_core.db.session import get_session from govoplan_core.i18n import ( + REFERENCE_LANGUAGE_CODE, i18n_settings, normalize_enabled_language_codes, system_enabled_language_codes, @@ -686,7 +687,7 @@ def _normalized_optional_text(value: str | None) -> str | None: def _normalized_tenant_locale(value: str) -> str: - return value.strip() or "en" + return value.strip() or REFERENCE_LANGUAGE_CODE def _apply_tenant_governance_updates(session: Session, tenant: Tenant, payload: TenantUpdateRequest) -> None: diff --git a/src/govoplan_tenancy/backend/api/v1/schemas.py b/src/govoplan_tenancy/backend/api/v1/schemas.py index 7778134..eab8529 100644 --- a/src/govoplan_tenancy/backend/api/v1/schemas.py +++ b/src/govoplan_tenancy/backend/api/v1/schemas.py @@ -6,6 +6,7 @@ from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field from govoplan_core.api.v1.schemas import DeltaDeletedItem +from govoplan_core.i18n import REFERENCE_LANGUAGE_CODE class TenantAdminItem(BaseModel): @@ -13,7 +14,11 @@ class TenantAdminItem(BaseModel): slug: str = Field(min_length=1, max_length=100) name: str = Field(min_length=1, max_length=255) description: str | None = None - default_locale: str = Field(default="en", min_length=1, max_length=20) + default_locale: str = Field( + default=REFERENCE_LANGUAGE_CODE, + min_length=1, + max_length=20, + ) settings: dict[str, Any] = Field(default_factory=dict) allow_custom_groups: bool | None = None allow_custom_roles: bool | None = None @@ -58,7 +63,7 @@ class TenantCreateRequest(BaseModel): name: str owner_account_id: str | None = None description: str | None = None - default_locale: str = "en" + default_locale: str = REFERENCE_LANGUAGE_CODE settings: dict[str, Any] = Field(default_factory=dict) allow_custom_groups: bool | None = None allow_custom_roles: bool | None = None @@ -124,7 +129,11 @@ class TenantSettingsItem(BaseModel): id: str slug: str name: str - default_locale: str = Field(default="en", min_length=1, max_length=20) + default_locale: str = Field( + default=REFERENCE_LANGUAGE_CODE, + min_length=1, + max_length=20, + ) available_languages: list[dict[str, Any]] = Field(default_factory=list) system_enabled_language_codes: list[str] = Field(default_factory=list) enabled_language_codes: list[str] = Field(default_factory=list) diff --git a/src/govoplan_tenancy/backend/manifest.py b/src/govoplan_tenancy/backend/manifest.py index 9e738a2..82090cd 100644 --- a/src/govoplan_tenancy/backend/manifest.py +++ b/src/govoplan_tenancy/backend/manifest.py @@ -32,7 +32,7 @@ def _route_factory(context: ModuleContext): manifest = ModuleManifest( id="tenancy", name="Tenancy", - version="0.1.15", + version="0.1.16", required_capabilities=( CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR, @@ -60,7 +60,7 @@ manifest = ModuleManifest( id="tenancy.lifecycle-and-settings", title="Administer tenant lifecycle and settings", summary="Tenancy adds explicit tenant creation, activation, context resolution, and tenant-owned settings over Core's shared scope storage.", - body="A tenant is a concrete administrative and data boundary. Tenant lifecycle changes must preserve ownership and recovery guarantees for module-owned records. Tenancy contributes system tenant management and tenant settings to the shared administration workspace; without this module, the Core and Access baseline can operate in single-scope compatibility mode. Core-reserved module entitlement settings are managed only through the Admin module's tenant-module policy endpoints and are preserved when generic tenant settings are replaced.", + body="A tenant is a concrete administrative and data boundary. Tenant lifecycle changes must preserve ownership and recovery guarantees for module-owned records. New tenants default to the German reference language unless the administrator selects another enabled system language; existing tenant and user preferences remain unchanged. Tenancy contributes system tenant management and tenant settings to the shared administration workspace; without this module, the Core and Access baseline can operate in single-scope compatibility mode. Core-reserved module entitlement settings are managed only through the Admin module's tenant-module policy endpoints and are preserved when generic tenant settings are replaced.", documentation_types=("admin",), audience=("system_admin", "tenant_admin", "operator"), related_modules=("access", "admin", "audit"), @@ -83,7 +83,7 @@ manifest = ModuleManifest( id="tenancy.reference.admin-fields", title="Tenant administration fields and consequences", summary="Tenant identity, ownership, locale, governance overrides, and lifecycle state have different mutation and recovery consequences.", - body="A tenant slug is immutable after creation and identifies the administrative boundary. The initial owner receives the protected tenant-owner role. Locale and enabled languages are bounded by system language packages. Governance overrides may narrow a system allowance but cannot loosen a system denial. Suspension keeps tenant-owned data and audit evidence while preventing normal use; an operator must switch away from the active tenant before suspending it.", + body="A tenant slug is immutable after creation and identifies the administrative boundary. The initial owner receives the protected tenant-owner role. German is the reference and new-tenant default; locale and enabled languages are bounded by system language packages and may be changed explicitly. Governance overrides may narrow a system allowance but cannot loosen a system denial. Suspension keeps tenant-owned data and audit evidence while preventing normal use; an operator must switch away from the active tenant before suspending it.", documentation_types=("admin",), audience=("system_admin", "tenant_admin", "operator"), related_modules=("access", "admin", "audit"), diff --git a/tests/test_tenant_lifecycle.py b/tests/test_tenant_lifecycle.py index 4565de6..176cfc0 100644 --- a/tests/test_tenant_lifecycle.py +++ b/tests/test_tenant_lifecycle.py @@ -11,7 +11,11 @@ from govoplan_tenancy.backend.api.v1.routes import ( _apply_tenant_status_update, _require_tenant_update_permissions, ) -from govoplan_tenancy.backend.api.v1.schemas import TenantUpdateRequest +from govoplan_tenancy.backend.api.v1.schemas import ( + TenantCreateRequest, + TenantSettingsItem, + TenantUpdateRequest, +) from govoplan_core.core.module_entitlements import MODULE_ENTITLEMENTS_KEY from govoplan_tenancy.backend.lifecycle import ( TENANT_EVENT_CREATED, @@ -35,6 +39,15 @@ class FakePrincipal: class TenantLifecycleContractTests(unittest.TestCase): + def test_new_tenant_contracts_use_german_reference_default(self) -> None: + tenant = TenantCreateRequest(slug="example", name="Example") + + self.assertEqual("de", tenant.default_locale) + self.assertEqual( + "de", + TenantSettingsItem(id="tenant-1", slug="example", name="Example").default_locale, + ) + def test_lifecycle_event_names_are_stable(self) -> None: self.assertEqual("tenant.created", tenant_lifecycle_event_type("created")) self.assertEqual("tenant.suspended", tenant_lifecycle_event_type("suspended")) @@ -119,7 +132,7 @@ class TenantUpdateHelperTests(unittest.TestCase): self.assertEqual("New tenant", tenant.name) self.assertIsNone(tenant.description) - self.assertEqual("en", tenant.default_locale) + self.assertEqual("de", tenant.default_locale) self.assertEqual({"theme": "contrast"}, tenant.settings) def test_tenant_content_update_preserves_reserved_module_entitlements(self) -> None: diff --git a/webui/package.json b/webui/package.json index 559337c..6efe030 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/tenancy-webui", - "version": "0.1.15", + "version": "0.1.16", "private": true, "type": "module", "main": "src/index.ts", @@ -17,7 +17,7 @@ } }, "peerDependencies": { - "@govoplan/core-webui": "^0.1.15", + "@govoplan/core-webui": "^0.1.16", "lucide-react": "^1.23.0", "react": ">=19.2.7 <20", "react-dom": ">=19.2.7 <20" diff --git a/webui/src/features/admin/TenantSettingsPanel.tsx b/webui/src/features/admin/TenantSettingsPanel.tsx index 50d2e04..8d60ab8 100644 --- a/webui/src/features/admin/TenantSettingsPanel.tsx +++ b/webui/src/features/admin/TenantSettingsPanel.tsx @@ -27,13 +27,13 @@ const fallback: TenantSettingsItem = { id: "", slug: "", name: "", - default_locale: "en", + default_locale: "de", available_languages: [ - { code: "en", label: "English", native_label: "English" }, - { code: "de", label: "German", native_label: "Deutsch" } + { code: "de", label: "German", native_label: "Deutsch" }, + { code: "en", label: "English", native_label: "English" } ], - system_enabled_language_codes: ["en", "de"], - enabled_language_codes: ["en", "de"], + system_enabled_language_codes: ["de", "en"], + enabled_language_codes: ["de", "en"], settings: {} }; diff --git a/webui/src/features/admin/TenantsPanel.tsx b/webui/src/features/admin/TenantsPanel.tsx index 372e88f..8b0b9ae 100644 --- a/webui/src/features/admin/TenantsPanel.tsx +++ b/webui/src/features/admin/TenantsPanel.tsx @@ -49,7 +49,7 @@ const emptyDraft: TenantDraft = { name: "", ownerAccountId: "", description: "", - defaultLocale: "en", + defaultLocale: "de", isActive: true, customGroups: "inherit", customRoles: "inherit", @@ -147,7 +147,7 @@ export default function TenantsPanel({ name: tenant.name, ownerAccountId: "", description: tenant.description || "", - defaultLocale: tenant.default_locale || "en", + defaultLocale: tenant.default_locale || "de", isActive: tenant.is_active, customGroups: fromOverride(tenant.allow_custom_groups), customRoles: fromOverride(tenant.allow_custom_roles),