feat: preserve governed configuration context

This commit is contained in:
2026-08-01 17:47:00 +02:00
parent 9776f862f8
commit fe247999e9
5 changed files with 166 additions and 3 deletions
+31 -3
View File
@@ -165,6 +165,10 @@ from govoplan_core.core.configuration_control import (
record_configuration_change_applied,
)
from govoplan_core.core.configuration_safety import configuration_safety_catalog, plan_configuration_change
from govoplan_core.core.provider_governance import (
ExternalProviderStateContext,
collect_external_provider_states,
)
from govoplan_core.core.access import CAPABILITY_ACCESS_EXPLANATION, AccessExplanationService, AccessDecisionProvenance, PrincipalRef
from govoplan_core.core.identity import CAPABILITY_IDENTITY_DIRECTORY, IdentityDirectory
from govoplan_core.core.idm import CAPABILITY_IDM_DIRECTORY, IdmDirectory, OrganizationFunctionAssignmentRef
@@ -916,9 +920,10 @@ def configuration_package_catalog_validation(
@router.post("/configuration-packages/dry-run", response_model=ConfigurationPackageDryRunResponse)
def configuration_package_dry_run_endpoint(
payload: ConfigurationPackageRunRequest,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(require_any_scope("admin:settings:read", "admin:policies:read", "system:settings:read", "system:governance:read")),
):
result = dry_run_configuration_package(payload.package, _configuration_providers(), _configuration_context(principal, tenant_id=payload.tenant_id, supplied_data=payload.supplied_data))
result = dry_run_configuration_package(payload.package, _configuration_providers(), _configuration_context(principal, tenant_id=payload.tenant_id, supplied_data=payload.supplied_data, session=session))
return ConfigurationPackageDryRunResponse(
diagnostics=[item.to_dict() for item in result.diagnostics],
required_data=[item.to_dict() for item in result.required_data],
@@ -944,7 +949,7 @@ def configuration_package_apply_endpoint(
)
except ConfigurationControlError as exc:
raise _configuration_control_http_error(exc) from exc
result = apply_configuration_package(payload.package, _configuration_providers(), _configuration_context(principal, tenant_id=payload.tenant_id, supplied_data=payload.supplied_data))
result = apply_configuration_package(payload.package, _configuration_providers(), _configuration_context(principal, tenant_id=payload.tenant_id, supplied_data=payload.supplied_data, session=session))
record_configuration_change_applied(
session,
key="configuration_packages.apply",
@@ -975,6 +980,7 @@ def configuration_package_apply_endpoint(
@router.post("/configuration-packages/export", response_model=ConfigurationPackageExportResponse)
def configuration_package_export_endpoint(
payload: ConfigurationPackageExportRequest,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(require_any_scope("admin:settings:read", "admin:policies:read", "system:settings:read", "system:governance:read")),
):
selection = ConfigurationExportSelection(
@@ -983,7 +989,7 @@ def configuration_package_export_endpoint(
module_ids=tuple(payload.module_ids),
object_refs=tuple(payload.object_refs),
)
result = export_configuration_package(_configuration_providers(), selection, _configuration_context(principal, tenant_id=payload.tenant_id))
result = export_configuration_package(_configuration_providers(), selection, _configuration_context(principal, tenant_id=payload.tenant_id, session=session))
return ConfigurationPackageExportResponse(
fragments=[item.to_dict() for item in result.fragments],
data_requirements=[item.to_dict() for item in result.data_requirements],
@@ -1010,21 +1016,43 @@ def _configuration_context(
*,
tenant_id: str | None = None,
supplied_data: dict[str, Any] | None = None,
session: Session | None = None,
) -> ConfigurationPreflightContext:
registry = get_registry()
installed_modules: dict[str, str] = {"access": "0.1.6"}
capabilities = {CONFIGURATION_PROVIDER_CAPABILITY, ACCESS_CONFIGURATION_CAPABILITY}
external_provider_declarations: dict[str, dict[str, object]] = {}
external_provider_states: dict[str, dict[str, object]] = {}
if registry is not None and hasattr(registry, "manifests"):
manifests = registry.manifests()
installed_modules = {manifest.id: manifest.version for manifest in manifests}
if hasattr(registry, "capability_names"):
capabilities.update(registry.capability_names())
if hasattr(registry, "external_provider_declarations"):
external_provider_declarations = {
declaration.id: declaration.to_dict()
for declaration in registry.external_provider_declarations()
}
if session is not None and hasattr(
registry,
"external_provider_state_providers",
):
external_provider_states = collect_external_provider_states(
registry.external_provider_state_providers(),
ExternalProviderStateContext(
session=session,
tenant_id=tenant_id or principal.tenant_id,
principal=principal,
),
)
return ConfigurationPreflightContext(
tenant_id=tenant_id or principal.tenant_id,
operator_user_id=principal.user.id,
supplied_data=supplied_data or {},
installed_modules=installed_modules,
capabilities=frozenset(capabilities),
external_provider_declarations=external_provider_declarations,
external_provider_states=external_provider_states,
)
+13
View File
@@ -27,6 +27,7 @@ from govoplan_core.core.identity import CAPABILITY_IDENTITY_DIRECTORY, IdentityD
from govoplan_core.core.idm import CAPABILITY_IDM_DIRECTORY, IdmDirectory
from govoplan_core.core.organizations import CAPABILITY_ORGANIZATION_DIRECTORY, OrganizationDirectory
from govoplan_core.core.module_guards import persistent_table_uninstall_guard
from govoplan_core.core.provider_governance import declared_module_architecture
from govoplan_core.core.modules import (
DocumentationCondition,
DocumentationLink,
@@ -741,6 +742,18 @@ manifest = ModuleManifest(
ACCESS_CONFIGURATION_CAPABILITY: _configuration_provider,
},
documentation=ACCESS_DOCUMENTATION,
architecture=declared_module_architecture(
layer="institutional_foundation",
kind="foundation",
maturity="vertical_slice",
documentation_ref="docs/ACCESS_MODULE_BOUNDARY.md",
test_ref="tests/test_login_security.py",
known_limits=("Recovery and upgrade evidence is not yet complete enough for supported maturity.",),
owned_concepts=("account authentication", "application role", "permission evaluation", "service account"),
non_owned_concepts=("person identity", "organization structure", "function incumbency", "policy definition"),
security_docs=("docs/ACCESS_MODULE_BOUNDARY.md",),
operations_docs=("README.md",),
),
)