diff --git a/src/govoplan_files/backend/manifest.py b/src/govoplan_files/backend/manifest.py index 239f04d..7cd2cc9 100644 --- a/src/govoplan_files/backend/manifest.py +++ b/src/govoplan_files/backend/manifest.py @@ -6,6 +6,9 @@ from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPA from govoplan_core.core.files import CAPABILITY_FILES_ACCESS from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard from govoplan_core.core.modules import ( + DocumentationCondition, + DocumentationLink, + DocumentationTopic, FrontendModule, MigrationSpec, ModuleContext, @@ -138,6 +141,73 @@ manifest = ModuleManifest( package_name="@govoplan/files-webui", nav_items=(NavItem(path="/files", label="Files", icon="folder", required_any=("files:file:read",), order=40),), ), + documentation=( + DocumentationTopic( + id="files.governed-connectors-and-provenance", + title="Governed file connectors and source provenance", + summary="Connector profiles control who may browse an external source, which destinations are permitted, and how imported files retain verifiable source context.", + body=( + "Users select only connector profiles visible in their current scope and import remote content into managed Files storage before another module uses it. " + "Administrators define profiles, separate credential references, and ordered system/tenant/owner policies; deny rules win and profile responses never expose secrets. " + "Operators control private-network access deployment-wide and must keep every remote connection pinned to a policy-validated DNS/IP answer. " + "The built-in HTTP transport pins each connection and refuses redirects. Live S3 and SMB SDK access fails closed until S3 redirects and SMB DFS referrals can be revalidated and pinned. " + "Successful imports store the connector id, provider, remote path and identity, source revision, and selected metadata as provenance on the managed file and its audit events." + ), + layer="configured", + documentation_types=("admin", "user"), + audience=("file_user", "file_admin", "tenant_admin", "operator"), + order=42, + conditions=( + DocumentationCondition( + required_modules=("files",), + any_scopes=( + "files:file:read", + "files:file:upload", + "files:file:admin", + "admin:settings:read", + "system:settings:read", + ), + ), + ), + links=( + DocumentationLink(label="Files", href="/files", kind="runtime"), + DocumentationLink(label="Tenant connector administration", href="/admin?section=tenant-file-connectors", kind="runtime"), + DocumentationLink(label="Connector profiles API", href="/api/v1/files/connectors/profiles", kind="api"), + DocumentationLink(label="Connector providers API", href="/api/v1/files/connectors/providers", kind="api"), + DocumentationLink(label="Tenant connector policy API", href="/api/v1/files/connectors/policies/tenant", kind="api"), + ), + related_modules=("campaigns",), + unlocks=("Campaigns can consume managed attachments while Files preserves frozen source provenance.",), + configuration_keys=( + "GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS", + "GOVOPLAN_FILES_CONNECTOR_PROFILES_JSON", + "GOVOPLAN_FILES_CONNECTOR_PROFILES_FILE", + "GOVOPLAN_CONNECTOR_SECRET_ENV_ALLOWLIST", + ), + metadata={ + "kind": "reference", + "perspectives": { + "user": "Choose a visible connection, browse it read-only, and import a selected file into managed storage before using it elsewhere.", + "admin": "Govern profile visibility, separate credential references, provider allow/deny policy, and permitted connector operations.", + "operator": "Control private-network access deployment-wide, provide credential and CA allowlists, and investigate fail-closed transport errors without weakening peer validation.", + }, + "security_invariants": [ + "Every network peer must be policy-validated and pinned at connection time.", + "Redirects and protocol referrals must be rejected or independently revalidated and pinned.", + "SDK transports that cannot provide those guarantees fail before client construction.", + ], + "provenance_fields": [ + "connector_id", + "provider", + "external_id", + "external_path", + "revision", + "metadata", + ], + "related_topic_ids": ["mail.profiles-and-policy"], + }, + ), + ), migration_spec=MigrationSpec( module_id="files", metadata=Base.metadata, diff --git a/tests/test_manifest_documentation.py b/tests/test_manifest_documentation.py new file mode 100644 index 0000000..8a19947 --- /dev/null +++ b/tests/test_manifest_documentation.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import unittest + + +class FilesManifestDocumentationTests(unittest.TestCase): + def test_connector_topic_covers_governance_pinning_and_provenance_perspectives(self) -> None: + from govoplan_files.backend.manifest import manifest + + topic = next(item for item in manifest.documentation if item.id == "files.governed-connectors-and-provenance") + + self.assertEqual(("admin", "user"), topic.documentation_types) + self.assertEqual({"user", "admin", "operator"}, set(topic.metadata["perspectives"])) + self.assertIn("fails closed", topic.body) + self.assertIn("DFS referrals", topic.body) + self.assertIn("revision", topic.metadata["provenance_fields"]) + self.assertIn("/api/v1/files/connectors/profiles", {link.href for link in topic.links}) + + +if __name__ == "__main__": + unittest.main()