feat(records): govern lifecycle recovery and transfer evidence
This commit is contained in:
@@ -29,13 +29,20 @@ from govoplan_core.core.modules import (
|
||||
RoleTemplate,
|
||||
)
|
||||
from govoplan_core.core.provider_governance import declared_module_architecture
|
||||
from govoplan_core.core.records import CAPABILITY_RECORDS_FILING
|
||||
from govoplan_core.core.records import (
|
||||
CAPABILITY_RECORDS_FILING,
|
||||
record_archive_capability,
|
||||
)
|
||||
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
|
||||
from govoplan_records.backend.archive import (
|
||||
SIMULATION_PROVIDER_ID,
|
||||
SimulatedRecordArchiveProvider,
|
||||
)
|
||||
|
||||
|
||||
MODULE_ID = "records"
|
||||
@@ -55,6 +62,7 @@ OPTIONAL_DEPENDENCIES = (
|
||||
"dms",
|
||||
"docs",
|
||||
"policy",
|
||||
"approvals",
|
||||
"audit",
|
||||
"transparency",
|
||||
"search",
|
||||
@@ -85,6 +93,13 @@ def _records_registry(context: ModuleContext) -> SqlRecordRegistry:
|
||||
return SqlRecordRegistry(context.registry)
|
||||
|
||||
|
||||
def _simulated_archive_provider(
|
||||
context: ModuleContext,
|
||||
) -> SimulatedRecordArchiveProvider:
|
||||
del context
|
||||
return SimulatedRecordArchiveProvider()
|
||||
|
||||
|
||||
def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
|
||||
records = (
|
||||
session.query(record_models.RecordIdentity)
|
||||
@@ -100,7 +115,32 @@ def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
|
||||
)
|
||||
.count()
|
||||
)
|
||||
return {"records": records, "open_records": open_records}
|
||||
active_holds = (
|
||||
session.query(record_models.RecordHoldRevision)
|
||||
.filter(
|
||||
record_models.RecordHoldRevision.tenant_id == tenant_id,
|
||||
record_models.RecordHoldRevision.superseded_at.is_(None),
|
||||
record_models.RecordHoldRevision.status == "active",
|
||||
)
|
||||
.count()
|
||||
)
|
||||
pending_dispositions = (
|
||||
session.query(record_models.RecordDispositionRevision)
|
||||
.filter(
|
||||
record_models.RecordDispositionRevision.tenant_id == tenant_id,
|
||||
record_models.RecordDispositionRevision.superseded_at.is_(None),
|
||||
record_models.RecordDispositionRevision.status.in_(
|
||||
("review_pending", "review_unavailable")
|
||||
),
|
||||
)
|
||||
.count()
|
||||
)
|
||||
return {
|
||||
"records": records,
|
||||
"open_records": open_records,
|
||||
"active_holds": active_holds,
|
||||
"pending_dispositions": pending_dispositions,
|
||||
}
|
||||
|
||||
|
||||
PERMISSIONS = (
|
||||
@@ -247,14 +287,16 @@ DOCUMENTATION = (
|
||||
},
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="records.lifecycle-limitations",
|
||||
title="Records lifecycle limitations",
|
||||
summary="Identifies lifecycle controls intentionally deferred beyond the native kernel.",
|
||||
id="records.lifecycle",
|
||||
title="Governed records lifecycle",
|
||||
summary="Close, retain, hold, appraise, approve, and package records with durable evidence.",
|
||||
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."
|
||||
"Closing a record applies the versioned record-class retention rule. Reopening preserves the "
|
||||
"previous schedule unless an administrator deliberately restarts it. Effective-dated holds block "
|
||||
"disposition and transfer. Appraisal selects retain, transfer, destroy, or reclassify; a disposition "
|
||||
"binds the exact evidence digest and requires independent approval through Approvals. Approval only "
|
||||
"changes lifecycle state. Destruction remains pending and transfer simulation never claims custody. "
|
||||
"Every API mutation is fenced and recorded in the Core recovery ledger."
|
||||
),
|
||||
layer="configured",
|
||||
documentation_types=("admin", "user"),
|
||||
@@ -263,17 +305,44 @@ DOCUMENTATION = (
|
||||
related_modules=("policy", "approvals", "audit", "dms"),
|
||||
translations={
|
||||
"de": {
|
||||
"title": "Grenzen des Aktenlebenszyklus",
|
||||
"summary": "Kennzeichnet bewusst nach dem nativen Kern umzusetzende Lebenszyklussteuerungen.",
|
||||
"title": "Gesteuerter Aktenlebenszyklus",
|
||||
"summary": "Akten mit dauerhaftem Nachweis abschließen, aufbewahren, sperren, bewerten, freigeben und paketieren.",
|
||||
"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."
|
||||
"Beim Abschluss einer Akte wird die versionierte Aufbewahrungsregel der Aktenklasse angewendet. "
|
||||
"Eine Wiedereröffnung bewahrt den bisherigen Zeitplan, sofern ein Administrator ihn nicht "
|
||||
"bewusst neu startet. Gültigkeitsbezogene Sperren blockieren Aussonderung und Übergabe. Die "
|
||||
"Bewertung wählt Aufbewahrung, Übergabe, Vernichtung oder Neuklassifikation; die Aussonderung "
|
||||
"bindet den exakten Nachweis und benötigt eine unabhängige Freigabe durch Approvals. Eine "
|
||||
"Freigabe ändert nur den Lebenszyklusstatus. Vernichtung bleibt vorgemerkt, und eine Simulation "
|
||||
"behauptet keine Archivverwahrung. Jede API-Änderung wird im Recovery-Ledger abgesichert."
|
||||
),
|
||||
}
|
||||
},
|
||||
metadata={"known_limit": True},
|
||||
metadata={
|
||||
"help_contexts": [
|
||||
"records.lifecycle",
|
||||
"records.lifecycle.volume",
|
||||
"records.lifecycle.close",
|
||||
"records.lifecycle.reopen",
|
||||
"records.lifecycle.appraise",
|
||||
"records.lifecycle.hold",
|
||||
"records.lifecycle.release-hold",
|
||||
"records.lifecycle.disposition",
|
||||
"records.lifecycle.finalize",
|
||||
"records.lifecycle.prepare-transfer",
|
||||
"records.lifecycle.dispatch-transfer",
|
||||
"records.lifecycle.recovery",
|
||||
"records.catalog.admin",
|
||||
"records.field.volume",
|
||||
"records.field.volume-label",
|
||||
"records.field.retention-trigger",
|
||||
"records.field.disposition",
|
||||
"records.field.hold-authority",
|
||||
"records.field.lifecycle-reason",
|
||||
"records.field.archive-provider",
|
||||
"records.field.archive-profile",
|
||||
],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -361,14 +430,25 @@ manifest = ModuleManifest(
|
||||
provides_interfaces=(
|
||||
ModuleInterfaceProvider(name="records.registry", version="1.0.0"),
|
||||
ModuleInterfaceProvider(name="records.filing", version="1.0.0"),
|
||||
ModuleInterfaceProvider(name="records.archive", version="1.0.0"),
|
||||
),
|
||||
capability_factories={CAPABILITY_RECORDS_FILING: _records_registry},
|
||||
capability_factories={
|
||||
CAPABILITY_RECORDS_FILING: _records_registry,
|
||||
record_archive_capability(SIMULATION_PROVIDER_ID): _simulated_archive_provider,
|
||||
},
|
||||
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",
|
||||
),
|
||||
record_archive_capability(SIMULATION_PROVIDER_ID): CapabilityDocumentation(
|
||||
label="Record archive simulation",
|
||||
summary=(
|
||||
"Validates archive-neutral package and receipt handling without transferring custody."
|
||||
),
|
||||
contract_version="1.0.0",
|
||||
),
|
||||
},
|
||||
migration_spec=MigrationSpec(
|
||||
module_id=MODULE_ID,
|
||||
@@ -377,6 +457,9 @@ manifest = ModuleManifest(
|
||||
retirement_supported=True,
|
||||
retirement_provider=drop_table_retirement_provider(
|
||||
record_models.RecordChronologyEntry,
|
||||
record_models.RecordTransferPackageRevision,
|
||||
record_models.RecordDispositionRevision,
|
||||
record_models.RecordHoldRevision,
|
||||
record_models.RecordItem,
|
||||
record_models.RecordVolumeRevision,
|
||||
record_models.RecordRevision,
|
||||
@@ -396,6 +479,9 @@ manifest = ModuleManifest(
|
||||
record_models.RecordRevision,
|
||||
record_models.RecordItem,
|
||||
record_models.RecordChronologyEntry,
|
||||
record_models.RecordHoldRevision,
|
||||
record_models.RecordDispositionRevision,
|
||||
record_models.RecordTransferPackageRevision,
|
||||
record_models.RecordClassRevision,
|
||||
record_models.RecordFilePlanRevision,
|
||||
label="Records",
|
||||
@@ -417,6 +503,9 @@ manifest = ModuleManifest(
|
||||
"record_item",
|
||||
"record_class",
|
||||
"file_plan_node",
|
||||
"record_hold",
|
||||
"record_disposition",
|
||||
"record_transfer_package",
|
||||
),
|
||||
evidence=(
|
||||
"src/govoplan_records/backend/service.py",
|
||||
@@ -433,10 +522,16 @@ manifest = ModuleManifest(
|
||||
),
|
||||
),
|
||||
retention=InformationGovernanceDimension(
|
||||
adoption="contract_only",
|
||||
limitation=(
|
||||
"Record classes preserve retention inputs; closure, holds, calculation, appraisal, "
|
||||
"and disposition are tracked in Records #5."
|
||||
adoption="enforced",
|
||||
object_types=(
|
||||
"record",
|
||||
"record_class",
|
||||
"record_hold",
|
||||
"record_disposition",
|
||||
),
|
||||
evidence=(
|
||||
"src/govoplan_records/backend/service.py",
|
||||
"tests/test_records.py",
|
||||
),
|
||||
),
|
||||
institutional_context=InformationGovernanceDimension(
|
||||
@@ -462,8 +557,8 @@ manifest = ModuleManifest(
|
||||
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.",
|
||||
"Restricted object grants are tracked separately.",
|
||||
"Archive packaging is native, but real target conformance and destructive effects remain external work.",
|
||||
),
|
||||
supported_authority_modes=(
|
||||
"native_authoritative",
|
||||
@@ -481,6 +576,10 @@ manifest = ModuleManifest(
|
||||
"record item",
|
||||
"filing decision",
|
||||
"record chronology",
|
||||
"record hold",
|
||||
"record appraisal",
|
||||
"record disposition",
|
||||
"record transfer package",
|
||||
),
|
||||
non_owned_concepts=(
|
||||
"file content",
|
||||
|
||||
Reference in New Issue
Block a user