508 lines
18 KiB
Python
508 lines
18 KiB
Python
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
|