597 lines
28 KiB
Python
597 lines
28 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.approvals import CAPABILITY_APPROVAL_REQUESTS
|
|
from govoplan_core.core.module_guards import (
|
|
drop_table_retirement_provider,
|
|
persistent_table_uninstall_guard,
|
|
)
|
|
from govoplan_core.core.modules import (
|
|
CapabilityDocumentation,
|
|
DocumentationCondition,
|
|
DocumentationLink,
|
|
DocumentationTopic,
|
|
FrontendModule,
|
|
FrontendRoute,
|
|
MigrationSpec,
|
|
ModuleContext,
|
|
ModuleInterfaceProvider,
|
|
ModuleManifest,
|
|
NavItem,
|
|
PermissionDefinition,
|
|
ProductAreaContribution,
|
|
RoleTemplate,
|
|
)
|
|
from govoplan_core.core.provider_governance import declared_module_architecture
|
|
from govoplan_core.core.tasks import WorkItemProviderRegistration
|
|
from govoplan_core.core.views import ViewSurface
|
|
from govoplan_core.db.base import Base
|
|
from govoplan_approvals.backend.db import models as approval_models
|
|
from govoplan_approvals.backend.dsar_provider import (
|
|
APPROVALS_DSAR_CAPABILITY,
|
|
ApprovalsDsarProvider,
|
|
)
|
|
from govoplan_approvals.backend.service import SqlApprovalRequests
|
|
|
|
|
|
MODULE_ID = "approvals"
|
|
MODULE_NAME = "Approvals"
|
|
MODULE_VERSION = "0.1.20"
|
|
READ_SCOPE = "approvals:workspace:read"
|
|
WRITE_SCOPE = "approvals:workspace:write"
|
|
DECIDE_SCOPE = "approvals:workspace:decide"
|
|
ADMIN_SCOPE = "approvals:workspace:admin"
|
|
OPTIONAL_DEPENDENCIES = (
|
|
"workflow_engine",
|
|
"audit",
|
|
"files",
|
|
"notifications",
|
|
"policy",
|
|
"tasks",
|
|
)
|
|
|
|
|
|
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=MODULE_NAME,
|
|
level="tenant",
|
|
module_id=module_id,
|
|
resource=resource,
|
|
action=action,
|
|
)
|
|
|
|
|
|
def _router(_context: ModuleContext):
|
|
from govoplan_approvals.backend.router import router
|
|
|
|
return router
|
|
|
|
|
|
def _requests(_context: ModuleContext) -> SqlApprovalRequests:
|
|
return SqlApprovalRequests()
|
|
|
|
|
|
def _dsar_provider(_context: ModuleContext) -> ApprovalsDsarProvider:
|
|
return ApprovalsDsarProvider()
|
|
|
|
|
|
def _work_items(_context: ModuleContext):
|
|
from govoplan_approvals.backend.work_items import ApprovalWorkItemProvider
|
|
|
|
return ApprovalWorkItemProvider()
|
|
|
|
|
|
def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
|
|
current = session.query(approval_models.ApprovalRequestRevision).filter(
|
|
approval_models.ApprovalRequestRevision.tenant_id == tenant_id,
|
|
approval_models.ApprovalRequestRevision.superseded_at.is_(None),
|
|
)
|
|
return {
|
|
"approval_requests": current.count(),
|
|
"approval_pending": current.filter(
|
|
approval_models.ApprovalRequestRevision.state.in_(("pending", "escalated"))
|
|
).count(),
|
|
}
|
|
|
|
|
|
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,
|
|
),
|
|
provides_interfaces=(
|
|
ModuleInterfaceProvider(name=CAPABILITY_APPROVAL_REQUESTS, version="0.1.0"),
|
|
ModuleInterfaceProvider(name=APPROVALS_DSAR_CAPABILITY, version="0.1.0"),
|
|
),
|
|
permissions=(
|
|
_permission(
|
|
READ_SCOPE,
|
|
"View approval requests",
|
|
"Read approval chains, current gates, outcomes, and history.",
|
|
),
|
|
_permission(
|
|
WRITE_SCOPE,
|
|
"Request approvals",
|
|
"Create immutable approval chains for exact subject revisions.",
|
|
),
|
|
_permission(
|
|
DECIDE_SCOPE,
|
|
"Decide approvals",
|
|
"Approve or reject eligible approval steps.",
|
|
),
|
|
_permission(
|
|
ADMIN_SCOPE,
|
|
"Administer approvals",
|
|
"Escalate due approvals and configure approval policies.",
|
|
),
|
|
),
|
|
role_templates=(
|
|
RoleTemplate(
|
|
slug="approvals_manager",
|
|
name="Approvals manager",
|
|
description="Create and manage approval requests.",
|
|
permissions=(READ_SCOPE, WRITE_SCOPE, DECIDE_SCOPE),
|
|
),
|
|
RoleTemplate(
|
|
slug="approver",
|
|
name="Approver",
|
|
description="Read and decide eligible approval steps.",
|
|
permissions=(READ_SCOPE, DECIDE_SCOPE),
|
|
),
|
|
RoleTemplate(
|
|
slug="approvals_admin",
|
|
name="Approvals administrator",
|
|
description="Administer approval policies and escalation.",
|
|
permissions=(READ_SCOPE, WRITE_SCOPE, DECIDE_SCOPE, ADMIN_SCOPE),
|
|
),
|
|
),
|
|
route_factory=_router,
|
|
nav_items=(
|
|
NavItem(
|
|
path="/approvals",
|
|
label="Approvals",
|
|
icon="list-checks",
|
|
required_any=(READ_SCOPE,),
|
|
order=37,
|
|
),
|
|
),
|
|
frontend=FrontendModule(
|
|
module_id=MODULE_ID,
|
|
package_name="@govoplan/approvals-webui",
|
|
routes=(
|
|
FrontendRoute(
|
|
path="/approvals",
|
|
component="ApprovalsPage",
|
|
required_any=(READ_SCOPE,),
|
|
order=37,
|
|
),
|
|
),
|
|
nav_items=(
|
|
NavItem(
|
|
path="/approvals",
|
|
label="Approvals",
|
|
icon="list-checks",
|
|
required_any=(READ_SCOPE,),
|
|
order=37,
|
|
),
|
|
),
|
|
product_areas=(
|
|
ProductAreaContribution(
|
|
id="work",
|
|
module_id=MODULE_ID,
|
|
label="i18n:govoplan-core.product_area.work",
|
|
icon="list-checks",
|
|
description="i18n:govoplan-core.product_area.work_description",
|
|
surface_ids=("approvals.nav.approvals", "approvals.route.approvals"),
|
|
order=10,
|
|
),
|
|
),
|
|
view_surfaces=(
|
|
ViewSurface(
|
|
id="approvals.navigation",
|
|
module_id=MODULE_ID,
|
|
kind="navigation",
|
|
label="Approvals navigation",
|
|
order=10,
|
|
),
|
|
ViewSurface(
|
|
id="approvals.workspace",
|
|
module_id=MODULE_ID,
|
|
kind="route",
|
|
label="Approval request workspace",
|
|
order=20,
|
|
),
|
|
ViewSurface(
|
|
id="approvals.admin.templates",
|
|
module_id=MODULE_ID,
|
|
kind="section",
|
|
label="Approval templates",
|
|
order=30,
|
|
),
|
|
),
|
|
),
|
|
capability_factories={
|
|
CAPABILITY_APPROVAL_REQUESTS: _requests,
|
|
APPROVALS_DSAR_CAPABILITY: _dsar_provider,
|
|
},
|
|
capability_documentation={
|
|
CAPABILITY_APPROVAL_REQUESTS: CapabilityDocumentation(
|
|
label="Governed approval requests",
|
|
summary="Freezes exact subject approval chains and resolves auditable sequential decisions.",
|
|
contract_version="0.1.0",
|
|
),
|
|
APPROVALS_DSAR_CAPABILITY: CapabilityDocumentation(
|
|
label="Approvals data-subject request provider",
|
|
summary=(
|
|
"Exports personal decision participation and minimized actor "
|
|
"attribution without exposing immutable approval internals."
|
|
),
|
|
contract_version="0.1.0",
|
|
),
|
|
},
|
|
work_item_providers=(
|
|
WorkItemProviderRegistration(
|
|
id="approvals.pending",
|
|
factory=_work_items,
|
|
order=30,
|
|
),
|
|
),
|
|
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(
|
|
approval_models.ApprovalReplay,
|
|
approval_models.ApprovalLifecycleEvent,
|
|
approval_models.ApprovalDecisionRecord,
|
|
approval_models.ApprovalRequestRevision,
|
|
approval_models.ApprovalTemplateRevision,
|
|
label=MODULE_NAME,
|
|
),
|
|
retirement_notes="Destructive retirement requires a verified snapshot and removes approval chains, decisions, signature references, and lifecycle evidence.",
|
|
),
|
|
uninstall_guard_providers=(
|
|
persistent_table_uninstall_guard(
|
|
approval_models.ApprovalRequestRevision,
|
|
approval_models.ApprovalDecisionRecord,
|
|
approval_models.ApprovalLifecycleEvent,
|
|
approval_models.ApprovalReplay,
|
|
approval_models.ApprovalTemplateRevision,
|
|
label=MODULE_NAME,
|
|
),
|
|
),
|
|
tenant_summary_providers=(_tenant_summary,),
|
|
documentation=(
|
|
DocumentationTopic(
|
|
id="approvals.data-subject-requests",
|
|
title="Approval data-subject requests",
|
|
summary=(
|
|
"Export a subject's approval decisions and minimized lifecycle "
|
|
"attribution without disclosing unrelated chain content."
|
|
),
|
|
body=(
|
|
"Approvals correlates exact account, membership, identity, or explicit "
|
|
"actor identifiers inside the active tenant. An optional request "
|
|
"identifier only narrows an already verified actor search and never "
|
|
"discloses a request by itself. Authored decisions include their bounded "
|
|
"reason, step, outcome, delegation reference, and actor activities. "
|
|
"Request, lifecycle, and template activity is minimized to attribution "
|
|
"and stable context. Approval payloads, authority provenance, signature "
|
|
"objects, hashes, idempotency keys, and replay state are excluded. "
|
|
"Decision-reason erasure requires manual legal and chain-integrity "
|
|
"review; all other attribution remains immutable evidence."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("user", "operator", "module_admin", "auditor"),
|
|
related_modules=("core", "access", "workflow_engine", "audit"),
|
|
metadata={
|
|
"kind": "reference",
|
|
"help_contexts": [
|
|
"approvals.workspace",
|
|
"privacy.data-subject-requests",
|
|
],
|
|
"consequence_classes": {
|
|
"export_decision_participation": (
|
|
"Returns bounded subject-authored decision evidence."
|
|
),
|
|
"review_reason_erasure": (
|
|
"Requires legal and approval-chain integrity review."
|
|
),
|
|
"retain_attribution": (
|
|
"Preserves minimized immutable lifecycle evidence."
|
|
),
|
|
},
|
|
},
|
|
translations={
|
|
"de": {
|
|
"title": "Datenschutzanfragen zu Genehmigungen",
|
|
"summary": (
|
|
"Genehmigungsentscheidungen einer betroffenen Person und minimierte Lebenszykluszuordnungen ausgeben, "
|
|
"ohne Inhalte fremder Genehmigungsketten offenzulegen."
|
|
),
|
|
"body": (
|
|
"Approvals gleicht innerhalb des aktiven Mandanten exakte Konto-, Mitgliedschafts-, Identitäts- oder "
|
|
"Akteurskennungen ab. Eine optionale Antragskennung schränkt nur eine bereits verifizierte "
|
|
"Akteurssuche ein und legt für sich allein keinen Antrag offen. Von der Person verfasste Entscheidungen "
|
|
"enthalten den begrenzten Grund, Schritt, Ausgang, Delegationsverweis und Akteursaktivitäten. Antrags-, "
|
|
"Lebenszyklus- und Vorlagenaktivitäten werden auf Zuordnung und stabilen Kontext minimiert. "
|
|
"Genehmigungsinhalte, Herkunft der Befugnis, Signaturobjekte, Prüfsummen, Idempotenzschlüssel und "
|
|
"Wiederholungszustand bleiben ausgeschlossen. Das Löschen eines Entscheidungsgrunds erfordert eine "
|
|
"manuelle rechtliche Prüfung und Integritätsprüfung der Genehmigungskette; alle übrigen Zuordnungen "
|
|
"bleiben unveränderliche Nachweise."
|
|
),
|
|
}
|
|
},
|
|
structured_translation_version="1",
|
|
structured_translations={
|
|
"de": {
|
|
"consequence_classes": {
|
|
"export_decision_participation": "Gibt begrenzte, von der betroffenen Person verfasste Entscheidungsnachweise zurück.",
|
|
"review_reason_erasure": "Erfordert eine rechtliche Prüfung und eine Integritätsprüfung der Genehmigungskette.",
|
|
"retain_attribution": "Bewahrt minimierte, unveränderliche Lebenszyklusnachweise.",
|
|
}
|
|
}
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="approvals.module-boundary",
|
|
title="Governed approval chains",
|
|
summary="Create exact-subject approval chains with delegation, separation of duties, escalation, and signature evidence.",
|
|
body=(
|
|
"An Approval request freezes its subject revision, ordered steps, eligible selectors, quorum, rejection policy, signature requirement, and governance references. "
|
|
"Decisions are append-only, tenant-bound, optimistic-concurrency protected, and replay safe. Consuming modules verify the exact subject through the capability rather than reading Approval tables. "
|
|
"When Tasks is enabled, a pending step appears in the common work inbox only for a principal who currently passes the exact decision eligibility checks. "
|
|
"The workspace keeps the permission-filtered request collection and selected evidence in separately scrollable panes and stacks them at narrow widths without losing selection."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("user", "operator", "module_admin", "product_owner", "auditor"),
|
|
conditions=(DocumentationCondition(required_scopes=(READ_SCOPE,)),),
|
|
related_modules=OPTIONAL_DEPENDENCIES,
|
|
links=(
|
|
DocumentationLink(
|
|
label="Approvals boundary and recovery",
|
|
href="govoplan-approvals/docs/APPROVALS_DOMAIN_BOUNDARY.md",
|
|
kind="repository",
|
|
),
|
|
),
|
|
metadata={
|
|
"kind": "workflow",
|
|
"seed": True,
|
|
"help_contexts": [
|
|
"approvals.navigation",
|
|
"approvals.workspace",
|
|
"approvals.admin.templates",
|
|
"approvals.state.permission-blocked",
|
|
"approvals.state.empty",
|
|
],
|
|
"privacy_notes": [
|
|
"Approval lists and histories remain tenant-bound and permission-filtered.",
|
|
"Signature references identify evidence but do not expose private key material.",
|
|
"Decision history retains actor and reason as governed evidence.",
|
|
],
|
|
},
|
|
translations={
|
|
"de": {
|
|
"title": "Gesteuerte Genehmigungsketten",
|
|
"summary": (
|
|
"Genehmigungsketten für exakt bezeichnete Gegenstände mit Delegation, Funktionstrennung, Eskalation und Signaturnachweisen erstellen."
|
|
),
|
|
"body": (
|
|
"Ein Genehmigungsantrag fixiert Gegenstandsrevision, geordnete Schritte, zulässige Selektoren, Quorum, "
|
|
"Ablehnungsregel, Signaturanforderung und Governance-Verweise. Entscheidungen werden nur angefügt, sind "
|
|
"mandantengebunden, durch optimistische Nebenläufigkeit geschützt und wiederholungssicher. Verbrauchende "
|
|
"Module prüfen den exakten Gegenstand über die Fähigkeit, statt Approval-Tabellen zu lesen. Wenn Tasks "
|
|
"aktiv ist, erscheint ein offener Schritt nur für Personen im gemeinsamen Arbeitseingang, die die exakte "
|
|
"Entscheidungsberechtigung aktuell erfüllen. Der Arbeitsbereich hält die berechtigungsgefilterte Sammlung "
|
|
"und den ausgewählten Nachweis in getrennt scrollbaren Bereichen und stapelt sie bei schmaler Darstellung, "
|
|
"ohne die Auswahl zu verlieren."
|
|
),
|
|
}
|
|
},
|
|
structured_translation_version="1",
|
|
structured_translations={
|
|
"de": {
|
|
"privacy_notes": [
|
|
"Genehmigungslisten und Verläufe bleiben mandantengebunden und berechtigungsgefiltert.",
|
|
"Signaturverweise bezeichnen Nachweise, legen aber kein privates Schlüsselmaterial offen.",
|
|
"Der Entscheidungsverlauf bewahrt Akteur und Grund als gesteuerten Nachweis.",
|
|
]
|
|
}
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="approvals.reference.fields-and-consequences",
|
|
title="Approval fields and consequences",
|
|
summary="Exact-subject identity, selector, separation-of-duty, signature, and decision consequences.",
|
|
body=(
|
|
"Subject module, type, identifier, version, and SHA-256 digest freeze the exact object revision being approved. "
|
|
"Ordered steps, actor selectors, required counts, requester separation, unique actors, and signature requirements "
|
|
"are copied into the immutable request and do not follow later template changes. Actor values are provider-neutral "
|
|
"identifiers interpreted through Access and IDM contracts. Approval or rejection appends a decision with actor, "
|
|
"reason, optional signature reference, and optimistic-concurrency revision. Completed, rejected, cancelled, and "
|
|
"expired requests remain evidence and cannot be decided again."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("user", "operator", "module_admin", "auditor"),
|
|
related_modules=OPTIONAL_DEPENDENCIES,
|
|
links=(
|
|
DocumentationLink(
|
|
label="Approvals boundary and recovery",
|
|
href="govoplan-approvals/docs/APPROVALS_DOMAIN_BOUNDARY.md",
|
|
kind="repository",
|
|
),
|
|
),
|
|
metadata={
|
|
"kind": "reference",
|
|
"seed": True,
|
|
"help_contexts": [
|
|
"approvals.field.subject-reference",
|
|
"approvals.field.subject-digest",
|
|
"approvals.field.actor-selector",
|
|
"approvals.field.separation-of-duties",
|
|
"approvals.field.signature-reference",
|
|
"approvals.action.create-request",
|
|
"approvals.action.decide-request",
|
|
],
|
|
"consequence_classes": {
|
|
"create_request": "Freezes an exact subject and immutable approval chain.",
|
|
"approve_step": "Appends an attributable decision and may advance or complete the chain.",
|
|
"reject_request": "Appends a rejection and completes the request according to its frozen policy.",
|
|
"retain_evidence": "Keeps request revisions, decisions, reasons, and signature references for reconstruction.",
|
|
},
|
|
},
|
|
translations={
|
|
"de": {
|
|
"title": "Genehmigungsfelder und Folgen",
|
|
"summary": (
|
|
"Folgen exakter Gegenstandsidentität, Selektoren, Funktionstrennung, Signatur und Entscheidung."
|
|
),
|
|
"body": (
|
|
"Gegenstandsmodul, Typ, Kennung, Version und SHA-256-Prüfsumme fixieren die exakte zu genehmigende "
|
|
"Objektrevision. Geordnete Schritte, Akteursselektoren, erforderliche Anzahlen, Trennung vom Antragsteller, "
|
|
"eindeutige Akteure und Signaturanforderungen werden in den unveränderlichen Antrag kopiert und folgen "
|
|
"späteren Vorlagenänderungen nicht. Akteurswerte sind anbieterneutrale Kennungen, die über Access- und "
|
|
"IDM-Verträge interpretiert werden. Genehmigung oder Ablehnung fügt eine Entscheidung mit Akteur, Grund, "
|
|
"optionalem Signaturverweis und Nebenläufigkeitsrevision an. Abgeschlossene, abgelehnte, abgebrochene und "
|
|
"abgelaufene Anträge bleiben Nachweise und können nicht erneut entschieden werden."
|
|
),
|
|
}
|
|
},
|
|
structured_translation_version="1",
|
|
structured_translations={
|
|
"de": {
|
|
"consequence_classes": {
|
|
"create_request": "Fixiert einen exakten Gegenstand und eine unveränderliche Genehmigungskette.",
|
|
"approve_step": "Fügt eine zuordenbare Entscheidung an und kann die Kette fortsetzen oder abschließen.",
|
|
"reject_request": "Fügt eine Ablehnung an und beendet den Antrag gemäß seiner fixierten Regel.",
|
|
"retain_evidence": "Bewahrt Antragsrevisionen, Entscheidungen, Gründe und Signaturverweise für die Rekonstruktion.",
|
|
}
|
|
}
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="approvals.workflow.administer-templates",
|
|
title="Administer approval templates",
|
|
summary="Create reusable approval chains, publish immutable revisions, compare history, and escalate steps only after their configured due time.",
|
|
body=(
|
|
"Approval administrators manage templates under Admin > Tenant > Approval templates. A stable key identifies the template while every edit creates a new draft revision with its own content hash, actor, predecessor, and timestamp. Publishing creates another immutable revision that new requests can bind to exactly; existing requests never follow later template changes. "
|
|
"The history dialog compares any two tenant-visible revisions as deterministic JSON-pointer changes without hiding unchanged evidence. Request operators with approval administration permission see Escalate only for pending requests, and the action becomes available after the current step's due time. The backend rechecks the due time and optimistic-concurrency revision before recording the lifecycle transition."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("operator", "module_admin", "product_owner", "auditor"),
|
|
conditions=(DocumentationCondition(required_scopes=(ADMIN_SCOPE,)),),
|
|
links=(
|
|
DocumentationLink(
|
|
label="Approval templates",
|
|
href="/admin?section=tenant-approval-templates",
|
|
kind="runtime",
|
|
),
|
|
DocumentationLink(
|
|
label="Template API", href="/api/v1/approvals/templates", kind="api"
|
|
),
|
|
DocumentationLink(
|
|
label="Template history API",
|
|
href="/api/v1/approvals/templates/{template_id}/history",
|
|
kind="api",
|
|
),
|
|
DocumentationLink(
|
|
label="Template comparison API",
|
|
href="/api/v1/approvals/templates/{template_id}/compare",
|
|
kind="api",
|
|
),
|
|
),
|
|
metadata={
|
|
"kind": "workflow",
|
|
"help_contexts": [
|
|
"approvals.admin.templates",
|
|
"approvals.action.escalate-request",
|
|
],
|
|
"consequence_classes": {
|
|
"revise_template": "Supersedes the current template and creates a new draft revision.",
|
|
"publish_template": "Creates an immutable published revision available to new requests.",
|
|
"escalate_request": "Records that the current due step entered escalation without deciding it.",
|
|
},
|
|
},
|
|
translations={
|
|
"de": {
|
|
"title": "Genehmigungsvorlagen verwalten",
|
|
"summary": (
|
|
"Wiederverwendbare Genehmigungsketten erstellen, unveränderliche Revisionen veröffentlichen, Verläufe vergleichen und Schritte erst nach ihrer Fälligkeit eskalieren."
|
|
),
|
|
"body": (
|
|
"Genehmigungsadministratoren verwalten Vorlagen unter Administration > Mandant > Genehmigungsvorlagen. "
|
|
"Ein stabiler Schlüssel bezeichnet die Vorlage; jede Bearbeitung erzeugt eine neue Entwurfsrevision mit "
|
|
"eigener Inhaltsprüfsumme, Akteur, Vorgänger und Zeitangabe. Die Veröffentlichung erzeugt eine weitere "
|
|
"unveränderliche Revision, an die neue Anträge exakt gebunden werden können; bestehende Anträge folgen "
|
|
"späteren Änderungen nie. Der Verlaufsdialog vergleicht zwei mandantensichtbare Revisionen als "
|
|
"deterministische JSON-Pointer-Änderungen, ohne unveränderte Nachweise auszublenden. Antragsbetreiber mit "
|
|
"Genehmigungsadministrationsrecht sehen Eskalieren nur bei offenen Anträgen und erst nach Fälligkeit des "
|
|
"aktuellen Schritts. Das Backend prüft Fälligkeit und Nebenläufigkeitsrevision erneut, bevor es den "
|
|
"Lebenszyklusübergang festhält."
|
|
),
|
|
}
|
|
},
|
|
structured_translation_version="1",
|
|
structured_translations={
|
|
"de": {
|
|
"consequence_classes": {
|
|
"revise_template": "Ersetzt die aktuelle Vorlage und erzeugt eine neue Entwurfsrevision.",
|
|
"publish_template": "Erzeugt eine unveränderliche veröffentlichte Revision für neue Anträge.",
|
|
"escalate_request": "Hält fest, dass der aktuell fällige Schritt eskaliert wurde, ohne ihn zu entscheiden.",
|
|
}
|
|
}
|
|
},
|
|
),
|
|
),
|
|
architecture=declared_module_architecture(
|
|
layer="human_work_procedure",
|
|
kind="governance",
|
|
maturity="vertical_slice",
|
|
documentation_ref="docs/APPROVALS_DOMAIN_BOUNDARY.md",
|
|
test_ref="tests/test_approvals.py",
|
|
known_limits=(
|
|
"Policy-authored template selection and cryptographic signature providers remain optional product depth; signature references are evidence pointers, not a cryptographic claim.",
|
|
),
|
|
supported_authority_modes=("native_authoritative",),
|
|
owned_concepts=(
|
|
"approval request",
|
|
"approval chain",
|
|
"approval decision",
|
|
"approval escalation",
|
|
),
|
|
non_owned_concepts=(
|
|
"workflow execution",
|
|
"identity",
|
|
"document signature",
|
|
"module business outcome",
|
|
),
|
|
migration_docs=("docs/APPROVALS_DOMAIN_BOUNDARY.md",),
|
|
recovery_docs=("docs/APPROVALS_DOMAIN_BOUNDARY.md",),
|
|
security_docs=("docs/APPROVALS_DOMAIN_BOUNDARY.md",),
|
|
operations_docs=("docs/APPROVALS_DOMAIN_BOUNDARY.md",),
|
|
),
|
|
)
|
|
|
|
|
|
def get_manifest() -> ModuleManifest:
|
|
return manifest
|