from __future__ import annotations from govoplan_core.core.modules import with_documentation_structured_translations from govoplan_files.backend.german_structured_documentation import GERMAN_STRUCTURED_TRANSLATIONS from dataclasses import replace from pathlib import Path from sqlalchemy import inspect from govoplan_core.core.access import ( CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER, ) from govoplan_core.core.encryption import CAPABILITY_ENCRYPTION_CONTENT_CIPHER from govoplan_core.core.files import ( CAPABILITY_FILES_ACCESS, CAPABILITY_FILES_ARTIFACT_STORE, CAPABILITY_FILES_POSTBOX_REFERENCES, CAPABILITY_FILES_TABULAR_CONTENT, ) 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, ModuleInterfaceRequirement, ModuleManifest, NavItem, PermissionDefinition, ProductAreaContribution, QuickAccessTool, RoleTemplate, ) from govoplan_core.core.operations import OperationalCheckProviderRegistration from govoplan_core.core.provider_governance import ( ExternalProviderDeclaration, ExternalProviderStateProviderRegistration, ProviderBehaviorDeclaration, ProviderObjectDeclaration, declared_module_architecture, ) from govoplan_core.core.search import SearchSourceProviderRegistration from govoplan_core.core.views import ViewSurface from govoplan_core.db.base import Base from govoplan_files.backend.change_tracking import register_files_change_tracking from govoplan_files.backend.configuration_provider import ( FILES_CONFIGURATION_CAPABILITY, FILES_INFRASTRUCTURE_DEPENDENCY_CAPABILITY, ) from govoplan_files.backend.db import models as file_models # noqa: F401 - populate Files ORM metadata from govoplan_files.backend.documentation import documentation_topics from govoplan_files.backend.german_documentation import ( localize_documentation_topics, ) from govoplan_files.backend.dsar_provider import FILES_DSAR_CAPABILITY from govoplan_files.backend.form_evidence import ( CAPABILITY_FORM_EVIDENCE_FILES, create_files_form_evidence_provider, ) from govoplan_files.backend.provider_state import ( REMOTE_STORAGE_PROVIDER_ID, remote_storage_provider_states, ) from govoplan_files.backend.search_source import create_files_search_source from govoplan_files.backend.record_source import ( CAPABILITY_RECORD_SOURCE_FILES, create_files_record_source, ) register_files_change_tracking() _files_table_retirement_provider = drop_table_retirement_provider( file_models.FileBlob, file_models.FileIntegrityScan, file_models.FileIntegrityFinding, file_models.FileFolder, file_models.FileAsset, file_models.FileVersion, file_models.FileFormEvidenceGrant, file_models.FileShare, file_models.FileConnectorCredential, file_models.FileConnectorPolicy, file_models.FileConnectorProfile, file_models.FileConnectorSpace, file_models.CampaignAttachmentUse, label="Files", ) def _configuration_provider(context: ModuleContext) -> object: del context from govoplan_files.backend.configuration_provider import ( FilesConfigurationProvider, ) return FilesConfigurationProvider() def _files_retirement_provider(session: object | None, module_id: str): plan = _files_table_retirement_provider(session, module_id) base_executor = plan.destroy_data_executor if base_executor is None: return plan def executor(execute_session: object, execute_module_id: str) -> None: if not hasattr(execute_session, "get_bind") or not hasattr( execute_session, "query" ): raise RuntimeError( "No database session is available for Files credential retirement." ) live_inspector = inspect(execute_session.get_bind()) if any( live_inspector.has_table(table_name) for table_name in ( file_models.FileConnectorCredential.__tablename__, file_models.FileConnectorProfile.__tablename__, ) ): from govoplan_files.backend.storage.connector_credential_deletion import ( delete_connector_credentials_for_retirement, ) delete_connector_credentials_for_retirement(execute_session) base_executor(execute_session, execute_module_id) return replace( plan, destroy_data_warnings=( *plan.destroy_data_warnings, "Files-owned encrypted connector credentials are scrubbed and audited immediately before tables are dropped; legacy non-owned external references are detached without claiming provider-side deletion.", ), destroy_data_executor=executor, ) 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="Files", level="tenant", module_id=module_id, resource=resource, action=action, ) PERMISSIONS = ( _permission( "files:file:read", "View files", "List, search and preview managed files." ), _permission( "files:file:download", "Download files", "Download managed files and generated archives.", ), _permission( "files:file:upload", "Upload files", "Upload new managed file versions." ), _permission( "files:file:organize", "Organize files", "Create folders, rename, move or copy managed files.", ), _permission( "files:file:share", "Share files", "List, grant, update, expire, and revoke managed file shares.", ), _permission( "files:file:delete", "Delete files", "Delete or hide managed files and folders where policy allows it.", ), _permission( "files:file:restore", "Restore files", "Restore soft-deleted managed files, folders, and connector spaces.", ), _permission( "files:file:retention", "Govern file retention", "Set managed-file retention deadlines and legal holds.", ), _permission( "files:file:purge", "Purge files", "Irreversibly purge eligible file records and unreferenced managed blobs.", ), _permission( "files:connector:write", "Write connected files", "Write managed file bytes to explicitly writable remote connector spaces.", ), _permission( "files:file:admin", "Administer file spaces", "Administer all file spaces in the tenant.", ), ) ROLE_TEMPLATES = ( RoleTemplate( slug="file_manager", name="File manager", description="Manage tenant file spaces without campaign delivery rights.", permissions=( "files:file:read", "files:file:download", "files:file:upload", "files:file:organize", "files:file:share", "files:file:delete", "files:file:restore", "files:file:retention", ), ), RoleTemplate( slug="file_viewer", name="File viewer", description="Read and download permitted files.", permissions=("files:file:read", "files:file:download"), ), ) def _tenant_summary(session, tenant_id: str) -> dict[str, int]: from govoplan_files.backend.db.models import ( FileAsset, FileConnectorCredential, FileConnectorPolicy, FileConnectorProfile, FileConnectorSpace, FileFormEvidenceGrant, ) return { "files": session.query(FileAsset) .filter(FileAsset.tenant_id == tenant_id) .count(), "connector_credentials": session.query(FileConnectorCredential) .filter(FileConnectorCredential.tenant_id == tenant_id) .count(), "connector_policies": session.query(FileConnectorPolicy) .filter(FileConnectorPolicy.tenant_id == tenant_id) .count(), "connector_profiles": session.query(FileConnectorProfile) .filter(FileConnectorProfile.tenant_id == tenant_id) .count(), "connector_spaces": session.query(FileConnectorSpace) .filter(FileConnectorSpace.tenant_id == tenant_id) .count(), "form_evidence_upload_grants": session.query(FileFormEvidenceGrant) .filter(FileFormEvidenceGrant.tenant_id == tenant_id) .count(), } def _tenant_summary_batch(session, tenant_ids) -> dict[str, dict[str, int]]: from sqlalchemy import func from govoplan_files.backend.db.models import ( FileAsset, FileConnectorCredential, FileConnectorPolicy, FileConnectorProfile, FileConnectorSpace, FileFormEvidenceGrant, ) ids = tuple(dict.fromkeys(str(tenant_id) for tenant_id in tenant_ids if tenant_id)) if not ids: return {} counts: dict[str, dict[str, int]] = { tenant_id: { "files": 0, "connector_credentials": 0, "connector_policies": 0, "connector_profiles": 0, "connector_spaces": 0, "form_evidence_upload_grants": 0, } for tenant_id in ids } models = ( ("files", FileAsset), ("connector_credentials", FileConnectorCredential), ("connector_policies", FileConnectorPolicy), ("connector_profiles", FileConnectorProfile), ("connector_spaces", FileConnectorSpace), ("form_evidence_upload_grants", FileFormEvidenceGrant), ) for count_key, model in models: rows = ( session.query(model.tenant_id, func.count(model.id)) .filter(model.tenant_id.in_(ids)) .group_by(model.tenant_id) .all() ) for tenant_id, count in rows: counts[tenant_id][count_key] = int(count) return counts def _veto_group_delete(session, tenant_id: str, group_id: str) -> None: from govoplan_files.backend.db.models import ( FileAsset, FileConnectorSpace, FileFolder, FileShare, ) owned_asset_count = ( session.query(FileAsset) .filter(FileAsset.tenant_id == tenant_id, FileAsset.owner_group_id == group_id) .count() ) owned_folder_count = ( session.query(FileFolder) .filter( FileFolder.tenant_id == tenant_id, FileFolder.owner_group_id == group_id ) .count() ) owned_connector_space_count = ( session.query(FileConnectorSpace) .filter( FileConnectorSpace.tenant_id == tenant_id, FileConnectorSpace.owner_group_id == group_id, ) .count() ) shared_asset_count = ( session.query(FileShare) .filter( FileShare.tenant_id == tenant_id, FileShare.target_type == "group", FileShare.target_id == group_id, ) .count() ) if ( owned_asset_count or owned_folder_count or owned_connector_space_count or shared_asset_count ): raise ValueError( "Cannot remove the group while it owns files, folders, connector spaces or file shares." ) def _files_router(context: ModuleContext): from govoplan_files.backend.runtime import configure_runtime configure_runtime(registry=context.registry, settings=context.settings) from govoplan_files.backend.router import router return router def _public_tenant_resolver(request: object, session: object) -> str | None: path = str(getattr(getattr(request, "url", None), "path", "")) if not path.endswith("/files/form-evidence/upload") or not hasattr( session, "query" ): return None headers = getattr(request, "headers", {}) token = str(headers.get("X-Form-Evidence-Token") or "").strip() if len(token) < 32: return None import hashlib row = ( session.query(file_models.FileFormEvidenceGrant) .filter( file_models.FileFormEvidenceGrant.token_sha256 == hashlib.sha256(token.encode("utf-8")).hexdigest() ) .one_or_none() ) return row.tenant_id if row is not None else None REMOTE_STORAGE_PROVIDER = ExternalProviderDeclaration( id=REMOTE_STORAGE_PROVIDER_ID, module_id="files", label="Remote file storage mirror", maturity="synchronize", operations=("discover", "search", "read", "synchronize", "preview", "write"), objects=( ProviderObjectDeclaration( object_type="remote_folder", field_groups=("identity", "hierarchy", "display", "source_metadata"), authority_modes=("external_authoritative", "external_mirror"), default_authority_mode="external_mirror", ), ProviderObjectDeclaration( object_type="remote_file", field_groups=("identity", "content", "version", "source_metadata"), authority_modes=("external_authoritative", "external_mirror"), default_authority_mode="external_mirror", ), ), behavior=ProviderBehaviorDeclaration( revision_tokens="Remote path, provider revision, size, and content digest are retained on managed imports.", concurrency="A sync compares the frozen source reference and revision before creating a new managed version.", freshness="Provider state distinguishes software availability from an unobserved live remote binding.", health="Unsupported providers or missing optional transports fail closed; live health remains unknown without a probe.", max_read_items=5000, idempotency="Source profile, remote object reference, and revision/digest suppress duplicate managed versions.", retry="Operators repeat bounded browse/import after a classified transport failure; effects are not blindly retried.", timeout_seconds=30, conflicts="Managed-file conflicts are explicit. S3 write-back requires a conditional create or a matching expected revision and never overwrites blindly.", outcome_unknown="Interrupted downloads are discarded unless their managed version commits. An uncertain S3 write remains outcome-unknown or recovery-required until provider request/content markers are reconciled.", outcome_unknown_supported=True, evidence="Managed versions retain connector profile, remote object identity, source revision, digest, and acquisition time.", audit_event_types=( "files.connector.accessed", "files.connector.imported", "files.connector.synced", "files.connector.written", ), correction="A later acquisition creates a new managed version and preserves prior provenance; a later S3 correction is another explicitly revision-guarded write.", rollback="Remote reads require no remote rollback. S3 writes use forward recovery because provider effects cannot join the Files database transaction.", compensation="A wrongly imported managed version can be retired under Files policy without deleting the source.", reconciliation="Re-read source metadata and digest, then compare the committed managed version and object-store inventory.", outage="Previously imported managed versions remain available while remote spaces report unknown or stale state.", classifications=("internal", "confidential", "personal"), purposes=("governed file acquisition", "managed evidence snapshot"), retention="Files retention applies to managed versions; external retention remains provider-owned.", secret_handling="Credentials remain encrypted or deployment-owned and never appear in provider state or provenance.", ), documentation_topic_ids=("files.governed-connectors-and-provenance",), ) def _dsar_provider(context: ModuleContext) -> object: del context from govoplan_files.backend.dsar_provider import FilesDsarProvider return FilesDsarProvider() manifest = ModuleManifest( id="files", name="Files", version="0.1.24", required_capabilities=( CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR, ), optional_dependencies=("campaigns", "encryption", "postbox", "records", "search"), provides_interfaces=( ModuleInterfaceProvider(name="files.access", version="0.1.6"), ModuleInterfaceProvider(name="files.campaign_attachments", version="0.1.6"), ModuleInterfaceProvider(name=CAPABILITY_FILES_ARTIFACT_STORE, version="0.1.14"), ModuleInterfaceProvider(name=CAPABILITY_FILES_POSTBOX_REFERENCES, version="1.0.0"), ModuleInterfaceProvider(name=CAPABILITY_FILES_TABULAR_CONTENT, version="1.0.0"), ModuleInterfaceProvider(name=CAPABILITY_RECORD_SOURCE_FILES, version="1.0.0"), ModuleInterfaceProvider(name=CAPABILITY_FORM_EVIDENCE_FILES, version="1.0.0"), ModuleInterfaceProvider(name=FILES_DSAR_CAPABILITY, version="0.1.0"), ), requires_interfaces=( ModuleInterfaceRequirement( name="campaigns.access", version_min="0.1.0", version_max_exclusive="0.2.0", optional=True, ), ModuleInterfaceRequirement( name=CAPABILITY_ENCRYPTION_CONTENT_CIPHER, version_min="1.0.0", version_max_exclusive="2.0.0", optional=True, ), ModuleInterfaceRequirement( name="search.source", version_min="1.0.0", version_max_exclusive="2.0.0", optional=True, ), ), permissions=PERMISSIONS, route_factory=_files_router, public_tenant_resolver=_public_tenant_resolver, role_templates=ROLE_TEMPLATES, tenant_summary_providers=(_tenant_summary,), tenant_summary_batch_providers=(_tenant_summary_batch,), search_sources=( SearchSourceProviderRegistration( id="files.objects", factory=create_files_search_source, ), ), delete_veto_providers={"group": (_veto_group_delete,)}, nav_items=( NavItem( path="/files", label="Files", icon="folder", required_any=("files:file:read",), order=40, ), ), frontend=FrontendModule( module_id="files", package_name="@govoplan/files-webui", routes=( FrontendRoute( path="/files", component="FilesPage", required_any=("files:file:read",), order=40, ), ), nav_items=( NavItem( path="/files", label="Files", icon="folder", required_any=("files:file:read",), order=40, ), ), view_surfaces=( ViewSurface( id="files.admin.system-connectors", module_id="files", kind="section", label="System file connections", order=75, ), ViewSurface( id="files.admin.tenant-connectors", module_id="files", kind="section", label="Tenant file connections", order=65, ), ViewSurface( id="files.admin.tenant-integrity", module_id="files", kind="section", label="File integrity", order=66, ), ViewSurface( id="files.admin.group-connectors", module_id="files", kind="section", label="Group file connections", order=65, ), ViewSurface( id="files.admin.user-connectors", module_id="files", kind="section", label="User file connections", order=65, ), ViewSurface( id="files.settings.connectors", module_id="files", kind="section", label="Personal file connections", order=20, ), ViewSurface( id="files.widget.spaces", module_id="files", kind="section", label="File spaces widget", order=35, ), ViewSurface( id="files.quick_access.files", module_id="files", kind="quick_access", label="Files Quick Access", order=40, ), ), product_areas=( ProductAreaContribution( id="records-documents", module_id="files", label="i18n:govoplan-core.product_area.records_documents", icon="folder", description="i18n:govoplan-core.product_area.records_documents_description", surface_ids=("files.nav.files", "files.route.files"), order=30, ), ), quick_access_tools=( QuickAccessTool( id="files.recent", module_id="files", category_id="files", label="i18n:govoplan-files.files.6ce6c512", description="i18n:govoplan-files.quick_access_description", surface_id="files.quick_access.files", icon="folder", full_page_path="/files", required_any=("files:file:read",), order=10, modes=("browse", "select", "upload"), returned_reference_kinds=("files.file-version",), help_context_id="files.quick_access.files", ), ), ), documentation=localize_documentation_topics(( DocumentationTopic( id="files.tabular-content", title="Use managed CSV and XLSX versions as governed data sources", summary="Expose exact, authorized managed file versions to Connectors without bypassing Files controls.", body=( "Files lists only CSV and XLSX assets visible to the current tenant user. " "Opening content additionally requires Files download permission and an exact immutable version reference. " "Size ceilings are checked before storage access; checksum verification, quarantine, encryption envelopes, deletion, ownership, and shares remain authoritative in Files. " "Connectors receives file metadata and verified bytes through the Core capability and never imports Files models, storage keys, or encryption internals. " "A newer current version does not silently replace a pinned source version; Connectors reports the version change for explicit review." ), layer="available", documentation_types=("admin", "user"), audience=("administrator", "operator", "power_user"), related_modules=("connectors", "datasources", "dataflow"), conditions=( DocumentationCondition( required_modules=("files", "connectors"), required_scopes=("files:file:read", "files:file:download"), ), ), links=( DocumentationLink( label="Files", href="/files", kind="runtime", ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), metadata={ "kind": "guide", "consequence_classes": { "pin_exact_version": "A source keeps its reviewed immutable file version until explicitly refreshed.", "preserve_file_controls": "Files access, integrity, encryption, retention, and legal-hold controls remain authoritative.", "fail_closed": "Unavailable, oversized, quarantined, or unauthorized content is not parsed or previewed.", }, }, order=29, ), DocumentationTopic( id="files.postbox.exact-version-references", title="Resolve exact file versions for Postbox evidence", summary="Keep Files authorization authoritative when Postbox displays a typed file reference.", body=( "Files exposes an optional capability that resolves file-asset and file-version references for an already authorized Postbox message. " "The capability returns immutable version metadata and a version-specific download route only when the current principal has Files download " "permission and direct resource access. It does not turn Postbox access into a file share." ), layer="configured", documentation_types=("user", "admin"), audience=("administrator", "user", "campaign_manager"), related_modules=("postbox", "audit"), conditions=( DocumentationCondition( required_modules=("files",), any_scopes=("files:file:download", "files:file:admin"), ), ), links=( DocumentationLink( label="Files", href="/files", kind="runtime", ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), translations={ "de": { "title": "Exakte Dateiversionen für Postfachnachweise auflösen", "summary": "Die Files-Berechtigungsprüfung bleibt maßgeblich, wenn Postbox einen typisierten Dateiverweis anzeigt.", "body": ( "Files stellt eine optionale Capability bereit, die Datei- und Dateiversionsverweise einer bereits autorisierten Postfachnachricht auflöst. " "Sie liefert unveränderliche Versionsmetadaten und einen versionsgenauen Download nur, wenn die aktuelle Person die Files-Downloadberechtigung " "und direkten Ressourcenzugriff besitzt. Postfachzugriff wird nicht automatisch zu einer Dateifreigabe." ), } }, metadata={"kind": "reference", "help_contexts": ["files.postbox-reference"]}, order=30, ), DocumentationTopic( id="files.configuration-package.managed-storage", title="Validate deployment-managed file storage", summary="Compare Files runtime storage with the non-secret deployment receipt without rewriting infrastructure settings.", body=( "The Files configuration provider validates the files.storage capability against the effective local or S3 runtime. " "It checks the backend, sanitized endpoint, bucket, trust or management marker, durable local path, and presence of referenced environment secrets. " "Matching configuration is already effective and therefore reports skip on every apply. Drift blocks the package; Files never copies secret values, rewrites process environment, " "or treats a storage replacement as an implicit migration. Before files.storage changes, Files reports the active runtime binding plus persisted blob counts and byte totals per backend " "through the non-secret Ops dependency inventory. Missing, stale, or incomplete inventory blocks host apply. Run Files integrity and Ops checks before and after deployment changes." ), layer="configured", documentation_types=("admin",), audience=("file_manager", "administrator", "operator"), related_modules=("core", "ops"), conditions=( DocumentationCondition( required_modules=("files", "access"), any_scopes=("admin:settings:read", "system:settings:read"), ), ), links=( DocumentationLink( label="Configuration packages", href="/admin?section=configuration-packages", kind="runtime", ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), translations={ "de": { "title": "Bereitstellungsverwalteten Dateispeicher prüfen", "summary": "Den Laufzeitspeicher von Files mit dem nicht geheimen Bereitstellungsnachweis vergleichen, ohne Infrastruktureinstellungen umzuschreiben.", "body": ( "Der Files-Konfigurationsprovider prüft die Fähigkeit files.storage gegen die wirksame lokale oder S3-Laufzeitkonfiguration. " "Geprüft werden Backend, bereinigter Endpunkt, Bucket, Vertrauens- oder Verwaltungskennzeichen, dauerhafter lokaler Pfad sowie das Vorhandensein referenzierter Umgebungsgeheimnisse. " "Eine passende Konfiguration ist bereits wirksam und meldet deshalb bei jeder Anwendung skip. Abweichungen blockieren das Paket; Files kopiert keine Geheimwerte, verändert keine Prozessumgebung " "und behandelt einen Speicherwechsel nicht als stillschweigende Migration. Vor einer Änderung von files.storage meldet Files die aktive Laufzeitbindung sowie gespeicherte Blob-Anzahlen und Byte-Summen je Backend " "im nicht geheimen Ops-Abhängigkeitsinventar. Ein fehlendes, veraltetes oder unvollständiges Inventar blockiert die Host-Anwendung. Die Integritäts- und Ops-Prüfungen sind vor und nach der Bereitstellungsänderung auszuführen." ), } }, metadata={ "kind": "workflow", "route": "/admin?section=configuration-packages", "help_contexts": ["admin.configuration-packages", "files.admin.tenant-integrity"], }, order=4, ), DocumentationTopic( id="files.quick-access-and-product-area", title="Files in Records and documents and Quick Access", summary="Use managed files in the Records and documents area and keep a compact file surface beside current work.", body=( "Files contributes its authorized workspace to Records and documents. When Quick Access is enabled, the owner-rendered " "selector shows at most seven recently changed files and can return one exact file-version reference to the current task. " "Accounts with upload permission can launch the full Files upload dialog into a freshly reauthorized managed space. Opening " "either path causes Files to re-run its own provider, space, folder, object, and scope checks; completion and cancellation are " "explicit, and the full Files workspace remains available as a deep-link fallback. View and rail settings may recommend or " "focus this tool, but do not bypass file ownership, shares, connector policy, integrity gates, or purpose-aware access." ), layer="configured", documentation_types=("user", "admin"), audience=("file_user", "file_manager", "administrator"), related_modules=("quick_access", "views", "records"), conditions=( DocumentationCondition( required_modules=("files",), any_scopes=("files:file:read", "files:file:admin"), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), translations={ "de": { "title": "Dateien in Akten und Dokumente sowie im Schnellzugriff", "summary": "Verwaltete Dateien im Produktbereich Akten und Dokumente und optional neben der aktuellen Arbeit verwenden.", "body": ( "Files ordnet seinen berechtigten Arbeitsbereich Akten und Dokumente zu. Ist der Schnellzugriff aktiviert, erscheint " "die vom Modul gerenderte Auswahl von höchstens sieben zuletzt geänderten berechtigten Dateien rechts neben der aktuellen " "Seite und kann einen exakten Dateiversionsverweis zurückgeben. Mit Upload-Berechtigung lässt sich der vollständige " "Upload-Dialog in einem erneut berechtigungsgeprüften verwalteten Bereich öffnen. Files prüft Anbieter, Bereich, Ordner, " "Objekt und Berechtigung bei jedem Pfad erneut. Abschluss und Abbruch sind ausdrücklich; Eigentum, Freigaben, Connector-" "Richtlinien, Integritätsprüfungen und zweckgebundener Zugriff bleiben maßgeblich." ), } }, metadata={"kind": "reference", "help_contexts": ["files.quick_access.files"]}, order=39, ), DocumentationTopic( id="files.forms-runtime.managed-evidence", title="Capture Form attachments as managed evidence", summary="Issue short-lived upload grants and bind managed file versions to one exact Form submission.", body=( "When Forms Runtime requests document evidence, Files issues a purpose-bound grant for an active same-tenant user custodian. The bearer token is shown once, stored only as a SHA-256 digest, expires after at most 15 minutes, and can create one managed file version. Final Form submission rechecks the exact Form, grant, asset, version, checksum, deletion state, and Files integrity gate. Public intake therefore does not receive general Files permissions, and a document captured for one submission cannot be reused silently for another." ), layer="configured", documentation_types=("admin", "user"), audience=("form_participant", "form_admin", "file_manager", "auditor"), related_modules=("forms_runtime", "forms"), order=42, conditions=( DocumentationCondition( required_modules=("files", "forms_runtime"), any_scopes=( "forms_runtime:instance:participate", "forms_runtime:instance:write", "files:file:admin", ), ), ), links=( DocumentationLink(label="Forms", href="/forms-runtime", kind="runtime"), DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), metadata={ "kind": "workflow", "route": "/forms-runtime", "help_contexts": ["forms_runtime.instance", "files.upload"], "security_invariants": [ "Bearer upload tokens are never retained in plaintext.", "A grant is bound to one tenant, Form instance, definition revision, purpose, custodian, size limit, and optional media-type allowlist.", "Submission fails closed unless the exact managed version passes the Files integrity gate.", ], "limitations": [ "The first signature profile is an authenticated acknowledgement; advanced and qualified electronic signatures require a separate provider." ], "prerequisites": [ "Forms Runtime and Files are enabled, and the intake profile has an active same-tenant user custodian." ], "steps": [ "Request a Files evidence grant from the editable Form instance, upload through the returned one-time bearer route, then add the returned evidence reference to the draft." ], "outcome": "The draft references an immutable managed Files version with exact checksum evidence.", "verification": "Submit the Form and verify that altered, deleted, quarantined, cross-tenant, and cross-submission references are rejected.", }, ), DocumentationTopic( id="files.records.exact-version-source", title="File exact versions into an eAkte", summary="Let Records preserve an immutable Files version reference after current access and integrity checks.", body=( "When Records is enabled, Files exposes exact managed FileVersion identities through the " "provider-neutral record-source contract. Filing verifies the active tenant, current Files " "permission and owner/share access, the immutable version identity, and the managed blob " "integrity gate. Records receives the filename, path, version, digest, media type, size, " "protection state, and filing launch link; Files continues to own the bytes." ), layer="configured", documentation_types=("admin", "user"), audience=("file_user", "file_manager", "records_manager", "auditor"), related_modules=("records",), order=40, conditions=( DocumentationCondition( required_modules=("files", "records"), required_scopes=("files:file:read",), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink(label="Records", href="/records", kind="runtime"), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), metadata={ "kind": "workflow", "help_contexts": ["files.list", "records.action.file"], "prerequisites": [ "The exact file version exists and passes the current Files integrity gate.", "You currently have Files read access and Records filing authority.", ], "steps": [ "Select the exact managed file version that belongs to the institutional record.", "Choose the destination record and state the access purpose and filing reason.", "Confirm filing; Files rechecks current access and resolves the exact version.", "Open the record chronology and verify the version identity and SHA-256 digest.", ], "outcome": "Records preserves an exact governed reference while Files remains byte authority.", }, ), DocumentationTopic( id="files.search.managed-content", title="Search managed files and folders", summary="Expose file names, logical paths, and descriptions to permission-aware platform Search.", body=( "When Search is installed, Files contributes managed files and folders to its derived index. " "Every result is tenant-bounded and rechecks current ownership, group membership, direct shares, " "expiry, revocation, deletion, and Files permissions before it is returned. Committed file and " "share changes are delivered through the platform event outbox; an administrator can rebuild the " "derived index without changing authoritative Files data." ), layer="configured", documentation_types=("admin", "user"), audience=("file_user", "file_manager", "administrator"), related_modules=("search",), order=41, conditions=( DocumentationCondition( required_modules=("files",), any_scopes=("files:file:read", "files:file:admin"), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink(label="Search", href="/search", kind="runtime"), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), metadata={ "kind": "reference", "route": "/files", "screen": "Files search contribution", "help_contexts": ["files.list"], }, ), DocumentationTopic( id="files.workflow.organize-managed-files", title="Organize managed files and folders", summary="Create folders and rename, move, or copy accessible managed content with explicit conflict handling.", body=( "Organization stays inside governed personal or group spaces. Moves preserve the asset identity, while copies create new assets and versions that reuse immutable blob bytes. " "Every target conflict must be rejected, renamed, overwritten, or skipped explicitly." ), layer="configured", documentation_types=("user",), audience=("file_user", "file_manager", "process_participant"), order=42, conditions=( DocumentationCondition( required_modules=("files",), required_scopes=("files:file:read", "files:file:organize"), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink( label="Move or copy API", href="/api/v1/files/transfer", kind="api" ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), unlocks=( "Managed content can be placed at stable logical paths without bypassing space access.", ), metadata={ "kind": "workflow", "route": "/files", "screen": "Files", "help_contexts": ["files.list"], "prerequisites": [ "You may view and organize managed files.", "You have write or owner access to every source item and destination space used by the operation; the global organize permission alone does not grant resource access.", ], "steps": [ "Open Files and select the personal or group space to organize.", "Create the required destination folders or select the files and folders to rename, move, or copy.", "Choose the destination and resolve each target conflict explicitly.", "Apply the operation and reopen the destination folder.", ], "outcome": "The selected content has the intended governed owner and logical path.", "verification": "Confirm each resulting path and owner; for a move, also confirm the old path is gone, and for a copy, confirm the source remains.", "related_topic_ids": [ "files.workflow.upload-managed-files", "files.workflow.find-and-download-files", "files.workflow.delete-managed-files", ], }, ), DocumentationTopic( id="files.workflow.find-and-download-files", title="Find and download managed files", summary="Search accessible managed content and download a current file version or a ZIP archive of a selection.", body=( "Files can be sorted and searched by logical path or name pattern. A download always uses the accessible current version; a multi-file selection can be generated as a temporary ZIP archive. " "There is no dedicated content-preview service yet." ), layer="configured", documentation_types=("user",), audience=("file_user", "file_manager", "process_participant"), order=43, conditions=( DocumentationCondition( required_modules=("files",), required_scopes=("files:file:read", "files:file:download"), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), unlocks=( "Authorized users can retrieve governed current versions without gaining organization or sharing authority.", ), metadata={ "kind": "workflow", "route": "/files", "screen": "Files", "help_contexts": ["files.list"], "prerequisites": [ "You may view and download managed files in the relevant space." ], "steps": [ "Open Files and select the relevant personal or group space.", "Navigate folders, sort the list, or use a path/name pattern to find the intended content.", "Review the displayed owner, path, size, checksum, and version details.", "Download one current file version, or select several files and choose Download ZIP.", ], "outcome": "The authorized current file bytes or generated archive are downloaded to the local device.", "verification": "Confirm the downloaded names and, where integrity matters, compare the file bytes with the displayed checksum.", "related_topic_ids": [ "files.workflow.organize-managed-files", "files.reference.snapshot-provenance-and-capabilities", ], }, ), DocumentationTopic( id="files.workflow.share-managed-files", title="Manage access to managed files", summary="List, grant, update, expire, or revoke direct read, write, and manage access without changing ownership.", body=( "File owners and file administrators can manage direct shares for users, groups, the tenant, and Campaign. Expired and revoked grants stop authorizing access immediately while independent active grants remain effective. " "The Files share dialog lists active and historical grants, and revocation is idempotent. The access explanation action evaluates the signed-in user by default. When Policy permits selected-user diagnostics, the same shared dialog can evaluate another active user in the current tenant; those cross-user diagnostics are recorded in audit evidence and do not grant access." ), layer="available", documentation_types=("user",), audience=("file_manager", "process_participant"), order=44, conditions=( DocumentationCondition( required_modules=("files",), required_scopes=("files:file:read", "files:file:share"), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink( label="List and manage shares", href="/api/v1/files/{file_id}/shares", kind="api", ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), related_modules=("access", "audit", "campaigns", "policy"), unlocks=( "A supporting process can grant governed file access without changing file ownership.", ), metadata={ "kind": "workflow", "route": "/files", "screen": "Files", "help_contexts": ["files.list"], "prerequisites": [ "You have the Files share permission and own the file, or administer file spaces for the active tenant.", "The intended user, group, tenant, or Campaign target exists and is active.", ], "steps": [ "Select one managed file, choose Manage shares, and review the effective and historical direct grants.", "Select the intended user, group, or tenant and choose read, write, or manage access plus an optional expiry.", "Grant or update the share without changing file ownership.", "Revoke a grant when it is no longer needed; repeated revocation is a no-op.", ], "limitations": [ "Campaign-target grants are normally created by the Campaign integration rather than selected manually in the Files dialog.", "A user may retain access through another active direct grant or ownership path after one share is revoked.", "Selected-user explanations require Policy permission and are diagnostics only; they do not impersonate the user or grant file access.", ], "outcome": "Direct access has the requested permission and lifetime while the managed asset keeps its owner.", "verification": "Test one intended and one denied path, then expire or revoke the grant and verify that only independent access paths remain.", "related_topic_ids": [ "files.workflow.find-and-download-files", "files.assurance.process-and-release-readiness", ], }, ), DocumentationTopic( id="files.workflow.delete-managed-files", title="Delete managed files and folders", summary="Soft-delete accessible managed files or a folder tree where current policy allows it.", body=( "Deletion hides the selected managed assets rather than hard-purging their stored evidence. Folder deletion is recursive by default and includes child folders and files; a non-recursive request fails for a non-empty folder. " "Authorized restoration reactivates the same asset, versions, provenance, folder tree, or connector-space link when its path or label is still free. Irreversible purge is a separate administrator workflow with retention and legal-hold checks." ), layer="configured", documentation_types=("user",), audience=("file_user", "file_manager", "process_participant"), order=45, conditions=( DocumentationCondition( required_modules=("files",), required_scopes=("files:file:read", "files:file:delete"), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), unlocks=( "Authorized users can remove obsolete content from active file views while preserving the current soft-delete boundary.", ), metadata={ "kind": "workflow", "route": "/files", "screen": "Files", "help_contexts": ["files.list"], "prerequisites": [ "You may view and delete the selected managed content and have write or owner access to every affected asset or folder.", "You have reviewed the complete folder tree when deleting recursively.", ], "steps": [ "Open Files and select the files or folder to delete.", "Review the selection and, for a folder, all content below it.", "Confirm the delete action.", "Refresh or reopen the space and verify that the selected paths are no longer active.", ], "limitations": [ "Deletion is soft deletion, not a hard purge.", "Restore fails closed when an active file, folder, or connector space already uses the path or label.", ], "outcome": "The selected content is hidden from active Files views under the current soft-delete model.", "verification": "Confirm the deleted paths no longer appear in the active space; do not treat the action as physical erasure.", "related_topic_ids": [ "files.workflow.organize-managed-files", "files.assurance.process-and-release-readiness", ], }, ), DocumentationTopic( id="files.workflow.restore-retain-and-purge", title="Restore, retain, hold, and irreversibly purge managed files", summary="Restore soft-deleted resources and govern irreversible erasure with retention, legal-hold, preview, approval, and recovery evidence.", body=( "Restore keeps the original asset identity, versions, blob references, and connector provenance. A retention administrator can set an explicit retained-until time or legal hold with a reason and revision check. Hard purge requires the dedicated purge permission, a current immutable preview hash, a caller idempotency key, the literal PURGE confirmation, and an approval reference. Active retention, legal hold, active shares, Campaign evidence, and Form evidence block purge. Purge removes eligible database records first and releases blobs; a separate bounded garbage-collection action rechecks every FileVersion reference under the same distributed blob fence used by uploads before deleting bytes and metadata. Every irreversible action is visible through the Core recovery ledger and audit evidence." ), layer="configured", documentation_types=("admin", "user"), audience=("file_manager", "file_admin", "records_manager", "operator"), order=46, conditions=( DocumentationCondition( required_modules=("files",), any_scopes=( "files:file:restore", "files:file:retention", "files:file:purge", ), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink( label="Purge preview API", href="/api/v1/files/purge/preview", kind="api", ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), related_modules=("audit", "ops", "campaigns", "forms-runtime"), unlocks=( "Authorized operators can distinguish reversible removal from policy-governed physical erasure.", ), metadata={ "kind": "workflow", "route": "/files", "screen": "Files lifecycle", "help_contexts": ["files.list"], "prerequisites": [ "The actor has the distinct restore, retention, or purge permission required by the intended action.", "A purge target is already soft-deleted and all dependent evidence or shares have been lawfully resolved.", ], "steps": [ "Set or review retention and legal-hold state using the displayed lifecycle revision.", "For restoration, verify the original path or label is free and restore the same resource.", "For erasure, create a bounded purge preview and resolve every named blocker.", "Execute the matching preview with an idempotency key, PURGE confirmation, and approval reference.", "Run bounded blob garbage collection and inspect any recovery-required or outcome-unknown operation in Ops.", ], "limitations": [ "Automatic time-based purge scheduling is not included; an authorized actor starts preview, execute, and blob collection.", "A protected blob's Encryption envelope follows the Encryption module's own retention and key-custody policy.", ], "outcome": "Eligible metadata and unreferenced bytes are erased with separately reviewable policy, audit, and recovery evidence.", "verification": "Verify the asset/version rows are absent, the blob remains while referenced, unreferenced bytes are absent after GC, and every operation has a valid recovery evidence chain.", "related_topic_ids": [ "files.workflow.delete-managed-files", "files.reference.integrity-recovery-and-fail-closed-transports", ], }, ), DocumentationTopic( id="files.privacy.data-subject-requests", title="Review Files data in a data-subject request", summary="Collect safe Files metadata and keep retention, evidence, and byte-erasure decisions explicit.", body=( "The Files DSAR provider searches only the effective tenant and requires a direct membership or namespaced Files user reference. " "It exports bounded file, version, folder, sharing, evidence, connector-configuration, and integrity metadata without raw file bytes, storage locations, tokens, passwords, secret references, or encrypted credential values. " "Plans may revoke an active share aimed at the subject or detach a mutable actor reference. Legal hold, active retention, Form evidence, Campaign delivery evidence, configuration history, and integrity evidence remain retained with a reason. File content, ownership, names, and paths require manual review. Approved physical erasure must use the separately authorized Files purge and blob-garbage-collection workflow so DSAR execution cannot bypass evidence blockers, approval, audit, or recovery controls." ), layer="configured", documentation_types=("admin",), audience=("privacy_officer", "file_admin", "records_manager", "operator"), order=47, conditions=( DocumentationCondition( required_modules=("files", "access"), any_scopes=( "access:privacy:read", "access:privacy:manage", "access:privacy:erase", ), ), ), links=( DocumentationLink( label="Data-subject requests", href="/admin?section=tenant-data-subject-requests", kind="runtime", ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), related_modules=("access", "audit", "campaigns", "forms-runtime", "ops"), metadata={ "kind": "workflow", "route": "/admin?section=tenant-data-subject-requests", "screen": "Data-subject requests", "help_contexts": ["admin.privacy.data-subject-requests"], "prerequisites": [ "The request has been authorized and contains a direct tenant membership or Files subject reference.", "The privacy reviewer can distinguish access export from erasure authority and Files purge authority.", ], "steps": [ "Run the provider search and confirm Files reports complete coverage rather than a failed or absent provider.", "Review file/version metadata, evidence retention reasons, and the source path for manual content review.", "Generate the erasure plan and execute only the approved reversible share-revocation or subject-reference actions.", "For approved byte erasure, resolve every lifecycle blocker and use Files purge preview, execution, and blob garbage collection separately.", ], "limitations": [ "Email, account, or identity selectors alone cannot be resolved by Files because Files does not own the Access directory; supply the corroborated membership reference.", "The provider does not embed raw file content in the JSON export and never performs physical blob deletion as a DSAR side effect.", ], "outcome": "Files-owned subject references are reviewed or removed without silently destroying retained content or evidence.", "verification": "Confirm every Files record has a retain, review, revoke, or detach disposition and inspect any separate purge through its audit and recovery evidence.", "related_topic_ids": [ "files.workflow.restore-retain-and-purge", "files.reference.integrity-recovery-and-fail-closed-transports", ], }, ), DocumentationTopic( id="files.governed-connectors-and-provenance", title="Govern file connections, folder sync, and credential deletion", summary="Keep endpoints, credentials, inherited policy, and bounded manual synchronization separate, with reviewable outcomes and provenance.", body=( "System, tenant, and one user/group/campaign leaf form the effective policy chain: deny rules win and every configured allow rule must match. " "Responses redact secret values and deployment references. Deleting a database-managed credential or profile immediately scrubs Files-owned encrypted material and private metadata in the same transaction as a non-secret audit event; dependent profiles are disabled, while legacy non-owned references are only detached and audited. " "Removing a connector space is a separate owner-authorized operation: it retires only the local virtual-space link and leaves provider content, imported managed files and shares, profiles, credentials, and remote references untouched. Intrinsic user and group managed spaces cannot be removed. " "A manual connector-folder sync traverses provider pagination with explicit file-count and depth bounds, preserves remote relative paths in the linked owner's managed space, and evaluates policy for the root and every downloaded item. Matching source identities produce unchanged or version-appending updates; new identities create managed assets. Unrelated target collisions default to skip, while rename, reject, and overwrite are explicit choices. Per-file savepoints keep successful siblings while conflicts, skips, policy denials, and transport failures remain visible in the response. The files.connector.folder_synced audit event records provenance, typed counts, truncation, and bounded result references without content or credentials. Scheduling remains separate. " "Connector spaces remain read-only by default. An administrator may explicitly enable two-way mode only for an S3 profile carrying the write capability; each write requires separate authority, inherited path policy, a conditional create or expected revision, and durable recovery evidence. Automatic remote deletion, rename, and move propagation remain disabled." ), layer="configured", documentation_types=("admin",), audience=("file_admin", "tenant_admin", "system_admin", "security_auditor"), order=50, conditions=( DocumentationCondition( required_modules=("files",), any_scopes=( "files:file:admin", "admin:settings:read", "admin:settings:write", "system:settings:read", "system:settings:write", ), ), ), links=( DocumentationLink( label="Tenant connector administration", href="/admin?section=tenant-file-connectors", kind="runtime", ), DocumentationLink( label="System connector administration", href="/admin?section=system-file-connectors", kind="runtime", ), DocumentationLink( label="Tenant connector policy API", href="/api/v1/files/connectors/policies/tenant", kind="api", ), DocumentationLink( label="Connector credentials API", href="/api/v1/files/connectors/credentials", kind="api", ), DocumentationLink( label="Manual connector-folder sync API", href="/api/v1/files/connector-spaces/{space_id}/sync", kind="api", ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), translations={ "de": { "title": "Dateiverbindungen, Ordnersynchronisierung und das Löschen von Zugangsdaten steuern", "summary": "Endpunkte, Zugangsdaten, vererbte Richtlinien und begrenzte manuelle Synchronisierung getrennt und mit prüfbaren Ergebnissen sowie Herkunftsnachweisen verwalten.", "body": ( "System, Mandant und genau eine Benutzer-, Gruppen- oder Kampagnenebene bilden die wirksame Richtlinienkette: Ablehnungsregeln haben Vorrang und jede konfigurierte Erlaubnisregel muss zutreffen. " "Antworten blenden Geheimwerte und Bereitstellungsverweise aus. Beim Löschen datenbankverwalteter Zugangsdaten oder Profile entfernt Files eigenes verschlüsseltes Material und private Metadaten in derselben Transaktion wie das nicht geheime Audit-Ereignis. Abhängige Profile werden deaktiviert; ältere, nicht Files gehörende Verweise werden nur getrennt und auditiert. " "Das Entfernen eines Connector-Bereichs ist ein eigener, eigentümerberechtigter Vorgang: Nur die lokale Verknüpfung des virtuellen Bereichs wird außer Kraft gesetzt. Inhalte beim Anbieter, importierte verwaltete Dateien und Freigaben, Profile, Zugangsdaten und Remote-Verweise bleiben erhalten. Intrinsische Benutzer- und Gruppenbereiche können nicht entfernt werden. " "Eine manuelle Connector-Ordnersynchronisierung durchläuft die Anbieter-Paginierung innerhalb ausdrücklicher Grenzen für Dateizahl und Tiefe, erhält relative Remote-Pfade im verwalteten Bereich des verknüpften Eigentümers und prüft die Richtlinie für die Wurzel sowie jede heruntergeladene Datei. Übereinstimmende Quellidentitäten bleiben unverändert oder erhalten eine neue Version; neue Identitäten erzeugen verwaltete Dateien. Nicht zugehörige Zielkonflikte werden standardmäßig übersprungen; Umbenennen, Ablehnen und Ersetzen sind ausdrückliche Entscheidungen. Savepoints je Datei bewahren erfolgreiche Geschwister, während Konflikte, Überspringungen, Richtlinienablehnungen und Transportfehler in der Antwort prüfbar bleiben. Das Audit-Ereignis files.connector.folder_synced hält Herkunft, typisierte Zähler, Abbruch am Grenzwert und begrenzte Ergebnisverweise ohne Inhalte oder Zugangsdaten fest. Zeitplanung bleibt getrennt. " "Connector-Bereiche bleiben standardmäßig schreibgeschützt. Eine Administration kann den Zweiwege-Modus nur für ein S3-Profil mit Schreib-Capability ausdrücklich aktivieren. Jeder Schreibvorgang benötigt eigene Berechtigung, die vererbte Pfadrichtlinie, eine bedingte Neuanlage oder erwartete Revision sowie dauerhafte Wiederherstellungsnachweise. Automatisches Löschen, Umbenennen und Verschieben auf dem Remote-System bleibt deaktiviert." ), } }, related_modules=("access", "audit", "mail"), unlocks=( "Scoped, explainable external-file access without exposing credentials to consuming modules.", ), configuration_keys=( "GOVOPLAN_FILES_CONNECTOR_PROFILES_JSON", "GOVOPLAN_FILES_CONNECTOR_PROFILES_FILE", "GOVOPLAN_CONNECTOR_SECRET_ENV_ALLOWLIST", "GOVOPLAN_CONNECTOR_CA_BUNDLE_ALLOWLIST", "MASTER_KEY_B64", ), metadata={ "kind": "reference", "route": "/admin?section=tenant-file-connectors", "screen": "File connections", "section": "Profiles, credentials, and effective connector policy", "help_contexts": [ "files.connectors", "files.connector.credentials", "files.connector.policy", "files.connector-folder-sync.remote-path", "files.connector-folder-sync.target-folder", "files.connector-folder-sync.conflict-strategy", "files.connector-folder-sync.max-files", "files.connector-folder-sync.recursive", ], "prerequisites": [ "A visible active connector space uses manual sync and links to an accessible managed user or group space.", "The operator has files:file:upload and the effective connector policy permits the requested root and source paths.", ], "steps": [ "Open the linked connector folder, choose Sync folder, and review the managed destination.", "Keep the safe skip default or explicitly choose rename, reject, or overwrite for unrelated target collisions.", "Set the bounded file count and subfolder choice, run the sync, and review every typed item outcome and any truncation warning.", "Continue a truncated run from a narrower remote folder; configure scheduling separately if background operation is required.", ], "limitations": [ "Manual folder sync does not schedule future runs or mutate, delete, rename, move, or change ACLs on remote content.", "A successful partial run is not complete coverage when the response is truncated or contains review outcomes.", ], "outcome": "Permitted connector files become governed managed assets or versions while every exception remains explicit and auditable.", "verification": "Compare the response summary and per-item outcomes with the files.connector.folder_synced audit event, then inspect provenance on representative created, updated, and unchanged assets.", "security_invariants": [ "New API-managed external secret references fail closed until Files can prove ownership and provider-side deletion.", "Deletion and destructive retirement scrub Files-owned encrypted connector material before completion and emit non-secret audit evidence.", "Legacy non-owned external references are detached and audited, never sent to an arbitrary provider delete operation.", "Connector-space removal is local and soft; it never claims to delete remote or previously imported managed content.", "Folder sync is bounded, path-contained, policy-checked per item, and never includes remote bytes or credentials in audit details.", "Two-way connector mode is explicit and S3-only; it never enables automatic remote delete, rename, move, or ACL propagation.", ], "related_topic_ids": [ "files.workflow.import-managed-snapshot", "files.reference.integrity-recovery-and-fail-closed-transports", "mail.profiles-and-policy", ], }, ), DocumentationTopic( id="files.reference.integrity-recovery-and-fail-closed-transports", title="Operate Files integrity, recovery, and connector transport safety", summary="Back up database evidence, blob ciphertext, and Encryption custody as one recovery unit, and pin every SDK-managed connector peer.", body=( "Local durable storage is the operational baseline. Recover Files from a coordinated database/blob snapshot with the matching Encryption tables and original deployment master key, then run the bounded resumable integrity scan from Administration and verify representative protected and unprotected access paths. Each scan batch and finding action requires the revision shown to the operator, so a stale screen cannot recheck or delete after concurrent reconciliation. Protected scans verify stored ciphertext before decryption and then verify plaintext semantic evidence. On PostgreSQL, managed blob creation/repair and applied orphan cleanup commit lease-fenced Core recovery intent before object effects; success, compensation, and forward completion require independent database and object checks, while mismatch is quarantined and unresolved work remains visible in Ops. Development SQLite instead records blob intent in the caller transaction to avoid a second-writer deadlock, uses a process-local fence, verifies after commit, and reconstructs durable compensation evidence after handled rollback. A hard process loss before the SQLite caller commits can therefore leave an unrecorded object; SQLite is not a production recovery profile and operators must run an integrity scan after such a loss. Missing or mismatched blobs are quarantined; orphan objects are reported before dry-run-first, explicitly authorized cleanup. " "Hard purge records irreversible intent before deleting database evidence, and blob garbage collection separately rechecks references under the shared blob lease before deleting a managed object. S3 connector write-back records digest-only forward-recovery intent before a conditional provider effect; request and content markers prove success, while mismatches remain visible in Ops and fence later writers. S3 connector pools pin every retry, redirect, discovered endpoint, and provider alias while retaining the configured TLS authority; outbound proxies and ambient credential discovery are disabled. SMB initial connections, reconnects, aliases, and DFS referrals use a Files-owned pinned transport and cache. Both apply the deployment private-network policy immediately before each socket opens and fail closed if an SDK no longer exposes the verified transport seam. Installer-owned Garage storage is supported only at the exact deployment service endpoint with its explicit trust marker. Destructive module retirement drops database tables but does not remove backend blob objects." ), layer="configured", documentation_types=("admin",), audience=("operator", "system_admin", "security_auditor"), order=51, conditions=( DocumentationCondition( required_modules=("files",), any_scopes=( "files:file:admin", "admin:settings:read", "system:settings:read", "system:audit:read", ), ), ), links=( DocumentationLink( label="System file connections", href="/admin?section=system-file-connectors", kind="runtime", ), DocumentationLink( label="File integrity operations", href="/admin?section=tenant-file-integrity", kind="runtime", ), DocumentationLink( label="Connector provider status", href="/api/v1/files/connectors/providers", kind="api", ), DocumentationLink( label="Create an integrity scan", href="/api/v1/files/integrity/scans", kind="api", ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), related_modules=("audit", "encryption", "ops"), unlocks=( "Recoverable managed-file evidence without weakening outbound peer validation.", ), configuration_keys=( "FILE_STORAGE_BACKEND", "FILE_STORAGE_LOCAL_ROOT", "FILE_STORAGE_LOCAL_FALLBACK_ROOTS", "FILE_STORAGE_S3_DEPLOYMENT_MANAGED", "FILE_UPLOAD_MAX_BYTES", "FILE_UPLOAD_ZIP_MAX_BYTES", "FILE_ARCHIVE_MAX_ENTRIES", "FILE_ARCHIVE_MAX_EXPANDED_BYTES", "FILE_ARCHIVE_MAX_EXPANSION_RATIO", "FILE_ARCHIVE_PREVIEW_TTL_SECONDS", "MASTER_KEY_B64", "GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS", "GOVOPLAN_CONNECTOR_MAX_STRUCTURED_RESPONSE_BYTES", "GOVOPLAN_CONNECTOR_MAX_FILE_TRANSFER_BYTES", ), metadata={ "kind": "reference", "route": "/admin?section=system-file-connectors", "screen": "System file connections and deployment operations", "section": "Storage integrity, backup/recovery, and fail-closed transports", "recovery_unit": [ "Files database rows", "Encryption envelope and wrapped-key rows", "managed blob namespace", "MASTER_KEY_B64", "deployment-owned connector configuration", ], "verification": "After restore, complete a checksum-enabled integrity scan, resolve every missing/corrupt finding, approve or retain every reported orphan, inspect Files recovery operations in Ops, verify authorized and denied access, and test configured pinned HTTP, S3, and SMB connectors against their recorded target topology.", "related_topic_ids": [ "files.governed-connectors-and-provenance", "files.reference.snapshot-provenance-and-capabilities", ], }, ), DocumentationTopic( id="files.reference.snapshot-provenance-and-capabilities", title="Integrate through managed snapshots and Files capabilities", summary="Other modules consume stable Files capabilities or HTTP contracts and retain exact version evidence instead of importing Files internals.", body=( "Use files.access to explain resource access and files.campaign_attachments to freeze campaign inputs at an exact asset, version, blob, checksum, and source revision. " "Import external content before a governed use, preserve provenance on derived snapshots, and keep collaboration, provider sync, OAuth, remote mutation, and domain workflow state in their owning modules." ), layer="available", documentation_types=("admin", "user"), audience=( "module_integrator", "file_admin", "campaign_admin", "process_designer", ), order=52, conditions=( DocumentationCondition( required_modules=("files",), any_scopes=( "files:file:read", "files:file:upload", "files:file:admin", ), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink(label="Files API", href="/api/v1/files", kind="api"), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), related_modules=("campaigns", "docs"), unlocks=( "Campaign, report, template, workflow, and document modules can exchange governed input/output snapshots.", ), metadata={ "kind": "reference", "route": "/files", "screen": "Files and module integration", "section": "Managed snapshot provenance and capability boundaries", "provided_interfaces": [ "files.access@0.1.6", "files.campaign_attachments@0.1.6", "forms_runtime.evidence.files@1.0.0", ], "provenance_fields": [ "connector_id", "provider", "external_id", "external_path", "revision", "metadata", ], "related_topic_ids": [ "files.workflow.import-managed-snapshot", "files.governed-connectors-and-provenance", "files.reference.integrity-recovery-and-fail-closed-transports", ], }, ), DocumentationTopic( id="files.assurance.process-and-release-readiness", title="Assure a Files-backed process and release", summary="Check a process against the implemented Files boundary, exercise permitted and denied paths, and retain evidence before approving a release or operational use.", body=( "A process owner must distinguish implemented controls from planned capabilities before relying on Files. A release is not ready until all package and manifest versions align and representative authorization, upload limits, conflict handling, download, deletion, connector, and recovery paths have been exercised. " "Restore, retention, legal hold, governed hard purge, blob garbage collection, share lifecycle, and explicit S3 write-back now emit dedicated policy, recovery, or audit evidence. Ordinary organization mutations still rely primarily on the Files change sequence rather than a dedicated canonical audit event. Record remaining limitations in the process assessment instead of treating one evidence stream as another." ), layer="configured", documentation_types=("admin", "user"), audience=( "process_owner", "release_manager", "file_admin", "operator", "security_auditor", ), order=53, conditions=( DocumentationCondition( required_modules=("files",), any_scopes=( "files:file:read", "files:file:admin", "admin:settings:read", "system:settings:read", "system:audit:read", ), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink(label="Files API", href="/api/v1/files", kind="api"), DocumentationLink( label="Connector provider status", href="/api/v1/files/connectors/providers", kind="api", ), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), related_modules=("campaigns", "audit", "ops"), unlocks=( "Process owners and release managers can approve Files use against explicit controls, evidence, and known gaps.", ), configuration_keys=( "FILE_STORAGE_BACKEND", "FILE_STORAGE_LOCAL_ROOT", "FILE_UPLOAD_MAX_BYTES", "FILE_UPLOAD_ZIP_MAX_BYTES", "FILE_ARCHIVE_MAX_ENTRIES", "FILE_ARCHIVE_MAX_EXPANDED_BYTES", "FILE_ARCHIVE_MAX_EXPANSION_RATIO", "FILE_ARCHIVE_PREVIEW_TTL_SECONDS", "MASTER_KEY_B64", "GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS", ), metadata={ "kind": "workflow", "route": "/files", "screen": "Files process and release assurance", "help_contexts": ["files.list"], "prerequisites": [ "A named process owner has defined the intended users, data classification, retention expectations, and integrations.", "A candidate release is installed on a clean database and an upgrade copy with aligned Python, root package, WebUI package, and module-manifest versions.", "Representative permitted and denied accounts, bounded test files, and a coordinated database/blob/key recovery set are available.", ], "steps": [ "Compare the process requirements with the handbook's implemented/planned boundary and record every unsupported requirement or compensating control.", "Confirm version alignment, apply migrations on clean and upgrade databases, and run the repository plus meta-repository security and static-analysis gates.", "Exercise allowed and denied personal, group, and share access with representative accounts.", "Exercise bounded archive preview and confirmation, every required conflict strategy, organization, download, and soft deletion.", "Where connectors are configured, verify policy explanation and one pinned HTTP provider; verify installer-owned Garage when selected, then exercise configured S3 retries/aliases and SMB reconnect/referral targets under the deployment private-network policy, confirming an incompatible SDK transport seam fails closed.", "Restore a coordinated database/blob/key backup and compare representative downloaded bytes with their recorded SHA-256 checksums.", "Record the tested versions, results, known limitations, evidence locations, residual risks, owner, and approval decision.", ], "outcome": "The process or release has an explicit approval record tied to aligned versions, representative evidence, known limitations, and owned residual risks.", "verification": "A reviewer can reproduce the recorded allow/deny, integrity, connector, and recovery checks and can trace each unmet requirement to a documented limitation or accepted compensating control.", "related_topic_ids": [ "files.workflow.upload-managed-files", "files.workflow.upload-and-unpack-zip", "files.workflow.organize-managed-files", "files.workflow.find-and-download-files", "files.workflow.share-managed-files", "files.workflow.delete-managed-files", "files.workflow.import-managed-snapshot", "files.governed-connectors-and-provenance", "files.reference.integrity-recovery-and-fail-closed-transports", "files.reference.snapshot-provenance-and-capabilities", ], }, ), DocumentationTopic( id="files.reference.shared-storage-profile", title="Operate Files with shared object storage", summary="Choose local, host-shared, or S3-backed storage consistently with the runtime topology.", body="Core supplies the common local/S3 object-storage backend while Files owns file metadata and object-key semantics. Local storage is valid for one runtime process; a shared host volume supports same-host replicas; independent hosts require an explicitly trusted HTTPS S3-compatible endpoint. Restore PostgreSQL, objects, and the master key to one coordinated recovery point.", layer="configured", documentation_types=("admin",), audience=("file_admin", "operator", "system_admin"), order=54, conditions=( DocumentationCondition( required_modules=("files",), any_scopes=("files:file:admin", "system:settings:read"), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), ), related_modules=("ops", "campaigns"), configuration_keys=( "GOVOPLAN_STATE_PROFILE", "GOVOPLAN_INSTALLATION_ID", "FILE_STORAGE_BACKEND", "FILE_STORAGE_LOCAL_ROOT", "FILE_STORAGE_S3_ENDPOINT_URL", "FILE_STORAGE_S3_ENDPOINT_TRUSTED", "FILE_STORAGE_S3_DEPLOYMENT_MANAGED", ), metadata={ "kind": "reference", "route": "/ops", "screen": "Storage and runtime posture", "limitations": [ "Installer-managed Garage is single-node unless an external multi-node cluster is operated separately.", "The application does not create or verify production PostgreSQL/object/key backups.", ], "verification": "Run the Files storage round-trip check and a coordinated restore drill against the exact deployment topology.", }, ), DocumentationTopic( id="files.reference.generated-artifact-store", title="Store generated module artifacts", summary="Let optional producer modules persist generated output through the Files authority boundary.", body="The files.artifact_store capability accepts generated bytes plus bounded non-secret provenance, applies Files upload authorization, ownership, path, version, and blob-storage rules, and returns provider-neutral file/version references. Idempotency uses source provenance. Artifact acceptance does not prove printing, mailing, or another external effect.", layer="available", documentation_types=("admin", "user"), audience=("file_admin", "operator", "module_admin", "integrator"), order=55, conditions=( DocumentationCondition( required_modules=("files",), any_scopes=("files:file:upload", "files:file:admin"), ), ), links=( DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink( label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository", ), DocumentationLink( label="Generated artifact contract", href="govoplan-files/docs/GENERATED_ARTIFACT_STORE.md", kind="repository", ), ), related_modules=("templates", "campaigns", "reporting"), metadata={"kind": "reference", "route": "/files"}, ), )), documentation_providers=(documentation_topics,), migration_spec=MigrationSpec( module_id="files", metadata=Base.metadata, script_location=str(Path(__file__).with_name("migrations") / "versions"), retirement_supported=True, retirement_provider=_files_retirement_provider, retirement_notes="Destructive retirement drops files-owned database tables after the installer captures a database snapshot.", ), uninstall_guard_providers=( persistent_table_uninstall_guard( file_models.FileBlob, file_models.FileFolder, file_models.FileAsset, file_models.FileVersion, file_models.FileFormEvidenceGrant, file_models.FileShare, file_models.FileConnectorCredential, file_models.FileConnectorPolicy, file_models.FileConnectorProfile, file_models.FileConnectorSpace, file_models.CampaignAttachmentUse, label="Files", ), ), capability_factories={ FILES_CONFIGURATION_CAPABILITY: _configuration_provider, FILES_INFRASTRUCTURE_DEPENDENCY_CAPABILITY: _configuration_provider, CAPABILITY_FILES_ACCESS: lambda context: __import__( "govoplan_files.backend.capabilities", fromlist=["access_capability"] ).access_capability(context), CAPABILITY_FILES_ARTIFACT_STORE: lambda context: __import__( "govoplan_files.backend.capabilities", fromlist=["artifact_store_capability"], ).artifact_store_capability(context), CAPABILITY_FILES_POSTBOX_REFERENCES: lambda context: __import__( "govoplan_files.backend.capabilities", fromlist=["postbox_reference_capability"], ).postbox_reference_capability(context), CAPABILITY_FILES_TABULAR_CONTENT: lambda context: __import__( "govoplan_files.backend.capabilities", fromlist=["managed_tabular_file_capability"], ).managed_tabular_file_capability(context), "files.campaign_attachments": lambda context: __import__( "govoplan_files.backend.capabilities", fromlist=["campaign_capability"] ).campaign_capability(context), CAPABILITY_RECORD_SOURCE_FILES: create_files_record_source, CAPABILITY_FORM_EVIDENCE_FILES: create_files_form_evidence_provider, FILES_DSAR_CAPABILITY: _dsar_provider, }, capability_documentation={ CAPABILITY_FILES_TABULAR_CONTENT: CapabilityDocumentation( label="Managed tabular file content", summary="Lists and opens authorized exact CSV/XLSX versions with Files integrity and access controls.", contract_version="1.0.0", ), CAPABILITY_RECORD_SOURCE_FILES: CapabilityDocumentation( label="Files record source", summary="Resolves currently authorized immutable managed file versions for Records filing.", contract_version="1.0.0", ), CAPABILITY_FORM_EVIDENCE_FILES: CapabilityDocumentation( label="Files Form evidence provider", summary="Issues one-time managed attachment grants and verifies exact Form evidence versions.", contract_version="1.0.0", ), FILES_DSAR_CAPABILITY: CapabilityDocumentation( label="Files data-subject request provider", summary="Finds safe Files metadata and classifies reversible references, manual file review, and retained evidence.", contract_version="0.1.0", ), }, operational_check_providers=( OperationalCheckProviderRegistration( module_id="files", check_id="files.managed_storage_roundtrip", provider=lambda: __import__( "govoplan_files.backend.operational_checks", fromlist=["managed_storage_roundtrip_check"], ).managed_storage_roundtrip_check(), cache_seconds=60, ), ), external_providers=(REMOTE_STORAGE_PROVIDER,), external_provider_state_providers=( ExternalProviderStateProviderRegistration( module_id="files", provider_id=REMOTE_STORAGE_PROVIDER_ID, provider=remote_storage_provider_states, ), ), architecture=declared_module_architecture( layer="content_records_evidence", kind="domain", maturity="vertical_slice", documentation_ref="docs/FILES_HANDBOOK.md", test_ref="tests/test_storage_backends.py", known_limits=( "Target-environment multi-node recovery drills remain deployment evidence; two-way connector writes are currently limited to explicit conditional S3 writes, with automatic remote delete, rename, move, and ACL propagation disabled.", ), supported_authority_modes=( "native_authoritative", "external_authoritative", "external_mirror", ), owned_concepts=( "file asset", "file version", "folder", "share", "connector space", "Form evidence upload grant", ), non_owned_concepts=( "record disposition", "campaign attachment rule", "external storage object", ), target_tested_providers=(REMOTE_STORAGE_PROVIDER_ID,), recovery_docs=("docs/FILES_HANDBOOK.md",), security_docs=("docs/CONNECTOR_BOUNDARY.md",), operations_docs=("docs/FILES_HANDBOOK.md",), ), ) manifest = with_documentation_structured_translations( manifest, locale="de", translations=GERMAN_STRUCTURED_TRANSLATIONS ) def get_manifest() -> ModuleManifest: return manifest