docs(search): add complete German workflow guidance
Module Package Release / publish-packages (push) Successful in 11s

This commit is contained in:
2026-08-22 20:56:44 +02:00
parent 016136f56c
commit 6c5545af66
6 changed files with 186 additions and 10 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/search-webui", "name": "@govoplan/search-webui",
"version": "0.1.18", "version": "0.1.19",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "webui/src/index.ts", "main": "webui/src/index.ts",
+2 -2
View File
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "govoplan-search" name = "govoplan-search"
version = "0.1.18" version = "0.1.19"
description = "Permission-aware global and contextual search for GovOPlaN." description = "Permission-aware global and contextual search for GovOPlaN."
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = [{ name = "GovOPlaN" }] authors = [{ name = "GovOPlaN" }]
dependencies = ["govoplan-core>=0.1.18"] dependencies = ["govoplan-core>=0.1.37"]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["src"] where = ["src"]
+1 -1
View File
@@ -1,3 +1,3 @@
"""GovOPlaN search module.""" """GovOPlaN search module."""
__version__ = "0.1.18" __version__ = "0.1.19"
+150 -5
View File
@@ -12,6 +12,7 @@ from govoplan_core.core.module_guards import (
) )
from govoplan_core.core.modules import ( from govoplan_core.core.modules import (
CapabilityDocumentation, CapabilityDocumentation,
DocumentationCondition,
DocumentationLink, DocumentationLink,
DocumentationTopic, DocumentationTopic,
FrontendModule, FrontendModule,
@@ -39,7 +40,7 @@ from govoplan_search.backend.dsar_provider import (
MODULE_ID = "search" MODULE_ID = "search"
MODULE_NAME = "Search" MODULE_NAME = "Search"
MODULE_VERSION = "0.1.18" MODULE_VERSION = "0.1.19"
READ_SCOPE = "search:result:read" READ_SCOPE = "search:result:read"
INDEX_SCOPE = "search:index:write" INDEX_SCOPE = "search:index:write"
ADMIN_SCOPE = "search:index:admin" ADMIN_SCOPE = "search:index:admin"
@@ -235,6 +236,56 @@ manifest = ModuleManifest(
"search.data-subject-requests", "search.data-subject-requests",
"search.admin.index", "search.admin.index",
], ],
"consequence_classes": {
"purge_derived_index": (
"Deletes matching derived documents and queued changes idempotently."
),
"preserve_authoritative_source": (
"Does not correct or erase the authoritative object owned by its source module."
),
"review_acl_only_matches": (
"Requires source-authority review when only an ACL projection identifies the subject."
),
},
},
translations={
"de": {
"title": "Datenschutzanfragen zum abgeleiteten Suchindex",
"summary": (
"Abgeleitete Indexkopien entfernen, ohne Search als führende Quelle "
"der indexierten Objekte zu behandeln."
),
"body": (
"Search gleicht innerhalb des exakten Mandanten nur ausdrückliche Index- "
"oder Änderungskennungen und quellanbietergeführte Referenzen ab. Selektoren "
"für Konten, Identitäten und Mitgliedschaften legen ausschließlich minimierte "
"Berechtigungsprojektionen offen und begründen niemals die Eigentümerschaft am "
"indexierten Quellobjekt. Indexierter Titel, Zusammenfassung, Inhalt, Suchtext, "
"URL, Schlüsselwörter, Metadaten, externe Referenzinhalte, Berechtigungswerte, "
"Prüfsummen, Cursor, eingereihte Dokumente und Fehler bleiben ausgeschlossen. "
"Abgeleitete Dokumente und eingereihte Änderungen können idempotent gelöscht "
"werden. Treffer allein über Berechtigungsprojektionen erfordern eine Prüfung "
"durch die führende Quelle. Berichtigung oder Löschung muss im Eigentümermodul "
"erfolgen, bevor Search neu aufgebaut wird; andernfalls darf die Quelle den "
"abgeleiteten Eintrag erneut veröffentlichen."
),
}
},
structured_translation_version="1",
structured_translations={
"de": {
"consequence_classes": {
"purge_derived_index": (
"Löscht passende abgeleitete Dokumente und eingereihte Änderungen idempotent."
),
"preserve_authoritative_source": (
"Berichtigt oder löscht nicht das führende Objekt des jeweiligen Quellmoduls."
),
"review_acl_only_matches": (
"Erfordert eine Prüfung durch die führende Quelle, wenn die betroffene Person nur über eine Berechtigungsprojektion erkannt wird."
),
}
}
}, },
links=( links=(
DocumentationLink( DocumentationLink(
@@ -270,14 +321,98 @@ manifest = ModuleManifest(
documentation_types=("admin", "user"), documentation_types=("admin", "user"),
audience=("administrator", "user"), audience=("administrator", "user"),
related_modules=("connectors", "views"), related_modules=("connectors", "views"),
conditions=(DocumentationCondition(required_scopes=(READ_SCOPE,)),),
metadata={ metadata={
"kind": "reference", "kind": "workflow",
"help_contexts": [ "help_contexts": [
"search.global", "search.global",
"search.results", "search.results",
"search.filters", "search.filters",
"search.admin.index", "search.admin.index",
], ],
"purpose": (
"Find currently authorized native and connected objects without broadening source permissions."
),
"prerequisites": [
"The actor has Search read access and the source module authorizes each returned object now.",
"At least one enabled source provider has published or can supply a searchable projection.",
],
"steps": [
"Open Search from the title bar, F3, or Ctrl/Cmd+K and enter a precise term.",
"Narrow the result set by type, source, or context without changing source authorization.",
"Review partial-provider and quarantine diagnostics before relying on completeness.",
"Open an authorized result; Search rechecks the provider-owned object permission before disclosure.",
"Administrators may process queued changes, reconcile providers, or start a bounded rebuild when diagnostics require it.",
],
"limitations": [
"Search is a derived discovery layer and is never authoritative for source content or access decisions.",
"A provider failure can make results incomplete but never permits unsafe results to bypass ACL filtering.",
],
"operational_consequences": {
"process_queue": "Applies pending derived index changes under current tenant and provider boundaries.",
"reconcile": "Compares enabled source coverage and retains unresolved changes in quarantine.",
"rebuild": "Recreates bounded derived projections while source data remains authoritative.",
},
"verification": [
"Every displayed result names its source and remains openable by the current principal.",
"Partial-provider failures and quarantined changes remain visible as diagnostics.",
"Administrative rebuild status can be reconciled against the enabled provider inventory.",
],
},
translations={
"de": {
"title": "Globale und kontextbezogene Suche",
"summary": (
"Berechtigte native und angebundene Objekte über eine gemeinsame, "
"berechtigungsbewusste Oberfläche finden."
),
"body": (
"Search verwendet den eingebauten Datenbankindex und kann optionale Anbieter "
"zusammenführen. Quellmodule melden durchsuchbare Typen, Kontextbereiche und "
"berechtigungsgeprüfte Indexeinträge. Externe Suchmaschinen bleiben optionale "
"Adapter. Der Suchbefehl in der Titelleiste, F3 oder Strg/Cmd+K öffnet die per "
"Tastatur bedienbare Suchüberlagerung; Filter erweitern niemals die "
"Quellberechtigungen der aktuellen Person. Anbieterausfälle werden als partielle "
"Diagnosen angezeigt, ohne sichere Ergebnisse zu verwerfen. Administratoren "
"können die Abdeckung nativer Quellen prüfen, eingereihte Änderungen verarbeiten, "
"aktivierte Module abgleichen und begrenzte Neuaufbauten aus der Administration "
"starten. Änderungen in Quarantäne bleiben sichtbar, bis sie repariert und "
"abgeglichen wurden."
),
}
},
structured_translation_version="1",
structured_translations={
"de": {
"purpose": (
"Aktuell berechtigte native und angebundene Objekte finden, ohne Quellberechtigungen zu erweitern."
),
"prerequisites": [
"Die handelnde Person darf Search lesen und das Quellmodul autorisiert jedes zurückgegebene Objekt weiterhin.",
"Mindestens ein aktivierter Quellanbieter hat eine durchsuchbare Projektion veröffentlicht oder kann sie bereitstellen.",
],
"steps": [
"Search über die Titelleiste, F3 oder Strg/Cmd+K öffnen und einen präzisen Suchbegriff eingeben.",
"Die Ergebnisse nach Typ, Quelle oder Kontext eingrenzen, ohne die Quellautorisierung zu verändern.",
"Diagnosen zu partiellen Anbieterausfällen und Quarantäne prüfen, bevor Vollständigkeit angenommen wird.",
"Ein berechtigtes Ergebnis öffnen; Search prüft vor der Offenlegung erneut die Berechtigung am quellengeführten Objekt.",
"Administratoren können bei entsprechenden Diagnosen eingereihte Änderungen verarbeiten, Anbieter abgleichen oder einen begrenzten Neuaufbau starten.",
],
"limitations": [
"Search ist eine abgeleitete Auffindbarkeitsschicht und niemals führend für Quellinhalte oder Zugriffsentscheidungen.",
"Ein Anbieterausfall kann Ergebnisse unvollständig machen, erlaubt aber niemals das Umgehen der Berechtigungsfilterung.",
],
"operational_consequences": {
"process_queue": "Verarbeitet ausstehende abgeleitete Indexänderungen innerhalb der aktuellen Mandanten- und Anbietergrenzen.",
"reconcile": "Vergleicht die Abdeckung aktivierter Quellen und hält ungeklärte Änderungen in Quarantäne.",
"rebuild": "Erstellt begrenzte abgeleitete Projektionen neu, während die Quelldaten führend bleiben.",
},
"verification": [
"Jedes angezeigte Ergebnis nennt seine Quelle und kann von der aktuellen Person weiterhin geöffnet werden.",
"Partielle Anbieterausfälle und Änderungen in Quarantäne bleiben als Diagnosen sichtbar.",
"Der administrative Neuaufbaustatus lässt sich mit dem Inventar aktivierter Anbieter abgleichen.",
],
}
}, },
links=( links=(
DocumentationLink( DocumentationLink(
@@ -295,10 +430,20 @@ manifest = ModuleManifest(
maturity="vertical_slice", maturity="vertical_slice",
documentation_ref="README.md", documentation_ref="README.md",
test_ref="tests/test_postgres_search.py", test_ref="tests/test_postgres_search.py",
known_limits=("The built-in PostgreSQL index is implemented; optional OpenSearch target evidence is not.",), known_limits=(
"The built-in PostgreSQL index is implemented; optional OpenSearch target evidence is not.",
),
supported_authority_modes=("external_mirror",), supported_authority_modes=("external_mirror",),
owned_concepts=("derived search index", "search ACL projection", "index change queue"), owned_concepts=(
non_owned_concepts=("source object", "source authorization", "external search engine"), "derived search index",
"search ACL projection",
"index change queue",
),
non_owned_concepts=(
"source object",
"source authorization",
"external search engine",
),
recovery_docs=("README.md",), recovery_docs=("README.md",),
security_docs=("README.md",), security_docs=("README.md",),
operations_docs=("README.md",), operations_docs=("README.md",),
+31
View File
@@ -0,0 +1,31 @@
from __future__ import annotations
import unittest
from govoplan_core.core.modules import (
documentation_structured_translation_issues,
user_workflow_scope_condition_issues,
)
from govoplan_search.backend.manifest import manifest
class SearchDocumentationTests(unittest.TestCase):
def test_public_topics_have_complete_german_reference_content(self) -> None:
self.assertEqual(2, len(manifest.documentation))
for topic in manifest.documentation:
translation = topic.translations.get("de", {})
self.assertTrue(
all(translation.get(key) for key in ("title", "summary", "body"))
)
self.assertEqual((), documentation_structured_translation_issues(topic))
def test_documentation_has_scope_conditioned_workflow_and_reference(self) -> None:
kinds = {topic.metadata.get("kind") for topic in manifest.documentation}
self.assertIn("workflow", kinds)
self.assertIn("reference", kinds)
for topic in manifest.documentation:
self.assertEqual((), user_workflow_scope_condition_issues(topic))
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/search-webui", "name": "@govoplan/search-webui",
"version": "0.1.18", "version": "0.1.19",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",