feat: explain configured institutional architecture
This commit is contained in:
@@ -20,6 +20,10 @@ from govoplan_core.core.modules import (
|
||||
user_workflow_scope_condition_issues,
|
||||
)
|
||||
from govoplan_core.core.registry import PlatformRegistry
|
||||
from govoplan_core.core.provider_governance import (
|
||||
ExternalProviderStateContext,
|
||||
collect_external_provider_states,
|
||||
)
|
||||
from govoplan_core.core.versioning import (
|
||||
format_version_range,
|
||||
version_satisfies_range,
|
||||
@@ -57,6 +61,7 @@ def docs_context(
|
||||
detail="Administrative documentation requires documentation-administrator authority",
|
||||
)
|
||||
registry = _registry(request)
|
||||
external_provider_states = _external_provider_states(registry, principal)
|
||||
target_version = version if isinstance(version, str) else None
|
||||
resolved_locale = _preferred_locale(request, locale)
|
||||
route_items = _route_items(registry.manifests(), principal)
|
||||
@@ -84,12 +89,14 @@ def docs_context(
|
||||
catalog = _admin_documentation_catalog(
|
||||
registry,
|
||||
principal,
|
||||
external_provider_states=external_provider_states,
|
||||
route_items=route_items,
|
||||
visible_route_items=visible_route_items,
|
||||
)
|
||||
else:
|
||||
catalog = _user_documentation_catalog(
|
||||
registry,
|
||||
external_provider_states=external_provider_states,
|
||||
visible_route_items=visible_route_items,
|
||||
documentation_layers=documentation_layers,
|
||||
)
|
||||
@@ -193,12 +200,17 @@ def _admin_documentation_catalog(
|
||||
registry: PlatformRegistry,
|
||||
principal: ApiPrincipal,
|
||||
*,
|
||||
external_provider_states: Mapping[str, Mapping[str, object]],
|
||||
route_items: list[dict[str, Any]],
|
||||
visible_route_items: list[dict[str, Any]],
|
||||
) -> dict[str, list[dict[str, Any]]]:
|
||||
return {
|
||||
"modules": [
|
||||
_module_payload(manifest, technical=True)
|
||||
_module_payload(
|
||||
manifest,
|
||||
technical=True,
|
||||
external_provider_states=external_provider_states,
|
||||
)
|
||||
for manifest in registry.manifests()
|
||||
],
|
||||
"permissions": [
|
||||
@@ -214,6 +226,7 @@ def _admin_documentation_catalog(
|
||||
def _user_documentation_catalog(
|
||||
registry: PlatformRegistry,
|
||||
*,
|
||||
external_provider_states: Mapping[str, Mapping[str, object]],
|
||||
visible_route_items: list[dict[str, Any]],
|
||||
documentation_layers: Mapping[str, list[dict[str, Any]]],
|
||||
) -> dict[str, list[dict[str, Any]]]:
|
||||
@@ -226,7 +239,11 @@ def _user_documentation_catalog(
|
||||
}
|
||||
return {
|
||||
"modules": [
|
||||
_module_payload(manifest, technical=False)
|
||||
_module_payload(
|
||||
manifest,
|
||||
technical=False,
|
||||
external_provider_states=external_provider_states,
|
||||
)
|
||||
for manifest in registry.manifests()
|
||||
if manifest.id in visible_module_ids
|
||||
],
|
||||
@@ -314,6 +331,13 @@ def _documentation_summary(
|
||||
permissions = catalog["permissions"]
|
||||
return {
|
||||
"module_count": len(catalog["modules"]),
|
||||
"architecture_declared_module_count": sum(
|
||||
1 for item in catalog["modules"] if item.get("architecture")
|
||||
),
|
||||
"external_provider_count": sum(
|
||||
int(item.get("external_provider_count") or 0)
|
||||
for item in catalog["modules"]
|
||||
),
|
||||
"visible_route_count": len(catalog["visible_routes"]),
|
||||
"available_route_count": len(catalog["available_routes"]),
|
||||
"permission_count": len(permissions),
|
||||
@@ -371,9 +395,34 @@ def _registry(request: Request) -> PlatformRegistry:
|
||||
return registry
|
||||
|
||||
|
||||
def _module_payload(manifest: ModuleManifest, *, technical: bool) -> dict[str, Any]:
|
||||
def _module_payload(
|
||||
manifest: ModuleManifest,
|
||||
*,
|
||||
technical: bool,
|
||||
external_provider_states: Mapping[str, Mapping[str, object]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
frontend = manifest.frontend
|
||||
migration = manifest.migration_spec
|
||||
architecture = manifest.architecture.to_dict() if manifest.architecture else None
|
||||
if architecture is not None and not technical:
|
||||
architecture = {
|
||||
"contract_version": architecture["contract_version"],
|
||||
"layer": architecture["layer"],
|
||||
"kind": architecture["kind"],
|
||||
"maturity": architecture["maturity"],
|
||||
"known_limits": architecture["known_limits"],
|
||||
"supported_authority_modes": architecture[
|
||||
"supported_authority_modes"
|
||||
],
|
||||
"owned_concepts": architecture["owned_concepts"],
|
||||
"non_owned_concepts": architecture["non_owned_concepts"],
|
||||
"reference_packages": architecture["reference_packages"],
|
||||
"target_tested_providers": architecture[
|
||||
"target_tested_providers"
|
||||
],
|
||||
"evidence": [],
|
||||
"documentation": {},
|
||||
}
|
||||
return {
|
||||
"id": manifest.id,
|
||||
"name": manifest.name,
|
||||
@@ -390,9 +439,87 @@ def _module_payload(manifest: ModuleManifest, *, technical: bool) -> dict[str, A
|
||||
"capabilities": sorted(manifest.capability_factories) if technical else [],
|
||||
"documentation_count": len(manifest.documentation) if technical else 0,
|
||||
"documentation_provider_count": len(manifest.documentation_providers) if technical else 0,
|
||||
"architecture": architecture,
|
||||
"external_provider_count": len(manifest.external_providers),
|
||||
"external_providers": [
|
||||
{
|
||||
**(
|
||||
declaration.to_dict()
|
||||
if technical
|
||||
else {
|
||||
"id": declaration.id,
|
||||
"module_id": declaration.module_id,
|
||||
"label": declaration.label,
|
||||
"maturity": declaration.maturity,
|
||||
"operations": list(declaration.operations),
|
||||
"authority_modes": list(declaration.authority_modes),
|
||||
"known_outage_behavior": declaration.behavior.outage,
|
||||
}
|
||||
),
|
||||
"runtime_state": _documentation_provider_state(
|
||||
(external_provider_states or {}).get(declaration.id),
|
||||
technical=technical,
|
||||
),
|
||||
}
|
||||
for declaration in manifest.external_providers
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _documentation_provider_state(
|
||||
state: Mapping[str, object] | None,
|
||||
*,
|
||||
technical: bool,
|
||||
) -> dict[str, object] | None:
|
||||
if state is None:
|
||||
return None
|
||||
if technical:
|
||||
return dict(state)
|
||||
return {
|
||||
key: state.get(key)
|
||||
for key in (
|
||||
"configured",
|
||||
"active",
|
||||
"authority_mode",
|
||||
"authority_modes",
|
||||
"health",
|
||||
"freshness",
|
||||
"conflict",
|
||||
"recovery",
|
||||
"observed_at",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def _external_provider_states(
|
||||
registry: PlatformRegistry,
|
||||
principal: ApiPrincipal,
|
||||
) -> dict[str, dict[str, object]]:
|
||||
registrations = registry.external_provider_state_providers()
|
||||
if not registrations:
|
||||
return {}
|
||||
tenant_id = str(principal.tenant_id or "").strip() or None
|
||||
try:
|
||||
with get_database().session() as session:
|
||||
return collect_external_provider_states(
|
||||
registrations,
|
||||
ExternalProviderStateContext(
|
||||
session=session,
|
||||
tenant_id=tenant_id,
|
||||
principal=principal,
|
||||
),
|
||||
)
|
||||
except Exception: # noqa: BLE001 - return sanitized per-provider diagnostics.
|
||||
return collect_external_provider_states(
|
||||
registrations,
|
||||
ExternalProviderStateContext(
|
||||
session=None,
|
||||
tenant_id=tenant_id,
|
||||
principal=principal,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _user_route_payload(item: Mapping[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"module_id": item["module_id"],
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
|
||||
from govoplan_core.core.access import (
|
||||
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
||||
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
||||
)
|
||||
from govoplan_core.core.modules import (
|
||||
DocumentationCondition,
|
||||
DocumentationLink,
|
||||
@@ -14,12 +17,48 @@ from govoplan_core.core.modules import (
|
||||
PermissionDefinition,
|
||||
RoleTemplate,
|
||||
)
|
||||
from govoplan_core.core.provider_governance import (
|
||||
ModuleArchitectureDeclaration,
|
||||
ModuleArchitectureDocumentation,
|
||||
ModuleMaturityEvidence,
|
||||
)
|
||||
|
||||
DOCS_READ_SCOPE = "docs:documentation:read"
|
||||
DOCS_ADMIN_READ_SCOPE = "docs:documentation:admin"
|
||||
DOCS_ADMIN_READ_SCOPES = (DOCS_ADMIN_READ_SCOPE, "system:settings:read", "admin:settings:read")
|
||||
DOCS_ADMIN_READ_SCOPES = (
|
||||
DOCS_ADMIN_READ_SCOPE,
|
||||
"system:settings:read",
|
||||
"admin:settings:read",
|
||||
)
|
||||
DOCS_READ_SCOPES = (DOCS_READ_SCOPE, *DOCS_ADMIN_READ_SCOPES)
|
||||
|
||||
ARCHITECTURE = ModuleArchitectureDeclaration(
|
||||
layer="governance_accountability",
|
||||
kind="presentation",
|
||||
maturity="vertical_slice",
|
||||
evidence=(
|
||||
ModuleMaturityEvidence(
|
||||
kind="test",
|
||||
reference="tests/test_docs_context.py",
|
||||
summary="Tests audience-safe configured documentation and architecture projections.",
|
||||
),
|
||||
ModuleMaturityEvidence(
|
||||
kind="documentation",
|
||||
reference="docs/DOCUMENTATION_LAYER_CONCEPT.md",
|
||||
summary="Defines the manifest-driven documentation boundary.",
|
||||
),
|
||||
),
|
||||
known_limits=(
|
||||
"Architecture declarations are in staged adoption, so undeclared modules remain visible as pending.",
|
||||
),
|
||||
owned_concepts=("configured documentation projection", "documentation audience filtering"),
|
||||
non_owned_concepts=("module feature behavior", "module evidence generation"),
|
||||
documentation=ModuleArchitectureDocumentation(
|
||||
security=("docs/DOCUMENTATION_LAYER_CONCEPT.md",),
|
||||
operations=("docs/DOCUMENTATION_LAYER_CONCEPT.md",),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
|
||||
module_id, resource, action = scope.split(":", 2)
|
||||
@@ -46,7 +85,10 @@ manifest = ModuleManifest(
|
||||
id="docs",
|
||||
name="Docs",
|
||||
version="0.1.10",
|
||||
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
||||
required_capabilities=(
|
||||
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
||||
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
||||
),
|
||||
optional_dependencies=("policy", "audit", "ops", "workflow_engine", "search"),
|
||||
permissions=(
|
||||
_permission(
|
||||
@@ -76,12 +118,35 @@ manifest = ModuleManifest(
|
||||
),
|
||||
),
|
||||
route_factory=_route_factory,
|
||||
nav_items=(NavItem(path="/docs", label="Docs", icon="reports", required_any=DOCS_READ_SCOPES, order=880),),
|
||||
nav_items=(
|
||||
NavItem(
|
||||
path="/docs",
|
||||
label="Docs",
|
||||
icon="reports",
|
||||
required_any=DOCS_READ_SCOPES,
|
||||
order=880,
|
||||
),
|
||||
),
|
||||
frontend=FrontendModule(
|
||||
module_id="docs",
|
||||
package_name="@govoplan/docs-webui",
|
||||
routes=(FrontendRoute(path="/docs", component="DocsPage", required_any=DOCS_READ_SCOPES, order=880),),
|
||||
nav_items=(NavItem(path="/docs", label="Docs", icon="reports", required_any=DOCS_READ_SCOPES, order=880),),
|
||||
routes=(
|
||||
FrontendRoute(
|
||||
path="/docs",
|
||||
component="DocsPage",
|
||||
required_any=DOCS_READ_SCOPES,
|
||||
order=880,
|
||||
),
|
||||
),
|
||||
nav_items=(
|
||||
NavItem(
|
||||
path="/docs",
|
||||
label="Docs",
|
||||
icon="reports",
|
||||
required_any=DOCS_READ_SCOPES,
|
||||
order=880,
|
||||
),
|
||||
),
|
||||
),
|
||||
documentation=(
|
||||
DocumentationTopic(
|
||||
@@ -120,6 +185,49 @@ manifest = ModuleManifest(
|
||||
),
|
||||
metadata={"kind": "system"},
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="docs.reference.institutional-governance-architecture",
|
||||
title="Institutional governance architecture",
|
||||
summary="GovOPlaN models institutional responsibility, governed work, formal outcomes, evidence, and external-system authority without turning every concept into Core or one monolithic application.",
|
||||
body=(
|
||||
"Organizations, Identity, IDM, Access, and Policy answer different parts of who may act. "
|
||||
"Mandate, service, procedure-party, and formal-decision semantics are being introduced as shared contracts and become modules only after independent lifecycle and reuse are proven. "
|
||||
"External integrations separately declare technical maturity and whether GovOPlaN is authoritative, mirrors an external source, synchronizes under governance, adds an overlay, or retains only a link."
|
||||
),
|
||||
layer="always",
|
||||
documentation_types=("admin",),
|
||||
audience=("tenant_admin", "operator", "module_admin", "product_owner"),
|
||||
order=15,
|
||||
translations={
|
||||
"de": {
|
||||
"title": "Architektur der institutionellen Steuerung",
|
||||
"summary": "GovOPlaN modelliert institutionelle Verantwortung, gesteuerte Arbeit, formale Ergebnisse, Nachweise und die Datenhoheit externer Systeme, ohne alle Begriffe in den Kern oder eine monolithische Anwendung zu ziehen.",
|
||||
"body": "Organisationen, Identitaeten, IDM, Zugriff und Richtlinien beantworten unterschiedliche Teile der Frage, wer handeln darf. Mandate, Leistungen, Verfahrensbeteiligte und formale Entscheidungen beginnen als gemeinsame Vertraege und werden erst bei nachgewiesenem eigenstaendigem Lebenszyklus zu Modulen. Integrationen erklaeren technische Reife und Datenhoheit getrennt.",
|
||||
},
|
||||
},
|
||||
links=(
|
||||
DocumentationLink(
|
||||
label="Institutional governance target architecture",
|
||||
href="govoplan/docs/INSTITUTIONAL_GOVERNANCE_TARGET_ARCHITECTURE.md",
|
||||
kind="repository",
|
||||
),
|
||||
DocumentationLink(
|
||||
label="Core module architecture",
|
||||
href="govoplan-core/docs/MODULE_ARCHITECTURE.md",
|
||||
kind="repository",
|
||||
),
|
||||
),
|
||||
metadata={
|
||||
"kind": "reference",
|
||||
"architecture_topics": [
|
||||
"institutional context",
|
||||
"module ownership",
|
||||
"source authority",
|
||||
"integration maturity",
|
||||
"product packages",
|
||||
],
|
||||
},
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="docs.pattern.field-help",
|
||||
title="Field help marker",
|
||||
@@ -177,19 +285,32 @@ manifest = ModuleManifest(
|
||||
),
|
||||
),
|
||||
links=(
|
||||
DocumentationLink(label="Organizations", href="/organizations", kind="runtime"),
|
||||
DocumentationLink(
|
||||
label="Organizations", href="/organizations", kind="runtime"
|
||||
),
|
||||
DocumentationLink(label="IDM assignments", href="/idm", kind="runtime"),
|
||||
DocumentationLink(label="Access administration", href="/admin", kind="runtime"),
|
||||
DocumentationLink(
|
||||
label="Access administration", href="/admin", kind="runtime"
|
||||
),
|
||||
),
|
||||
metadata={
|
||||
"kind": "reference",
|
||||
"admin_explanation": "Function-to-role effects are owned by Access. IDM assignment changes can be governed independently from organization model changes.",
|
||||
"user_explanation": "A person can hold a function because IDM links their identity to the organization function. Access decides which application permissions that function gives.",
|
||||
"module_boundaries": [
|
||||
{"module": "organizations", "owns": "unit types, structures, relations, units, and function definitions"},
|
||||
{
|
||||
"module": "organizations",
|
||||
"owns": "unit types, structures, relations, units, and function definitions",
|
||||
},
|
||||
{"module": "identity", "owns": "identities and account links"},
|
||||
{"module": "idm", "owns": "identity-to-function assignments, delegation, acting-for links, and synchronization mapping"},
|
||||
{"module": "access", "owns": "roles, permissions, and accepted function-to-role mappings"},
|
||||
{
|
||||
"module": "idm",
|
||||
"owns": "identity-to-function assignments, delegation, acting-for links, and synchronization mapping",
|
||||
},
|
||||
{
|
||||
"module": "access",
|
||||
"owns": "roles, permissions, and accepted function-to-role mappings",
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
@@ -210,6 +331,7 @@ manifest = ModuleManifest(
|
||||
},
|
||||
),
|
||||
),
|
||||
architecture=ARCHITECTURE,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from govoplan_core.core.modules import (
|
||||
DocumentationCondition,
|
||||
DocumentationLink,
|
||||
DocumentationSourceDefinition,
|
||||
DocumentationType,
|
||||
ModuleManifest,
|
||||
|
||||
Reference in New Issue
Block a user