from __future__ import annotations from pathlib import Path from govoplan_core.core.institutional import CAPABILITY_DECISION_REGISTRY from govoplan_core.core.module_guards import ( drop_table_retirement_provider, persistent_table_uninstall_guard, ) from govoplan_core.core.modules import ( CapabilityDocumentation, DocumentationLink, DocumentationTopic, MigrationSpec, ModuleContext, ModuleInterfaceProvider, ModuleManifest, PermissionDefinition, RoleTemplate, ) from govoplan_core.core.provider_governance import declared_module_architecture from govoplan_core.core.search import SearchSourceProviderRegistration from govoplan_core.db.base import Base from govoplan_decisions.backend.db import models as decision_models from govoplan_decisions.backend.dsar_provider import ( DECISIONS_DSAR_CAPABILITY, DecisionsDsarProvider, ) from govoplan_decisions.backend.record_source import ( CAPABILITY_RECORD_SOURCE_DECISIONS, create_decisions_record_source, ) from govoplan_decisions.backend.service import SqlDecisionRegistry from govoplan_decisions.backend.search_source import create_decisions_search_source MODULE_ID = "decisions" MODULE_NAME = "Decisions" MODULE_VERSION = "0.1.19" READ_SCOPE = "decisions:decision:read" SENSITIVE_READ_SCOPE = "decisions:decision:read_sensitive" WRITE_SCOPE = "decisions:decision:write" ADMIN_SCOPE = "decisions:decision:admin" 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="Decisions", level="tenant", module_id=module_id, resource=resource, action=action, ) def _router(_context: ModuleContext): from govoplan_decisions.backend.router import router return router def _registry(_context: ModuleContext) -> SqlDecisionRegistry: return SqlDecisionRegistry() def _dsar_provider(_context: ModuleContext) -> DecisionsDsarProvider: return DecisionsDsarProvider() manifest = ModuleManifest( id=MODULE_ID, name=MODULE_NAME, version=MODULE_VERSION, optional_dependencies=( "mandates", "approvals", "committee", "cases", "audit", "policy", "records", "files", "postbox", "mail", ), provides_interfaces=( ModuleInterfaceProvider(name="decisions.formal_outcome", version="0.1.0"), ModuleInterfaceProvider(name="decisions.reconstruction", version="0.1.0"), ModuleInterfaceProvider( name=CAPABILITY_RECORD_SOURCE_DECISIONS, version="1.0.0" ), ModuleInterfaceProvider(name=DECISIONS_DSAR_CAPABILITY, version="0.1.0"), ), permissions=( _permission( READ_SCOPE, "View Decision metadata", "View formal Decision metadata and evidence references.", ), _permission( SENSITIVE_READ_SCOPE, "View protected Decisions", "View protected reasoning, operative results, and conditions.", ), _permission( WRITE_SCOPE, "Record Decisions", "Record governed formal outcomes and lifecycle revisions.", ), _permission( ADMIN_SCOPE, "Administer Decisions", "Administer Decision access, lifecycle, and recovery.", ), ), role_templates=( RoleTemplate( slug="decision_officer", name="Decision officer", description="Record and inspect formal Decisions.", permissions=(READ_SCOPE, SENSITIVE_READ_SCOPE, WRITE_SCOPE), ), RoleTemplate( slug="decision_auditor", name="Decision auditor", description="Reconstruct protected formal outcomes.", permissions=(READ_SCOPE, SENSITIVE_READ_SCOPE), ), ), route_factory=_router, capability_factories={ CAPABILITY_DECISION_REGISTRY: _registry, CAPABILITY_RECORD_SOURCE_DECISIONS: create_decisions_record_source, DECISIONS_DSAR_CAPABILITY: _dsar_provider, }, capability_documentation={ CAPABILITY_DECISION_REGISTRY: CapabilityDocumentation( label="Formal Decision registry", summary="Records and resolves immutable, reconstructable formal outcomes.", contract_version="0.1.0", ), CAPABILITY_RECORD_SOURCE_DECISIONS: CapabilityDocumentation( label="Formal Decision record source", summary="Resolves complete, currently authorized immutable Decision revisions for Records filing.", contract_version="1.0.0", ), DECISIONS_DSAR_CAPABILITY: CapabilityDocumentation( label="Decisions data-subject request provider", summary=( "Exports minimized formal-Decision creator attribution without " "protected reasoning, outcomes, conditions, or payloads." ), contract_version="0.1.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( decision_models.FormalDecisionRevision, label="Decisions" ), retirement_notes="Destructive retirement requires a database snapshot and removes formal Decision history.", ), uninstall_guard_providers=( persistent_table_uninstall_guard( decision_models.FormalDecisionRevision, label="Decisions" ), ), search_sources=( SearchSourceProviderRegistration( id="decisions.decisions", factory=create_decisions_search_source, ), ), documentation=( DocumentationTopic( id="decisions.data-subject-requests", title="Formal Decision data-subject requests", summary=( "Export minimized creator attribution from immutable formal Decision " "revisions without exposing protected Decision content." ), body=( "Decisions searches exact account, membership, identity, or explicit " "actor identifiers inside the active tenant. An optional Decision " "identifier only narrows that verified actor search and cannot disclose " "a Decision by itself. The access record contains revision, type, state, " "valid-time, recorded-time, and creator activity only. Protected " "reasoning, operative results, conditions, evidence payloads, and " "digests remain excluded. Formal Decision history is immutable legal " "and institutional evidence, so the provider publishes retain-only " "erasure outcomes and never rewrites a revision." ), layer="configured", documentation_types=("admin", "user"), audience=("user", "operator", "module_admin", "auditor"), related_modules=("core", "approvals", "committee", "records"), metadata={ "help_contexts": ["privacy.data-subject-requests"], "consequence_classes": { "export_creator_attribution": ( "Returns lifecycle and creator context without protected payloads." ), "retain_formal_history": ( "Preserves immutable institutional and legal evidence." ), }, }, translations={ "de": { "title": "Datenschutzanfragen zu formellen Entscheidungen", "summary": ( "Minimierte Angaben zur erstellenden Person aus unveränderlichen " "Entscheidungsrevisionen exportieren, ohne geschützte Inhalte offenzulegen." ), "body": ( "Decisions sucht innerhalb des aktiven Mandanten nach exakten Konto-, " "Mitgliedschafts-, Identitäts- oder ausdrücklich angegebenen Akteurskennungen. " "Eine optionale Entscheidungskennung schränkt nur diese bereits verifizierte " "Akteurssuche ein und kann für sich allein keine Entscheidung offenlegen. Der " "Ausgabedatensatz enthält ausschließlich Revision, Typ, Status, Gültigkeits- und " "Aufzeichnungszeit sowie die Aktivität der erstellenden Person. Geschützte " "Begründungen, verfügende Ergebnisse, Bedingungen, Nachweisinhalte und Prüfsummen " "bleiben ausgeschlossen. Die Historie formeller Entscheidungen ist unveränderlicher " "rechtlicher und institutioneller Nachweis; deshalb veröffentlicht der Anbieter " "ausschließlich Aufbewahrungsergebnisse und schreibt keine Revision um." ), } }, structured_translation_version="1", structured_translations={ "de": { "consequence_classes": { "export_creator_attribution": ( "Gibt Lebenszyklus- und Erstellerkontext ohne geschützte Inhalte zurück." ), "retain_formal_history": ( "Bewahrt unveränderliche institutionelle und rechtliche Nachweise auf." ), } } }, ), DocumentationTopic( id="decisions.formal-outcome", title="Formal institutional Decisions", summary="Reconstruct authority, evidence, reasoning, outcome, effects, and review history.", body="Decisions preserves immutable formal outcomes. Corrections and revocations create linked revisions; requested and observed effects remain distinct for reconciliation. List and unversioned detail reads follow the titlebar valid-time and recorded-time selection; exact revision references remain exact and current authorization is unchanged. When Records is enabled, filing resolves one exact complete Decision revision only after current metadata and protected-read permissions are rechecked; Records retains the digest-bound source reference while Decisions remains authoritative. When Search is enabled, Decisions contributes only rebuildable lifecycle, subject, legal-basis, effect, and linked-Case metadata. Operative results, reasoning, and conditions are excluded, and every result receives a current permission check before disclosure.", layer="configured", documentation_types=("admin", "user"), audience=("user", "operator", "module_admin", "auditor"), links=( DocumentationLink( label="Decisions domain and recovery", href="govoplan-decisions/docs/DECISIONS_DOMAIN.md", kind="repository", ), ), metadata={ "kind": "reference", "help_contexts": [ "decisions.search.result", "decisions.record.filing", ], }, translations={ "de": { "title": "Formelle institutionelle Entscheidungen", "summary": ( "Zuständigkeit, Nachweise, Begründung, Ergebnis, Wirkungen und " "Überprüfungshistorie nachvollziehbar rekonstruieren." ), "body": ( "Decisions bewahrt formelle Ergebnisse als unveränderliche Revisionen auf. " "Berichtigungen und Widerrufe erzeugen verknüpfte Revisionen; beabsichtigte und " "beobachtete Wirkungen bleiben für den Abgleich getrennt. Listen und nicht " "versionierte Detailansichten folgen der in der Titelleiste gewählten Gültigkeits- " "und Aufzeichnungszeit; exakte Revisionsverweise bleiben exakt und die aktuelle " "Berechtigungsprüfung gilt unverändert. Ist Records aktiviert, wird nur eine exakte, " "vollständige Entscheidungsrevision abgelegt, nachdem aktuelle Metadaten- und " "Leseberechtigungen erneut geprüft wurden. Records bewahrt den prüfsummengebundenen " "Quellverweis, während Decisions maßgeblich bleibt. Ist Search aktiviert, trägt " "Decisions ausschließlich wiederaufbaubare Lebenszyklus-, Betreff-, Rechtsgrundlagen-, " "Wirkungs- und Fallverknüpfungsmetadaten bei. Verfügende Ergebnisse, Begründungen und " "Bedingungen bleiben ausgeschlossen; vor jeder Offenlegung wird die aktuelle " "Berechtigung erneut geprüft." ), } }, ), ), architecture=declared_module_architecture( layer="governance_accountability", kind="domain", maturity="vertical_slice", documentation_ref="docs/DECISIONS_DOMAIN.md", test_ref="tests/test_decisions.py", known_limits=( "No dedicated Decision WebUI is included; consuming procedure modules present outcomes in context.", ), supported_authority_modes=("native_authoritative",), owned_concepts=( "formal decision", "decision correction", "decision effect observation", ), non_owned_concepts=( "approval gate", "committee deliberation", "effect execution", "record binary", ), reference_packages=("product.service-to-decision",), migration_docs=("docs/DECISIONS_DOMAIN.md",), recovery_docs=("docs/DECISIONS_DOMAIN.md",), security_docs=("docs/DECISIONS_DOMAIN.md",), operations_docs=("docs/DECISIONS_DOMAIN.md",), ), ) def get_manifest() -> ModuleManifest: return manifest