from __future__ import annotations from pathlib import Path from govoplan_core.core.access import ( CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER, ) from govoplan_core.core.information_governance import ( InformationGovernanceDimension, ModuleInformationGovernance, ) from govoplan_core.core.module_guards import ( drop_table_retirement_provider, persistent_table_uninstall_guard, ) from govoplan_core.core.modules import ( CapabilityDocumentation, DocumentationLink, DocumentationTopic, FrontendModule, FrontendRoute, MigrationSpec, ModuleContext, ModuleInterfaceProvider, ModuleManifest, NavItem, PermissionDefinition, RoleTemplate, ) from govoplan_core.core.provider_governance import declared_module_architecture from govoplan_core.core.records import CAPABILITY_RECORDS_FILING from govoplan_core.core.search import SearchSourceProviderRegistration from govoplan_core.core.views import ViewSurface from govoplan_core.db.base import Base from govoplan_records.backend.db import models as record_models from govoplan_records.backend.search_source import create_records_search_source from govoplan_records.backend.service import SqlRecordRegistry MODULE_ID = "records" MODULE_NAME = "Records" MODULE_VERSION = "0.1.18" READ_SCOPE = "records:workspace:read" WRITE_SCOPE = "records:workspace:write" ADMIN_SCOPE = "records:workspace:admin" OPTIONAL_DEPENDENCIES = ( "files", "cases", "forms_runtime", "decisions", "campaigns", "postbox", "reporting", "dms", "docs", "policy", "audit", "transparency", "search", ) def _permission(scope: str, label: str, description: str) -> PermissionDefinition: module_id, resource, action = scope.split(":", 2) return PermissionDefinition( scope=scope, label=label, description=description, category="Records", level="tenant", module_id=module_id, resource=resource, action=action, ) def _router(context: ModuleContext): from govoplan_records.backend.router import create_router return create_router(context.registry) def _records_registry(context: ModuleContext) -> SqlRecordRegistry: return SqlRecordRegistry(context.registry) def _tenant_summary(session, tenant_id: str) -> dict[str, int]: records = ( session.query(record_models.RecordIdentity) .filter(record_models.RecordIdentity.tenant_id == tenant_id) .count() ) open_records = ( session.query(record_models.RecordRevision) .filter( record_models.RecordRevision.tenant_id == tenant_id, record_models.RecordRevision.superseded_at.is_(None), record_models.RecordRevision.state == "open", ) .count() ) return {"records": records, "open_records": open_records} PERMISSIONS = ( _permission( READ_SCOPE, "View records workspace", "Read currently authorized records, contents, chronology, and file-plan context.", ), _permission( WRITE_SCOPE, "Manage records workspace", "Create and revise records, create volumes, and file exact source revisions.", ), _permission( ADMIN_SCOPE, "Administer records workspace", "Version file-plan nodes and record classes and administer Records configuration.", ), ) ROLE_TEMPLATES = ( RoleTemplate( slug="records_manager", name="Records manager", description="Create, revise, structure, and file content into records.", permissions=(READ_SCOPE, WRITE_SCOPE), ), RoleTemplate( slug="records_viewer", name="Records viewer", description="Read records and their governed chronology.", permissions=(READ_SCOPE,), ), RoleTemplate( slug="records_administrator", name="Records administrator", description="Configure file plans and record classes and manage records.", permissions=(READ_SCOPE, WRITE_SCOPE, ADMIN_SCOPE), ), ) DOCUMENTATION = ( DocumentationTopic( id="records.workspace", title="eAkte workspace", summary="Create and browse institutional records, their exact filed items, and chronology.", body=( "Records owns the stable record identity, file-plan classification, immutable revisions, " "volumes, filing decisions, and chronology. Files and other source modules continue to own " "their content. Filing resolves and preserves an exact source revision only after the source " "module confirms current access. The titlebar temporal selection changes valid and recorded " "time while current authorization always remains in force." ), layer="configured", documentation_types=("admin", "user"), audience=("user", "records_manager", "operator", "module_admin", "auditor"), order=100, related_modules=OPTIONAL_DEPENDENCIES, links=( DocumentationLink( label="eAkte architecture", href="govoplan-records/docs/EAKTE_ARCHITECTURE.md", kind="repository", ), ), translations={ "de": { "title": "eAkte-Arbeitsbereich", "summary": "Institutionelle Akten, exakt veraktete Objekte und die Chronologie anlegen und einsehen.", "body": ( "Records verwaltet die stabile Aktenidentität, Aktenplanklassifikation, unveränderliche " "Revisionen, Bände, Veraktungsentscheidungen und die Chronologie. Dateien und andere " "Quellmodule bleiben Eigentümer ihrer Inhalte. Bei der Veraktung wird erst nach aktueller " "Zugriffsprüfung durch das Quellmodul eine exakte Quellrevision festgehalten. Die temporale " "Auswahl in der Titelleiste ändert Gültigkeits- und Erfassungszeit; die aktuelle Berechtigung " "gilt stets weiter." ), } }, metadata={ "help_contexts": [ "records.workspace", "records.file-plan", "records.record-list", "records.record-detail", "records.record-items", "records.chronology", "records.action.create", "records.action.edit", "records.field.record-number", "records.field.state", "records.field.title", "records.field.class", "records.field.classification", "records.field.description", "records.field.change-reason", ], }, ), DocumentationTopic( id="records.filing", title="Exact record filing", summary="File immutable Files or Cases revisions through a provider-neutral capability.", body=( "Every filing requires a record, purpose, filing reason, idempotency key, and exact source " "revision. Records stores source identity, authority mode, digest and content metadata where " "available, represented valid time, source recorded time, filing actor and capacity, and an " "immutable chronology entry. A repeated idempotency key replays only the identical request." ), layer="configured", documentation_types=("admin", "user"), audience=("user", "records_manager", "operator", "module_admin", "auditor"), order=110, related_modules=("files", "cases", "policy", "audit"), links=( DocumentationLink( label="Records domain boundary", href="govoplan-records/docs/RECORDS_DOMAIN_BOUNDARY.md", kind="repository", ), ), translations={ "de": { "title": "Exakte Veraktung", "summary": "Unveränderliche Datei- oder Vorgangsrevisionen über eine anbieterneutrale Schnittstelle verakten.", "body": ( "Jede Veraktung benötigt eine Akte, einen Zweck, eine Veraktungsbegründung, einen " "Idempotenzschlüssel und eine exakte Quellrevision. Records speichert Quellidentität, " "Autoritätsmodus, soweit verfügbar Prüfsumme und Inhaltsmetadaten, Gültigkeits- und " "Erfassungszeit der Quelle, handelnde Person und Funktion sowie einen unveränderlichen " "Chronologieeintrag. Ein wiederholter Idempotenzschlüssel gibt nur dieselbe Anfrage erneut aus." ), } }, metadata={ "help_contexts": [ "records.action.file", "records.field.source-module", "records.field.source-object", "records.field.source-revision", "records.field.purpose", "records.field.filing-reason", ], }, ), DocumentationTopic( id="records.lifecycle-limitations", title="Records lifecycle limitations", summary="Identifies lifecycle controls intentionally deferred beyond the native kernel.", body=( "The current vertical supports planned and open records. Restricted object grants, closure, " "retention calculation, holds, appraisal, disposition, transfer, destruction, and external " "archive effects are separate governed work packages. No destructive effect is implied by " "enabling Records." ), layer="configured", documentation_types=("admin", "user"), audience=("user", "records_manager", "operator", "module_admin", "auditor"), order=120, related_modules=("policy", "approvals", "audit", "dms"), translations={ "de": { "title": "Grenzen des Aktenlebenszyklus", "summary": "Kennzeichnet bewusst nach dem nativen Kern umzusetzende Lebenszyklussteuerungen.", "body": ( "Der aktuelle Stand unterstützt geplante und offene Akten. Objektbezogene Freigaben, " "Abschluss, Aufbewahrungsberechnung, Sperren, Bewertung, Aussonderung, Übergabe, Vernichtung " "und externe Archiveffekte sind getrennte gesteuerte Arbeitspakete. Die Aktivierung von " "Records löst keine vernichtende Wirkung aus." ), } }, metadata={"known_limit": True}, ), ) manifest = ModuleManifest( id=MODULE_ID, name=MODULE_NAME, version=MODULE_VERSION, dependencies=("access",), optional_dependencies=OPTIONAL_DEPENDENCIES, required_capabilities=( CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR, ), permissions=PERMISSIONS, role_templates=ROLE_TEMPLATES, route_factory=_router, nav_items=( NavItem( path="/records", label="Records", icon="archive", required_any=(READ_SCOPE,), order=47, surface_id="records.navigation", ), ), frontend=FrontendModule( module_id=MODULE_ID, package_name="@govoplan/records-webui", routes=( FrontendRoute( path="/records", component="RecordsPage", required_any=(READ_SCOPE,), order=47, surface_id="records.workspace", ), ), nav_items=( NavItem( path="/records", label="Records", icon="archive", required_any=(READ_SCOPE,), order=47, surface_id="records.navigation", ), ), view_surfaces=( ViewSurface( id="records.workspace.file-plan", module_id=MODULE_ID, kind="section", label="File plan", parent_id="records.workspace", order=10, ), ViewSurface( id="records.workspace.list", module_id=MODULE_ID, kind="section", label="Record list", parent_id="records.workspace", order=20, ), ViewSurface( id="records.workspace.detail", module_id=MODULE_ID, kind="section", label="Record detail", parent_id="records.workspace", order=30, ), ViewSurface( id="records.workspace.file", module_id=MODULE_ID, kind="action", label="File source revision", parent_id="records.workspace.detail", order=40, ), ), ), provides_interfaces=( ModuleInterfaceProvider(name="records.registry", version="1.0.0"), ModuleInterfaceProvider(name="records.filing", version="1.0.0"), ), capability_factories={CAPABILITY_RECORDS_FILING: _records_registry}, capability_documentation={ CAPABILITY_RECORDS_FILING: CapabilityDocumentation( label="Record filing", summary="Resolves authorized exact source revisions and files immutable record items.", contract_version="1.0.0", ), }, migration_spec=MigrationSpec( module_id=MODULE_ID, metadata=Base.metadata, script_location=str(Path(__file__).with_name("migrations") / "versions"), retirement_supported=True, retirement_provider=drop_table_retirement_provider( record_models.RecordChronologyEntry, record_models.RecordItem, record_models.RecordVolumeRevision, record_models.RecordRevision, record_models.RecordIdentity, record_models.RecordClassRevision, record_models.RecordFilePlanRevision, label="Records", ), retirement_notes=( "Destructive retirement requires a database snapshot and removes record identities, " "file plans, exact filing references, and chronology. Source content remains provider-owned." ), ), uninstall_guard_providers=( persistent_table_uninstall_guard( record_models.RecordIdentity, record_models.RecordRevision, record_models.RecordItem, record_models.RecordChronologyEntry, record_models.RecordClassRevision, record_models.RecordFilePlanRevision, label="Records", ), ), tenant_summary_providers=(_tenant_summary,), search_sources=( SearchSourceProviderRegistration( id="records.objects", factory=create_records_search_source ), ), documentation=DOCUMENTATION, information_governance=ModuleInformationGovernance( temporal_browsing=InformationGovernanceDimension( adoption="enforced", object_types=( "record", "record_volume", "record_item", "record_class", "file_plan_node", ), evidence=( "src/govoplan_records/backend/service.py", "tests/test_records.py", ), ), purpose_aware_access=InformationGovernanceDimension( adoption="partial", object_types=("record", "record_item"), evidence=("src/govoplan_records/backend/service.py",), limitation=( "Purposes are mandatory and preserved for record operations, but Policy-backed " "object-level purpose constraints and restricted-record grants are not implemented yet." ), ), retention=InformationGovernanceDimension( adoption="contract_only", limitation=( "Record classes preserve retention inputs; closure, holds, calculation, appraisal, " "and disposition are tracked in Records #5." ), ), institutional_context=InformationGovernanceDimension( adoption="enforced", object_types=( "record", "record_item", "record_event", "record_class", "file_plan_node", ), evidence=( "src/govoplan_records/backend/db/models.py", "src/govoplan_records/backend/service.py", "tests/test_records.py", ), ), ), architecture=declared_module_architecture( layer="content_records_evidence", kind="domain", maturity="vertical_slice", documentation_ref="docs/EAKTE_ARCHITECTURE.md", test_ref="tests/test_records.py", known_limits=( "Restricted object grants and lifecycle stages after open are tracked separately.", "Archive transfer and destructive effects are not part of the native kernel.", ), supported_authority_modes=( "native_authoritative", "external_authoritative", "external_mirror", "governed_sync", "governance_overlay", "linked_reference", ), owned_concepts=( "record", "record class", "file plan", "record volume", "record item", "filing decision", "record chronology", ), non_owned_concepts=( "file content", "source object", "case lifecycle", "workflow execution", "generic policy", "audit event", "archive preservation provider", ), reference_packages=( "product.service-to-decision", "product.monthly-data-operations", ), migration_docs=("docs/EAKTE_ARCHITECTURE.md",), recovery_docs=("docs/EAKTE_ARCHITECTURE.md",), security_docs=("docs/EAKTE_ARCHITECTURE.md",), operations_docs=("docs/EAKTE_ARCHITECTURE.md",), ), ) def get_manifest() -> ModuleManifest: return manifest