Add purpose-bound form evidence storage

This commit is contained in:
2026-08-06 12:42:20 +02:00
parent 60c3d14f08
commit aa9f90c71e
12 changed files with 2039 additions and 178 deletions
+495 -87
View File
@@ -5,13 +5,19 @@ 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.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,
)
from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard
from govoplan_core.core.module_guards import (
drop_table_retirement_provider,
persistent_table_uninstall_guard,
)
from govoplan_core.core.modules import (
CapabilityDocumentation,
DocumentationCondition,
@@ -42,6 +48,10 @@ from govoplan_core.db.base import Base
from govoplan_files.backend.change_tracking import register_files_change_tracking
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.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,
@@ -62,6 +72,7 @@ _files_table_retirement_provider = drop_table_retirement_provider(
file_models.FileFolder,
file_models.FileAsset,
file_models.FileVersion,
file_models.FileFormEvidenceGrant,
file_models.FileShare,
file_models.FileConnectorCredential,
file_models.FileConnectorPolicy,
@@ -79,8 +90,12 @@ def _files_retirement_provider(session: object | None, module_id: str):
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.")
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)
@@ -121,13 +136,37 @@ def _permission(scope: str, label: str, description: str) -> PermissionDefinitio
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:admin", "Administer file spaces", "Administer all file spaces in the tenant."),
_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:admin",
"Administer file spaces",
"Administer all file spaces in the tenant.",
),
)
ROLE_TEMPLATES = (
@@ -154,21 +193,48 @@ ROLE_TEMPLATES = (
def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
from govoplan_files.backend.db.models import FileAsset, FileConnectorCredential, FileConnectorPolicy, FileConnectorProfile, FileConnectorSpace
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(),
"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
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:
@@ -180,6 +246,7 @@ def _tenant_summary_batch(session, tenant_ids) -> dict[str, dict[str, int]]:
"connector_policies": 0,
"connector_profiles": 0,
"connector_spaces": 0,
"form_evidence_upload_grants": 0,
}
for tenant_id in ids
}
@@ -189,6 +256,7 @@ def _tenant_summary_batch(session, tenant_ids) -> dict[str, dict[str, int]]:
("connector_policies", FileConnectorPolicy),
("connector_profiles", FileConnectorProfile),
("connector_spaces", FileConnectorSpace),
("form_evidence_upload_grants", FileFormEvidenceGrant),
)
for count_key, model in models:
rows = (
@@ -203,21 +271,51 @@ def _tenant_summary_batch(session, tenant_ids) -> dict[str, dict[str, int]]:
def _veto_group_delete(session, tenant_id: str, group_id: str) -> None:
from govoplan_files.backend.db.models import FileAsset, FileConnectorSpace, FileFolder, FileShare
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.")
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):
@@ -229,6 +327,29 @@ def _files_router(context: ModuleContext):
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",
@@ -285,13 +406,17 @@ manifest = ModuleManifest(
id="files",
name="Files",
version="0.1.18",
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
required_capabilities=(
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
),
optional_dependencies=("campaigns", "encryption", "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_RECORD_SOURCE_FILES, version="1.0.0"),
ModuleInterfaceProvider(name=CAPABILITY_FORM_EVIDENCE_FILES, version="1.0.0"),
),
requires_interfaces=(
ModuleInterfaceRequirement(
@@ -315,6 +440,7 @@ manifest = ModuleManifest(
),
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,),
@@ -325,7 +451,15 @@ manifest = ModuleManifest(
),
),
delete_veto_providers={"group": (_veto_group_delete,)},
nav_items=(NavItem(path="/files", label="Files", icon="folder", required_any=("files:file:read",), order=40),),
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",
@@ -337,18 +471,121 @@ manifest = ModuleManifest(
order=40,
),
),
nav_items=(NavItem(path="/files", label="Files", icon="folder", 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.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,
),
),
),
documentation=(
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",
@@ -374,7 +611,11 @@ manifest = ModuleManifest(
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"),
DocumentationLink(
label="Files handbook",
href="govoplan-files/docs/FILES_HANDBOOK.md",
kind="repository",
),
),
metadata={
"kind": "workflow",
@@ -417,7 +658,11 @@ manifest = ModuleManifest(
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"),
DocumentationLink(
label="Files handbook",
href="govoplan-files/docs/FILES_HANDBOOK.md",
kind="repository",
),
),
metadata={
"kind": "reference",
@@ -446,10 +691,18 @@ manifest = ModuleManifest(
),
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"),
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.",
),
unlocks=("Managed content can be placed at stable logical paths without bypassing space access.",),
metadata={
"kind": "workflow",
"route": "/files",
@@ -494,15 +747,23 @@ manifest = ModuleManifest(
),
links=(
DocumentationLink(label="Files", href="/files", kind="runtime"),
DocumentationLink(label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository"),
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.",
),
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."],
"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.",
@@ -537,11 +798,21 @@ manifest = ModuleManifest(
),
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"),
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=("campaigns",),
unlocks=("A supporting process can grant governed file access without changing file ownership.",),
unlocks=(
"A supporting process can grant governed file access without changing file ownership.",
),
metadata={
"kind": "workflow",
"route": "/files",
@@ -589,9 +860,15 @@ manifest = ModuleManifest(
),
links=(
DocumentationLink(label="Files", href="/files", kind="runtime"),
DocumentationLink(label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository"),
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.",
),
unlocks=("Authorized users can remove obsolete content from active file views while preserving the current soft-delete boundary.",),
metadata={
"kind": "workflow",
"route": "/files",
@@ -644,14 +921,36 @@ manifest = ModuleManifest(
),
),
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"),
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.",),
unlocks=(
"Scoped, explainable external-file access without exposing credentials to consuming modules.",
),
configuration_keys=(
"GOVOPLAN_FILES_CONNECTOR_PROFILES_JSON",
"GOVOPLAN_FILES_CONNECTOR_PROFILES_FILE",
@@ -705,14 +1004,36 @@ manifest = ModuleManifest(
),
),
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"),
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.",),
unlocks=(
"Recoverable managed-file evidence without weakening outbound peer validation.",
),
configuration_keys=(
"FILE_STORAGE_BACKEND",
"FILE_STORAGE_LOCAL_ROOT",
@@ -734,7 +1055,13 @@ manifest = ModuleManifest(
"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"],
"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",
@@ -752,28 +1079,54 @@ manifest = ModuleManifest(
),
layer="available",
documentation_types=("admin", "user"),
audience=("module_integrator", "file_admin", "campaign_admin", "process_designer"),
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"),
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"),
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.",),
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"],
"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",
@@ -791,7 +1144,13 @@ manifest = ModuleManifest(
),
layer="configured",
documentation_types=("admin", "user"),
audience=("process_owner", "release_manager", "file_admin", "operator", "security_auditor"),
audience=(
"process_owner",
"release_manager",
"file_admin",
"operator",
"security_auditor",
),
order=53,
conditions=(
DocumentationCondition(
@@ -808,11 +1167,21 @@ manifest = ModuleManifest(
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"),
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.",),
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",
@@ -877,7 +1246,11 @@ manifest = ModuleManifest(
),
links=(
DocumentationLink(label="Files", href="/files", kind="runtime"),
DocumentationLink(label="Files handbook", href="govoplan-files/docs/FILES_HANDBOOK.md", kind="repository"),
DocumentationLink(
label="Files handbook",
href="govoplan-files/docs/FILES_HANDBOOK.md",
kind="repository",
),
),
related_modules=("ops", "campaigns"),
configuration_keys=(
@@ -917,8 +1290,16 @@ manifest = ModuleManifest(
),
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"),
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"},
@@ -939,6 +1320,7 @@ manifest = ModuleManifest(
file_models.FileFolder,
file_models.FileAsset,
file_models.FileVersion,
file_models.FileFormEvidenceGrant,
file_models.FileShare,
file_models.FileConnectorCredential,
file_models.FileConnectorPolicy,
@@ -949,10 +1331,18 @@ manifest = ModuleManifest(
),
),
capability_factories={
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),
"files.campaign_attachments": lambda context: __import__("govoplan_files.backend.capabilities", fromlist=["campaign_capability"]).campaign_capability(context),
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),
"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,
},
capability_documentation={
CAPABILITY_RECORD_SOURCE_FILES: CapabilityDocumentation(
@@ -960,6 +1350,11 @@ manifest = ModuleManifest(
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",
),
},
operational_check_providers=(
OperationalCheckProviderRegistration(
@@ -986,14 +1381,27 @@ manifest = ModuleManifest(
maturity="vertical_slice",
documentation_ref="docs/FILES_HANDBOOK.md",
test_ref="tests/test_storage_backends.py",
known_limits=("Target-environment multi-node recovery drills and writable remote connector effects are not reference-ready; hard purge and legal hold remain unimplemented.",),
known_limits=(
"Target-environment multi-node recovery drills and writable remote connector effects are not reference-ready; hard purge and legal hold remain unimplemented.",
),
supported_authority_modes=(
"native_authoritative",
"external_authoritative",
"external_mirror",
),
owned_concepts=("file asset", "file version", "folder", "share", "connector space"),
non_owned_concepts=("record disposition", "campaign attachment rule", "external storage object"),
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",),