diff --git a/package.json b/package.json index f248dab..4d8a565 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/approvals", - "version": "0.1.18", + "version": "0.1.19", "private": true, "description": "Governed approval chains, decisions, delegation, and escalation for GovOPlaN.", "type": "module", diff --git a/pyproject.toml b/pyproject.toml index e8697d7..a771b51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-approvals" -version = "0.1.18" +version = "0.1.19" description = "Governed approval chains, decisions, delegation, and escalation for GovOPlaN." readme = "README.md" requires-python = ">=3.12" diff --git a/src/govoplan_approvals/backend/manifest.py b/src/govoplan_approvals/backend/manifest.py index 5d1503c..c0e92ab 100644 --- a/src/govoplan_approvals/backend/manifest.py +++ b/src/govoplan_approvals/backend/manifest.py @@ -13,6 +13,7 @@ from govoplan_core.core.module_guards import ( ) from govoplan_core.core.modules import ( CapabilityDocumentation, + DocumentationCondition, DocumentationLink, DocumentationTopic, FrontendModule, @@ -40,7 +41,7 @@ from govoplan_approvals.backend.service import SqlApprovalRequests MODULE_ID = "approvals" MODULE_NAME = "Approvals" -MODULE_VERSION = "0.1.18" +MODULE_VERSION = "0.1.19" READ_SCOPE = "approvals:workspace:read" WRITE_SCOPE = "approvals:workspace:write" DECIDE_SCOPE = "approvals:workspace:decide" @@ -300,6 +301,7 @@ manifest = ModuleManifest( audience=("user", "operator", "module_admin", "auditor"), related_modules=("core", "access", "workflow_engine", "audit"), metadata={ + "kind": "reference", "help_contexts": [ "approvals.workspace", "privacy.data-subject-requests", @@ -316,6 +318,36 @@ manifest = ModuleManifest( ), }, }, + 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", @@ -330,6 +362,7 @@ manifest = ModuleManifest( 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( @@ -339,6 +372,7 @@ manifest = ModuleManifest( ), ), metadata={ + "kind": "workflow", "seed": True, "help_contexts": [ "approvals.navigation", @@ -353,6 +387,34 @@ manifest = ModuleManifest( "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", @@ -378,6 +440,7 @@ manifest = ModuleManifest( ), ), metadata={ + "kind": "reference", "seed": True, "help_contexts": [ "approvals.field.subject-reference", @@ -395,6 +458,34 @@ manifest = ModuleManifest( "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", @@ -407,6 +498,7 @@ manifest = ModuleManifest( layer="configured", documentation_types=("admin", "user"), audience=("operator", "module_admin", "product_owner", "auditor"), + conditions=(DocumentationCondition(required_scopes=(ADMIN_SCOPE,)),), links=( DocumentationLink( label="Approval templates", @@ -428,6 +520,7 @@ manifest = ModuleManifest( ), ), metadata={ + "kind": "workflow", "help_contexts": [ "approvals.admin.templates", "approvals.action.escalate-request", @@ -438,6 +531,35 @@ manifest = ModuleManifest( "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( diff --git a/tests/test_interface_documentation_contract.py b/tests/test_interface_documentation_contract.py index 73e185c..45db33e 100644 --- a/tests/test_interface_documentation_contract.py +++ b/tests/test_interface_documentation_contract.py @@ -2,6 +2,10 @@ from __future__ import annotations import unittest +from govoplan_core.core.modules import ( + documentation_structured_translation_issues, + localizable_documentation_metadata_keys, +) from govoplan_approvals.backend.manifest import manifest @@ -33,6 +37,26 @@ class ApprovalsInterfaceDocumentationContractTests(unittest.TestCase): self.assertIn("reject_request", reference.metadata["consequence_classes"]) self.assertIn("approvals.admin.templates", templates.metadata["help_contexts"]) + def test_german_reference_documentation_is_complete(self) -> None: + topics = manifest.documentation + self.assertEqual(4, len(topics)) + for topic in topics: + translation = topic.translations.get("de", {}) + self.assertTrue(translation.get("title"), topic.id) + self.assertTrue(translation.get("summary"), topic.id) + self.assertTrue(translation.get("body"), topic.id) + if localizable_documentation_metadata_keys(topic): + self.assertEqual("1", topic.structured_translation_version, topic.id) + self.assertIn("de", topic.structured_translations, topic.id) + self.assertEqual((), documentation_structured_translation_issues(topic)) + + kinds = {topic.metadata.get("kind") for topic in topics} + self.assertIn("workflow", kinds) + self.assertIn("reference", kinds) + for topic in topics: + if topic.metadata.get("kind") == "workflow" and "user" in topic.documentation_types: + self.assertTrue(topic.conditions, topic.id) + if __name__ == "__main__": unittest.main() diff --git a/webui/package.json b/webui/package.json index 62b49ab..e54edda 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/approvals-webui", - "version": "0.1.18", + "version": "0.1.19", "private": true, "type": "module", "main": "src/index.ts", diff --git a/webui/src/module.ts b/webui/src/module.ts index cce2435..a3b3d41 100644 --- a/webui/src/module.ts +++ b/webui/src/module.ts @@ -28,7 +28,7 @@ const approvalsAdminSections: AdminSectionsUiCapability = { export const approvalsModule: PlatformWebModule = { id: "approvals", label: "i18n:govoplan-approvals.approvals", - version: "0.1.18", + version: "0.1.19", dependencies: ["access"], optionalDependencies: ["workflow_engine", "audit", "files", "notifications", "policy", "tasks"], translations: generatedTranslations,