docs(files): expose adaptive handbook topics

This commit is contained in:
2026-07-21 16:31:49 +02:00
parent 062ad5ddfb
commit 6c8a8c655d
3 changed files with 264 additions and 51 deletions

View File

@@ -137,6 +137,8 @@ manual interoperability testing.
Connector and collaboration ownership boundaries are documented in Connector and collaboration ownership boundaries are documented in
`docs/CONNECTOR_BOUNDARY.md` and `docs/DOCUMENT_COLLABORATION_BOUNDARY.md`. `docs/CONNECTOR_BOUNDARY.md` and `docs/DOCUMENT_COLLABORATION_BOUNDARY.md`.
The role-adaptive user, administration, integration, and operator guide is the
[Files handbook](docs/FILES_HANDBOOK.md).
ZIP uploads are processed without buffering the whole archive in memory. The API ZIP uploads are processed without buffering the whole archive in memory. The API
spools incoming ZIP request bodies to a bounded temporary file, then extracts spools incoming ZIP request bodies to a bounded temporary file, then extracts

View File

@@ -195,72 +195,206 @@ manifest = ModuleManifest(
), ),
documentation=( documentation=(
DocumentationTopic( DocumentationTopic(
id="files.governed-connectors-and-provenance", id="files.workflow.import-managed-snapshot",
title="Governed file connectors and source provenance", title="Import an external file as a governed snapshot",
summary="Connector profiles control who may browse an external source, which destinations are permitted, and how imported files retain verifiable source context.", summary="Browse an authorized connection read-only, import one selected file into managed storage, and use the frozen version in another GovOPlaN task.",
body=( body=(
"Users select only connector profiles visible in their current scope and import remote content into managed Files storage before another module uses it. " "Choose a visible file connection in Files, browse only the permitted remote path, and import the selected content into your personal or group space. "
"Administrators define profiles, separate credential references, and ordered system/tenant/owner policies; deny rules win and profile responses never expose secrets. " "The managed copy records source provenance and remains unchanged until an explicit manual sync. Browse, import, and sync never mutate the remote source."
"Deleting a database-managed profile or credential immediately scrubs Files-owned encrypted material and records a non-secret audit event in the same transaction; legacy non-owned external references are detached without provider calls. "
"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", layer="configured",
documentation_types=("admin", "user"), documentation_types=("user",),
audience=("file_user", "file_admin", "tenant_admin", "operator"), audience=("file_user", "campaign_manager", "report_author"),
order=40,
conditions=(
DocumentationCondition(
required_modules=("files",),
any_scopes=("files:file:read", "files:file:upload"),
),
),
links=(
DocumentationLink(label="Files", href="/files", kind="runtime"),
DocumentationLink(label="Visible connector profiles", href="/api/v1/files/connectors/profiles", kind="api"),
DocumentationLink(
label="Connector import API",
href="/api/v1/files/connectors/profiles/{profile_id}/import",
kind="api",
),
DocumentationLink(label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository"),
),
related_modules=("campaigns",),
unlocks=("Campaigns, reports, and workflows can consume a managed snapshot with stable source evidence.",),
metadata={
"kind": "workflow",
"route": "/files",
"screen": "Files",
"prerequisites": [
"Files is installed and you may read and upload files.",
"An administrator has configured a connector profile visible in your current user, group, tenant, or campaign scope.",
],
"steps": [
"Open Files and choose a managed destination space.",
"Choose Sync from connection, select a visible profile, and browse to the permitted remote file.",
"Import or sync the selected file and resolve any destination conflict explicitly.",
"Review the managed file's source and current-version details before using it in another task.",
],
"outcome": "The external content is a tenant-managed snapshot with a checksum, exact version, and recorded source context.",
"verification": "Reopen the managed file and confirm its connector/provider, remote identity or path, source revision when available, checksum, and current version.",
"related_topic_ids": [
"files.governed-connectors-and-provenance",
"files.reference.integrity-recovery-and-fail-closed-transports",
"files.reference.snapshot-provenance-and-capabilities",
],
},
),
DocumentationTopic(
id="files.governed-connectors-and-provenance",
title="Govern file connections and credential deletion",
summary="Keep endpoint profiles, reusable credentials, and inherited connector policy separate, and understand what DELETE removes immediately.",
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."
),
layer="configured",
documentation_types=("admin",),
audience=("file_admin", "tenant_admin", "system_admin", "security_auditor"),
order=41,
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="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository"),
),
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",
"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.",
],
"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 bytes, and the encryption key as one recovery unit, and keep unsupported SDK transports fail-closed.",
body=(
"Local durable storage is the operational baseline. Recover Files from a coordinated database/blob snapshot with the original master key, then verify representative checksums and access paths. "
"Live S3 managed storage/connectors and SMB connectors fail closed until botocore redirects/endpoint discovery and SMB initial connections/DFS referrals support connection-time DNS/IP pinning. 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=42, order=42,
conditions=( conditions=(
DocumentationCondition( DocumentationCondition(
required_modules=("files",), required_modules=("files",),
any_scopes=( any_scopes=(
"files:file:read",
"files:file:upload",
"files:file:admin", "files:file:admin",
"admin:settings:read", "admin:settings:read",
"system:settings:read", "system:settings:read",
"system:audit:read",
), ),
), ),
), ),
links=( links=(
DocumentationLink(label="Files", href="/files", kind="runtime"), DocumentationLink(label="System file connections", href="/admin?section=system-file-connectors", kind="runtime"),
DocumentationLink(label="Tenant connector administration", href="/admin?section=tenant-file-connectors", kind="runtime"), DocumentationLink(label="Connector provider status", href="/api/v1/files/connectors/providers", kind="api"),
DocumentationLink(label="Connector profiles API", href="/api/v1/files/connectors/profiles", kind="api"), DocumentationLink(label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository"),
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",), related_modules=("audit", "ops"),
unlocks=("Campaigns can consume managed attachments while Files preserves frozen source provenance.",), unlocks=("Recoverable managed-file evidence without weakening outbound peer validation.",),
configuration_keys=( configuration_keys=(
"FILE_STORAGE_BACKEND",
"FILE_STORAGE_LOCAL_ROOT",
"FILE_STORAGE_LOCAL_FALLBACK_ROOTS",
"FILE_UPLOAD_MAX_BYTES",
"FILE_UPLOAD_ZIP_MAX_BYTES",
"MASTER_KEY_B64",
"GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS", "GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS",
"GOVOPLAN_FILES_CONNECTOR_PROFILES_JSON", "GOVOPLAN_CONNECTOR_MAX_STRUCTURED_RESPONSE_BYTES",
"GOVOPLAN_FILES_CONNECTOR_PROFILES_FILE", "GOVOPLAN_CONNECTOR_MAX_FILE_TRANSFER_BYTES",
"GOVOPLAN_CONNECTOR_SECRET_ENV_ALLOWLIST",
), ),
metadata={ metadata={
"kind": "reference", "kind": "reference",
"perspectives": { "route": "/admin?section=system-file-connectors",
"user": "Choose a visible connection, browse it read-only, and import a selected file into managed storage before using it elsewhere.", "screen": "System file connections and deployment operations",
"admin": "Govern profile visibility, separate credential references, provider allow/deny policy, and permitted connector operations.", "section": "Storage integrity, backup/recovery, and fail-closed transports",
"operator": "Control private-network access deployment-wide, provide credential and CA allowlists, and investigate fail-closed transport errors without weakening peer validation.", "recovery_unit": ["Files database rows", "managed blob namespace", "MASTER_KEY_B64", "deployment-owned connector configuration"],
"verification": "After restore, download representative files, compare their bytes with recorded SHA-256 values, verify authorized and denied access, and test one permitted pinned HTTP connector.",
"related_topic_ids": [
"files.governed-connectors-and-provenance",
"files.reference.snapshot-provenance-and-capabilities",
],
}, },
"security_invariants": [ ),
"Every network peer must be policy-validated and pinned at connection time.", DocumentationTopic(
"Redirects and protocol referrals must be rejected or independently revalidated and pinned.", id="files.reference.snapshot-provenance-and-capabilities",
"SDK transports that cannot provide those guarantees fail before client construction.", title="Integrate through managed snapshots and Files capabilities",
"New API-managed external secret references fail closed until Files can prove ownership and provider-side deletion.", summary="Other modules consume stable Files capabilities or HTTP contracts and retain exact version evidence instead of importing Files internals.",
"Deletion and destructive retirement scrub Files-owned encrypted connector material before completion and emit non-secret audit evidence.", body=(
"Legacy non-owned external references are detached and audited, never sent to an arbitrary provider delete operation.", "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=43,
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"],
"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",
], ],
"provenance_fields": [
"connector_id",
"provider",
"external_id",
"external_path",
"revision",
"metadata",
],
"related_topic_ids": ["mail.profiles-and-policy"],
}, },
), ),
), ),

View File

@@ -3,20 +3,97 @@ from __future__ import annotations
import unittest import unittest
TOPIC_IDS = {
"files.workflow.import-managed-snapshot",
"files.governed-connectors-and-provenance",
"files.reference.integrity-recovery-and-fail-closed-transports",
"files.reference.snapshot-provenance-and-capabilities",
}
HANDBOOK_HREF = "govoplan-files/docs/FILES_HANDBOOK.md"
class FilesManifestDocumentationTests(unittest.TestCase): class FilesManifestDocumentationTests(unittest.TestCase):
def test_connector_topic_covers_governance_pinning_and_provenance_perspectives(self) -> None: @classmethod
def setUpClass(cls) -> None:
from govoplan_files.backend.manifest import manifest from govoplan_files.backend.manifest import manifest
topic = next(item for item in manifest.documentation if item.id == "files.governed-connectors-and-provenance") cls.topics = {topic.id: topic for topic in manifest.documentation}
def topic(self, topic_id: str):
return self.topics[topic_id]
def test_adaptive_topics_have_role_scope_module_and_link_contracts(self) -> None:
self.assertEqual(TOPIC_IDS, set(self.topics))
self.assertEqual({"admin", "user"}, {item for topic in self.topics.values() for item in topic.documentation_types})
for topic in self.topics.values():
with self.subTest(topic=topic.id):
self.assertTrue(topic.audience)
self.assertTrue(topic.conditions)
self.assertTrue(any("files" in condition.required_modules for condition in topic.conditions))
self.assertTrue(any(condition.any_scopes or condition.required_scopes for condition in topic.conditions))
self.assertIn("runtime", {link.kind for link in topic.links})
self.assertIn("api", {link.kind for link in topic.links})
self.assertIn(HANDBOOK_HREF, {link.href for link in topic.links if link.kind == "repository"})
def test_import_topic_is_a_complete_user_workflow(self) -> None:
topic = self.topic("files.workflow.import-managed-snapshot")
self.assertEqual(("user",), topic.documentation_types)
self.assertEqual("workflow", topic.metadata["kind"])
self.assertEqual("/files", topic.metadata["route"])
self.assertEqual("Files", topic.metadata["screen"])
for key in ("prerequisites", "steps", "outcome", "verification"):
self.assertTrue(topic.metadata[key])
self.assertIn("never mutate the remote source", topic.body)
self.assertIn("/api/v1/files/connectors/profiles/{profile_id}/import", {link.href for link in topic.links})
def test_admin_topic_covers_policy_redaction_and_atomic_credential_deletion(self) -> None:
topic = self.topic("files.governed-connectors-and-provenance")
self.assertEqual(("admin",), topic.documentation_types)
self.assertEqual("reference", topic.metadata["kind"])
self.assertEqual("File connections", topic.metadata["screen"])
self.assertTrue(topic.metadata["section"])
self.assertIn("deny rules win", topic.body)
self.assertIn("redact secret values", topic.body)
self.assertIn("same transaction", topic.body)
self.assertTrue(any("non-owned external references" in item for item in topic.metadata["security_invariants"]))
self.assertIn("/api/v1/files/connectors/policies/tenant", {link.href for link in topic.links})
self.assertIn("/api/v1/files/connectors/credentials", {link.href for link in topic.links})
def test_operator_topic_covers_recovery_and_fail_closed_s3_smb(self) -> None:
topic = self.topic("files.reference.integrity-recovery-and-fail-closed-transports")
self.assertEqual(("admin",), topic.documentation_types)
self.assertEqual("reference", topic.metadata["kind"])
self.assertIn("S3", topic.body)
self.assertIn("SMB", topic.body)
self.assertIn("fail closed", topic.body)
self.assertIn("DFS referrals", topic.body)
self.assertIn("does not remove backend blob objects", topic.body)
self.assertIn("MASTER_KEY_B64", topic.metadata["recovery_unit"])
self.assertTrue(topic.metadata["verification"])
self.assertIn("GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS", topic.configuration_keys)
def test_integrator_topic_exposes_capability_and_provenance_boundary(self) -> None:
topic = self.topic("files.reference.snapshot-provenance-and-capabilities")
self.assertEqual(("admin", "user"), topic.documentation_types) self.assertEqual(("admin", "user"), topic.documentation_types)
self.assertEqual({"user", "admin", "operator"}, set(topic.metadata["perspectives"])) self.assertEqual("reference", topic.metadata["kind"])
self.assertIn("fails closed", topic.body) self.assertEqual(
self.assertIn("DFS referrals", topic.body) ["files.access@0.1.6", "files.campaign_attachments@0.1.6"],
self.assertIn("immediately scrubs", topic.body) topic.metadata["provided_interfaces"],
self.assertTrue(any("non-owned external references" in item for item in topic.metadata["security_invariants"])) )
self.assertIn("revision", topic.metadata["provenance_fields"]) self.assertIn("revision", topic.metadata["provenance_fields"])
self.assertIn("/api/v1/files/connectors/profiles", {link.href for link in topic.links}) self.assertIn("exact asset, version, blob, checksum", topic.body)
self.assertIn("collaboration", topic.body)
def test_internal_related_topic_ids_resolve(self) -> None:
for topic in self.topics.values():
for related_id in topic.metadata.get("related_topic_ids", []):
if related_id.startswith("files."):
self.assertIn(related_id, TOPIC_IDS, f"{topic.id} refers to missing topic {related_id}")
if __name__ == "__main__": if __name__ == "__main__":