From 206873b62a9c77ac5715f9ce7d6bc16d156efa74 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Sat, 22 Aug 2026 18:04:33 +0200 Subject: [PATCH] Release Access v0.1.20 configuration package context --- package.json | 2 +- pyproject.toml | 2 +- src/govoplan_access/__init__.py | 2 +- .../backend/api/v1/admin_schemas.py | 2 + src/govoplan_access/backend/api/v1/routes.py | 2 + src/govoplan_access/backend/manifest.py | 59 ++++++++++++++++++- tests/test_configuration_package_context.py | 37 ++++++++++++ webui/package.json | 2 +- 8 files changed, 103 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4ecc804..cafcadc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/access-webui", - "version": "0.1.19", + "version": "0.1.20", "private": true, "type": "module", "main": "webui/src/index.ts", diff --git a/pyproject.toml b/pyproject.toml index 244e6a2..213b3c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-access" -version = "0.1.19" +version = "0.1.20" description = "GovOPlaN access platform module with identity, auth, RBAC, and scope primitives." readme = "README.md" requires-python = ">=3.12" diff --git a/src/govoplan_access/__init__.py b/src/govoplan_access/__init__.py index 2eaf23e..61c20a8 100644 --- a/src/govoplan_access/__init__.py +++ b/src/govoplan_access/__init__.py @@ -1,3 +1,3 @@ """GovOPlaN access platform module.""" -__version__ = "0.1.19" +__version__ = "0.1.20" diff --git a/src/govoplan_access/backend/api/v1/admin_schemas.py b/src/govoplan_access/backend/api/v1/admin_schemas.py index 77670c4..ec348dd 100644 --- a/src/govoplan_access/backend/api/v1/admin_schemas.py +++ b/src/govoplan_access/backend/api/v1/admin_schemas.py @@ -850,6 +850,7 @@ class ConfigurationPackageApplyResponse(BaseModel): diagnostics: list[dict[str, Any]] = Field(default_factory=list) created_refs: dict[str, str] = Field(default_factory=dict) updated_refs: dict[str, str] = Field(default_factory=dict) + rollback: dict[str, Any] | None = None class ConfigurationPackageExportRequest(BaseModel): @@ -865,6 +866,7 @@ class ConfigurationPackageExportResponse(BaseModel): fragments: list[dict[str, Any]] = Field(default_factory=list) data_requirements: list[dict[str, Any]] = Field(default_factory=list) diagnostics: list[dict[str, Any]] = Field(default_factory=list) + provenance: dict[str, Any] | None = None class SystemSettingsItem(BaseModel): diff --git a/src/govoplan_access/backend/api/v1/routes.py b/src/govoplan_access/backend/api/v1/routes.py index fb770b3..61fa95f 100644 --- a/src/govoplan_access/backend/api/v1/routes.py +++ b/src/govoplan_access/backend/api/v1/routes.py @@ -1036,6 +1036,7 @@ def configuration_package_apply_endpoint( diagnostics=[item.to_dict() for item in result.diagnostics], created_refs=dict(result.created_refs), updated_refs=dict(result.updated_refs), + rollback=result.rollback.to_dict() if result.rollback is not None else None, ) @@ -1056,6 +1057,7 @@ def configuration_package_export_endpoint( fragments=[item.to_dict() for item in result.fragments], data_requirements=[item.to_dict() for item in result.data_requirements], diagnostics=[item.to_dict() for item in result.diagnostics], + provenance=result.provenance.to_dict() if result.provenance is not None else None, ) diff --git a/src/govoplan_access/backend/manifest.py b/src/govoplan_access/backend/manifest.py index 4ce31b9..b4ddc42 100644 --- a/src/govoplan_access/backend/manifest.py +++ b/src/govoplan_access/backend/manifest.py @@ -378,6 +378,63 @@ ACCESS_DOCUMENTATION: tuple[DocumentationTopic, ...] = ( "help_contexts": ["access.resource-explanation.subject"], }, ), + DocumentationTopic( + id="access.workflow.configuration-packages", + title="Preflight and apply configuration packages", + summary="Access exposes the governed admin API that resolves module providers, verifies operator authority, and records package apply evidence.", + body=( + "A configuration-package dry-run resolves installed module providers and reports missing modules, capabilities, deployment data, conflicts, and policy blockers before any provider is invoked for apply. Portable $data references are accepted only when the package declares the corresponding requirement; their supplied values are resolved in memory and are not added to export provenance. Apply remains subject to the configuration-change approval policy. Providers may commit independently, so processing stops at the first apply or health blocker and the response distinguishes blocked-before-apply, no-op, snapshot rollback, and partial-apply recovery states. Export invokes only selected module providers, preserves their secret redaction rules, and adds bounded provenance without serializing supplied credentials." + ), + layer="configured", + documentation_types=("admin",), + audience=("system_admin", "operator", "security_reviewer"), + order=28, + conditions=( + DocumentationCondition( + required_modules=("access", "admin"), + any_scopes=( + "admin:settings:read", + "admin:settings:write", + "system:settings:read", + "system:settings:write", + "system:governance:read", + "system:governance:write", + ), + ), + ), + links=( + DocumentationLink(label="Configuration package preflight", href="/api/v1/admin/configuration-packages/dry-run", kind="api"), + DocumentationLink(label="Configuration package apply", href="/api/v1/admin/configuration-packages/apply", kind="api"), + DocumentationLink(label="Configuration package export", href="/api/v1/admin/configuration-packages/export", kind="api"), + ), + related_modules=("admin", "audit", "ops"), + translations={ + "de": { + "title": "Konfigurationspakete vorprüfen und anwenden", + "summary": "Access stellt die gesteuerte Admin-API bereit, die Modul-Provider auflöst, die Ausführungsberechtigung prüft und Anwendungsnachweise festhält.", + "body": ( + "Die Vorprüfung eines Konfigurationspakets löst die Provider installierter Module auf und meldet fehlende Module, Fähigkeiten, Einsatzdaten, Konflikte und Richtlinienblocker, bevor eine Anwendung beginnt. " + "Portable $data-Verweise sind nur zulässig, wenn das Paket die zugehörige Anforderung deklariert; bereitgestellte Werte werden ausschließlich im Arbeitsspeicher aufgelöst und nicht in den Herkunftsnachweis des Exports aufgenommen. " + "Das Anwenden unterliegt weiterhin der Freigaberichtlinie für Konfigurationsänderungen. Provider können unabhängig festschreiben. Deshalb endet die Verarbeitung beim ersten Anwendungs- oder Gesundheitsblocker; die Antwort unterscheidet Blockierung vor Anwendung, Leerlauf, Snapshot-Rücksetzung und Wiederherstellung nach Teilanwendung. " + "Der Export ruft nur die ausgewählten Modul-Provider auf, wahrt deren Regeln zur Geheimnisschwärzung und ergänzt einen begrenzten Herkunftsnachweis, ohne bereitgestellte Zugangsdaten zu serialisieren." + ), + } + }, + metadata={ + "kind": "workflow", + "help_contexts": ["admin.configuration-packages"], + "limitations": [ + "Package apply does not install missing modules.", + "Cross-provider apply is not an atomic distributed transaction.", + "Generic rollback depends on a retained pre-apply database snapshot.", + ], + "operational_consequences": [ + "A stale or blocked preflight must be rerun before apply.", + "A partial apply requires recovery before the package is retried.", + "Secret values remain outside portable fragments and provenance.", + ], + }, + ), DocumentationTopic( id="access.operator.enroll-first-administrator", title="Enroll the first production administrator", @@ -1254,7 +1311,7 @@ def _people_search(context: ModuleContext) -> object: manifest = ModuleManifest( id="access", name="Access", - version="0.1.19", + version="0.1.20", optional_dependencies=("identity", "organizations", "tenancy", "idm"), provides_interfaces=( ModuleInterfaceProvider(name=CAPABILITY_ACCESS_PEOPLE_SEARCH, version="0.1.0"), diff --git a/tests/test_configuration_package_context.py b/tests/test_configuration_package_context.py index fe69668..ddb1489 100644 --- a/tests/test_configuration_package_context.py +++ b/tests/test_configuration_package_context.py @@ -5,6 +5,10 @@ from types import SimpleNamespace import unittest from unittest.mock import patch +from govoplan_access.backend.api.v1.admin_schemas import ( + ConfigurationPackageApplyResponse, + ConfigurationPackageExportResponse, +) from govoplan_access.backend.api.v1.routes import _configuration_context from govoplan_core.core.infrastructure_capabilities import ( InfrastructureCapabilityReceiptError, @@ -16,6 +20,39 @@ from govoplan_core.core.provider_governance import ( class ConfigurationPackageContextTests(unittest.TestCase): + def test_api_responses_preserve_rollback_and_redacted_export_provenance(self) -> None: + applied = ConfigurationPackageApplyResponse( + rollback={ + "status": "database_restore_required", + "summary": "Snapshot is the generic rollback boundary.", + "recovery_action": "Retain the snapshot.", + } + ) + exported = ConfigurationPackageExportResponse( + provenance={ + "exported_at": "2026-08-22T12:00:00+00:00", + "source_core_version": "0.1.35", + "module_versions": {"forms": "0.1.20"}, + "tenant_id": "tenant-1", + "exporter_id": "user-1", + "selection": { + "scopes": ["tenant"], + "module_ids": ["forms"], + "object_refs": [], + }, + "redacted_secret_keys": ["credential_ref"], + } + ) + + self.assertEqual( + "database_restore_required", + applied.model_dump()["rollback"]["status"], + ) + self.assertEqual( + ["credential_ref"], + exported.model_dump()["provenance"]["redacted_secret_keys"], + ) + def test_context_carries_operator_scopes_and_validated_infrastructure_receipt(self) -> None: receipt = SimpleNamespace(installation_id="deployment-1") principal = SimpleNamespace( diff --git a/webui/package.json b/webui/package.json index 2132160..a3e9e04 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/access-webui", - "version": "0.1.19", + "version": "0.1.20", "private": true, "type": "module", "scripts": {