feat: explain configured institutional architecture
This commit is contained in:
@@ -27,5 +27,7 @@ tools/checks/check-focused.sh
|
||||
- Keep documentation-layer behavior in this module, not core.
|
||||
- Read configuration, module manifests, routes, permissions, and capability metadata through core contracts.
|
||||
- Do not import feature-module internals. Modules should contribute documentation metadata through manifests, capabilities, generated docs, or typed DTOs.
|
||||
- Treat documentation as part of every behavior change. Update the owning module's manifest-driven `DocumentationTopic` contributions for each affected user and administrator workflow, setting, permission, limitation, and operational consequence.
|
||||
- Require every module manifest to provide a static user and administrator documentation baseline, even when configured-state details come from `documentation_providers`. Validate the complete workspace with `/mnt/DATA/git/govoplan/tools/checks/check-manifest-shapes.py`.
|
||||
- Prefer role-aware and configuration-aware documentation over global manuals.
|
||||
- Keep active backlog state in Gitea issues; keep durable context in repository docs and synced wiki pages.
|
||||
|
||||
@@ -55,6 +55,21 @@ Frontend package:
|
||||
|
||||
Platform module manifests, configuration packages, release catalogs, and governance rules are documented in `govoplan-core/docs/`.
|
||||
|
||||
Every module manifest must contribute a static documentation baseline for both
|
||||
the `user` and `admin` projections through `ModuleManifest.documentation`.
|
||||
Runtime providers may add actor- and configuration-specific detail, but they do
|
||||
not replace that baseline. A behavior change is complete only when the owning
|
||||
module updates the affected workflows, settings, permissions, limitations, and
|
||||
operational consequences. Validate workspace coverage with:
|
||||
|
||||
```sh
|
||||
cd /mnt/DATA/git/govoplan
|
||||
./tools/checks/check-manifest-shapes.py
|
||||
```
|
||||
|
||||
Feature content remains in the owning module. Docs indexes and renders the
|
||||
contributions without importing feature implementations.
|
||||
|
||||
Capabilities can provide generic documentation without exposing their runtime
|
||||
provider implementation:
|
||||
|
||||
@@ -79,6 +94,13 @@ freshness, provenance, descriptions, and package requirements remain visible
|
||||
as typed evidence; provider objects, credentials, and secret configuration are
|
||||
never imported into the Docs WebUI.
|
||||
|
||||
Module-owned external-provider runtime state is also projected through the
|
||||
Core contract. Administrative documentation may show sanitized binding-level
|
||||
state; ordinary-user documentation receives only aggregate configured, active,
|
||||
authority, health, freshness, conflict, recovery, and observation fields.
|
||||
URLs, credential references, provider error text, and binding identifiers are
|
||||
excluded from the user projection.
|
||||
|
||||
## Concept documents
|
||||
|
||||
- `docs/DOCUMENTATION_LAYER_CONCEPT.md` defines the configured, available, and evidence documentation model.
|
||||
|
||||
@@ -93,6 +93,25 @@ register durable topics directly in its `ModuleManifest.documentation` tuple.
|
||||
Use this for stable explanations such as the module purpose, common workflows,
|
||||
policy hierarchy, route meaning, and links to public docs or repository docs.
|
||||
|
||||
Every manifest must retain at least one static topic for each of the `user` and
|
||||
`admin` projections. A shared topic may serve both only when its language and
|
||||
disclosure level are appropriate for both audiences. A module that is still a
|
||||
seed should state that limitation plainly rather than documenting an unfinished
|
||||
screen as available. `documentation_providers` enrich this baseline; they do
|
||||
not replace it because a provider may be unavailable before configuration or
|
||||
database access succeeds.
|
||||
|
||||
Documentation is part of a behavior change's completion criteria. The owning
|
||||
module updates affected workflows, fields and settings, permissions, optional
|
||||
integration behavior, failure or limitation explanations, and operator
|
||||
consequences in the same change. The workspace manifest-shape check enforces
|
||||
the static audience baseline:
|
||||
|
||||
```sh
|
||||
cd /mnt/DATA/git/govoplan
|
||||
./tools/checks/check-manifest-shapes.py
|
||||
```
|
||||
|
||||
When the text depends on active configuration, a module registers a provider in
|
||||
`ModuleManifest.documentation_providers`. The provider receives a
|
||||
`DocumentationContext` with the active registry, principal, settings, and a
|
||||
|
||||
@@ -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,7 +439,85 @@ 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]:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -18,12 +18,18 @@ from govoplan_core.core.modules import (
|
||||
FrontendRoute,
|
||||
)
|
||||
from govoplan_core.core.registry import PlatformRegistry
|
||||
from govoplan_core.core.provider_governance import (
|
||||
ModuleArchitectureDeclaration,
|
||||
ModuleMaturityEvidence,
|
||||
)
|
||||
from govoplan_tenancy.backend.manifest import get_manifest as get_tenancy_manifest
|
||||
from govoplan_docs.backend.api.v1.routes import (
|
||||
_classify_documentation,
|
||||
_condition_visibility,
|
||||
_documentation_topic_anchor,
|
||||
_documentation_topic_groups,
|
||||
_documentation_provider_state,
|
||||
_module_payload,
|
||||
_visible_documentation_sources,
|
||||
docs_context,
|
||||
)
|
||||
@@ -46,6 +52,61 @@ class FakePrincipal:
|
||||
|
||||
|
||||
class DocsContextTests(unittest.TestCase):
|
||||
def test_user_provider_state_omits_binding_details(self) -> None:
|
||||
state = {
|
||||
"configured": True,
|
||||
"active": True,
|
||||
"health": "healthy",
|
||||
"freshness": "current",
|
||||
"conflict": "clear",
|
||||
"recovery": "ready",
|
||||
"binding_ref": "calendar:sync-source:secret-context",
|
||||
"bindings": [{"binding_ref": "calendar:sync-source:secret-context"}],
|
||||
}
|
||||
|
||||
user = _documentation_provider_state(state, technical=False)
|
||||
technical = _documentation_provider_state(state, technical=True)
|
||||
|
||||
self.assertNotIn("binding_ref", user)
|
||||
self.assertNotIn("bindings", user)
|
||||
self.assertEqual("healthy", user["health"])
|
||||
self.assertIn("bindings", technical)
|
||||
|
||||
def test_module_architecture_projection_hides_evidence_from_user_docs(self) -> None:
|
||||
manifest = ModuleManifest(
|
||||
id="example",
|
||||
name="Example",
|
||||
version="1.0.0",
|
||||
architecture=ModuleArchitectureDeclaration(
|
||||
layer="governance_accountability",
|
||||
kind="governance",
|
||||
maturity="vertical_slice",
|
||||
evidence=(
|
||||
ModuleMaturityEvidence(
|
||||
kind="test",
|
||||
reference="tests/test_example.py",
|
||||
summary="Private implementation evidence.",
|
||||
),
|
||||
ModuleMaturityEvidence(
|
||||
kind="documentation",
|
||||
reference="docs/EXAMPLE.md",
|
||||
summary="Architecture explanation.",
|
||||
),
|
||||
),
|
||||
known_limits=("Example limit.",),
|
||||
supported_authority_modes=("linked_reference",),
|
||||
owned_concepts=("examples",),
|
||||
),
|
||||
)
|
||||
|
||||
technical = _module_payload(manifest, technical=True)
|
||||
user = _module_payload(manifest, technical=False)
|
||||
|
||||
self.assertEqual("vertical_slice", technical["architecture"]["maturity"])
|
||||
self.assertEqual(2, len(technical["architecture"]["evidence"]))
|
||||
self.assertEqual([], user["architecture"]["evidence"])
|
||||
self.assertEqual(["Example limit."], user["architecture"]["known_limits"])
|
||||
|
||||
def test_topics_are_filtered_by_installed_or_selected_version(self) -> None:
|
||||
registry = PlatformRegistry()
|
||||
registry.register(ModuleManifest(
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
import { apiFetch, type ApiSettings } from "@govoplan/core-webui";
|
||||
|
||||
export type DocsModuleArchitecture = {
|
||||
contract_version: string;
|
||||
layer: string;
|
||||
kind: string;
|
||||
maturity: string;
|
||||
evidence: Array<{ kind: string; reference: string; summary: string }>;
|
||||
known_limits: string[];
|
||||
supported_authority_modes: string[];
|
||||
owned_concepts: string[];
|
||||
non_owned_concepts: string[];
|
||||
reference_packages: string[];
|
||||
target_tested_providers: string[];
|
||||
documentation: Record<string, string[]>;
|
||||
};
|
||||
|
||||
export type DocsExternalProvider = {
|
||||
id: string;
|
||||
module_id: string;
|
||||
label: string;
|
||||
maturity: string;
|
||||
operations: string[];
|
||||
authority_modes: string[];
|
||||
behavior?: Record<string, unknown>;
|
||||
known_outage_behavior?: string | null;
|
||||
runtime_state?: {
|
||||
configured?: boolean;
|
||||
active?: boolean;
|
||||
authority_mode?: string | null;
|
||||
authority_modes?: string[];
|
||||
health?: string;
|
||||
freshness?: string;
|
||||
conflict?: string;
|
||||
recovery?: string;
|
||||
observed_at?: string;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type DocsModule = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -16,6 +53,9 @@ export type DocsModule = {
|
||||
capabilities: string[];
|
||||
documentation_count: number;
|
||||
documentation_provider_count: number;
|
||||
architecture?: DocsModuleArchitecture | null;
|
||||
external_provider_count: number;
|
||||
external_providers: DocsExternalProvider[];
|
||||
};
|
||||
|
||||
export type DocsRoute = {
|
||||
@@ -168,6 +208,8 @@ export type DocsContext = {
|
||||
};
|
||||
summary: {
|
||||
module_count: number;
|
||||
architecture_declared_module_count: number;
|
||||
external_provider_count: number;
|
||||
visible_route_count: number;
|
||||
available_route_count: number;
|
||||
permission_count: number;
|
||||
|
||||
@@ -969,6 +969,8 @@ function ModuleTable({ modules }: { modules: DocsModule[] }) {
|
||||
if (!modules.length) return <p className="muted">i18n:govoplan-docs.no_configured_modules_found.f6f9ce24</p>;
|
||||
const columns: DataGridColumn<DocsModule>[] = [
|
||||
{ id: "module", header: "i18n:govoplan-docs.module.b8ff0289", width: "minmax(220px, 1fr)", minWidth: 190, resizable: true, sortable: true, filterable: true, value: (module) => `${module.name} ${module.id} ${module.version}`, render: (module) => <div><strong>{module.name}</strong><span className="muted block">{module.id} {module.version}</span></div> },
|
||||
{ id: "architecture", header: "i18n:govoplan-docs.architecture.4ca303a3", width: "minmax(210px, .9fr)", minWidth: 190, resizable: true, sortable: true, filterable: true, value: (module) => module.architecture ? `${module.architecture.layer} ${module.architecture.kind} ${module.architecture.maturity}` : "undeclared", render: (module) => module.architecture ? <div><strong>{module.architecture.maturity}</strong><span className="muted block">{module.architecture.layer} · {module.architecture.kind}</span>{module.architecture.known_limits.length ? <span className="muted block">{module.architecture.known_limits.length} i18n:govoplan-docs.known_limits.31871a6b</span> : null}</div> : <span className="muted">i18n:govoplan-docs.staged_declaration_pending.7ce9c1a1</span> },
|
||||
{ id: "authority", header: "i18n:govoplan-docs.source_authority.0a863835", width: "minmax(260px, 1fr)", minWidth: 230, resizable: true, filterable: true, value: (module) => `${module.architecture?.supported_authority_modes.join(" ") ?? ""} ${module.external_provider_count} ${module.external_providers.map((provider) => `${provider.runtime_state?.health ?? "unobserved"} ${provider.runtime_state?.freshness ?? ""} ${provider.runtime_state?.conflict ?? ""} ${provider.runtime_state?.recovery ?? ""}`).join(" ")}`, render: (module) => <div>{module.architecture?.supported_authority_modes.length ? module.architecture.supported_authority_modes.join(", ") : "-"}<span className="muted block">{module.external_provider_count} i18n:govoplan-docs.external_providers.d618fc54</span>{module.external_providers.map((provider) => <span className="muted block" key={provider.id}>{provider.label}: {provider.runtime_state ? `${provider.runtime_state.health ?? "unknown"} · ${provider.runtime_state.freshness ?? "unknown"} · ${provider.runtime_state.conflict ?? "unknown"} · ${provider.runtime_state.recovery ?? "unknown"}` : "unobserved"}</span>)}</div> },
|
||||
{ id: "routes", header: "i18n:govoplan-docs.routes.03730e58", width: 170, sortable: true, value: (module) => module.route_count, render: (module) => <>{module.route_count} i18n:govoplan-docs.route.200e2a66 {module.nav_count} nav</> },
|
||||
{ id: "permissions", header: "i18n:govoplan-docs.permissions.d06d5557", width: 130, sortable: true, value: (module) => module.permission_count },
|
||||
{ id: "frontend", header: "i18n:govoplan-docs.frontend.152d1cf2", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, sortable: true, filterable: true, value: (module) => module.frontend_package || "-", render: (module) => module.frontend_package || "-" },
|
||||
|
||||
@@ -9,6 +9,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-docs.advanced.203a8d6b": "Advanced",
|
||||
"i18n:govoplan-docs.available_documentation.db8bcc42": "Available documentation",
|
||||
"i18n:govoplan-docs.available_routes.c2635868": "Available routes",
|
||||
"i18n:govoplan-docs.architecture.4ca303a3": "Architecture",
|
||||
"i18n:govoplan-docs.basics.5fcebeef": "Basics",
|
||||
"i18n:govoplan-docs.capabilities.ca09c54b": "Capabilities",
|
||||
"i18n:govoplan-docs.common_tasks.9f825c48": "Common tasks",
|
||||
@@ -25,10 +26,12 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-docs.evidence.7ea014de": "Evidence",
|
||||
"i18n:govoplan-docs.field.7558c082": "Field",
|
||||
"i18n:govoplan-docs.frontend.152d1cf2": "Frontend",
|
||||
"i18n:govoplan-docs.external_providers.d618fc54": "external providers",
|
||||
"i18n:govoplan-docs.granted_permissions.0a232e78": "Granted permissions",
|
||||
"i18n:govoplan-docs.guidance_for_the_functions_available_in_this_ins.723b7cad": "Guidance for the functions available in this installation.",
|
||||
"i18n:govoplan-docs.help_center.f3f3a34b": "Help Center",
|
||||
"i18n:govoplan-docs.loading_documentation_context.1c091645": "Loading documentation context...",
|
||||
"i18n:govoplan-docs.known_limits.31871a6b": "known limits",
|
||||
"i18n:govoplan-docs.meaning.584d8aa0": "Meaning",
|
||||
"i18n:govoplan-docs.module.b8ff0289": "Module",
|
||||
"i18n:govoplan-docs.modules.04e9462c": "Modules",
|
||||
@@ -74,6 +77,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-docs.screen.c4878ec4": "Screen",
|
||||
"i18n:govoplan-docs.section.5e498158": "Section",
|
||||
"i18n:govoplan-docs.source.6da13add": "Source",
|
||||
"i18n:govoplan-docs.source_authority.0a863835": "Source authority",
|
||||
"i18n:govoplan-docs.source_details.6dc79c75": "Source details",
|
||||
"i18n:govoplan-docs.inspect_source.bcc1739d": "Inspect source",
|
||||
"i18n:govoplan-docs.inspection.83dc17ca": "Inspection",
|
||||
@@ -81,6 +85,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-docs.visibility.80ab5798": "Visibility",
|
||||
"i18n:govoplan-docs.close.87b84f71": "Close",
|
||||
"i18n:govoplan-docs.status.bae7d5be": "Status",
|
||||
"i18n:govoplan-docs.staged_declaration_pending.7ce9c1a1": "Staged declaration pending",
|
||||
"i18n:govoplan-docs.steps.6041435e": "Steps",
|
||||
"i18n:govoplan-docs.summary.d6b9936d": "Summary",
|
||||
"i18n:govoplan-docs.technical_documentation_for_the_modules_and_conf.267e739e": "Technical documentation for the modules and configuration active in this installation.",
|
||||
@@ -106,6 +111,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-docs.advanced.203a8d6b": "Fortgeschritten",
|
||||
"i18n:govoplan-docs.available_documentation.db8bcc42": "Verfügbare Dokumentation",
|
||||
"i18n:govoplan-docs.available_routes.c2635868": "Verfügbare Routen",
|
||||
"i18n:govoplan-docs.architecture.4ca303a3": "Architektur",
|
||||
"i18n:govoplan-docs.basics.5fcebeef": "Grundlagen",
|
||||
"i18n:govoplan-docs.capabilities.ca09c54b": "Fähigkeiten",
|
||||
"i18n:govoplan-docs.common_tasks.9f825c48": "Häufige Aufgaben",
|
||||
@@ -122,10 +128,12 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-docs.evidence.7ea014de": "Evidence",
|
||||
"i18n:govoplan-docs.field.7558c082": "Feld",
|
||||
"i18n:govoplan-docs.frontend.152d1cf2": "Frontend",
|
||||
"i18n:govoplan-docs.external_providers.d618fc54": "externe Anbieter",
|
||||
"i18n:govoplan-docs.granted_permissions.0a232e78": "Gewährte Berechtigungen",
|
||||
"i18n:govoplan-docs.guidance_for_the_functions_available_in_this_ins.723b7cad": "Anleitung für die in dieser Installation verfügbaren Funktionen.",
|
||||
"i18n:govoplan-docs.help_center.f3f3a34b": "Hilfezentrum",
|
||||
"i18n:govoplan-docs.loading_documentation_context.1c091645": "Dokumentationskontext wird geladen...",
|
||||
"i18n:govoplan-docs.known_limits.31871a6b": "bekannte Einschränkungen",
|
||||
"i18n:govoplan-docs.meaning.584d8aa0": "Bedeutung",
|
||||
"i18n:govoplan-docs.module.b8ff0289": "Modul",
|
||||
"i18n:govoplan-docs.modules.04e9462c": "Module",
|
||||
@@ -171,6 +179,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-docs.screen.c4878ec4": "Ansicht",
|
||||
"i18n:govoplan-docs.section.5e498158": "Bereich",
|
||||
"i18n:govoplan-docs.source.6da13add": "Quelle",
|
||||
"i18n:govoplan-docs.source_authority.0a863835": "Quellenhoheit",
|
||||
"i18n:govoplan-docs.source_details.6dc79c75": "Quelldetails",
|
||||
"i18n:govoplan-docs.inspect_source.bcc1739d": "Quelle anzeigen",
|
||||
"i18n:govoplan-docs.inspection.83dc17ca": "Prüfdaten",
|
||||
@@ -178,6 +187,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-docs.visibility.80ab5798": "Sichtbarkeit",
|
||||
"i18n:govoplan-docs.close.87b84f71": "Schließen",
|
||||
"i18n:govoplan-docs.status.bae7d5be": "Status",
|
||||
"i18n:govoplan-docs.staged_declaration_pending.7ce9c1a1": "Deklaration steht noch aus",
|
||||
"i18n:govoplan-docs.steps.6041435e": "Schritte",
|
||||
"i18n:govoplan-docs.summary.d6b9936d": "Zusammenfassung",
|
||||
"i18n:govoplan-docs.technical_documentation_for_the_modules_and_conf.267e739e": "Technische Dokumentation für die in dieser Installation aktiven Module und Konfiguration.",
|
||||
|
||||
Reference in New Issue
Block a user