# GovOPlaN Files Handbook This handbook describes the Files module as implemented in version `0.1.18`. It is the operational source of truth for users, process owners, administrators, operators, auditors, and module integrators. Statements about future behavior are marked **planned**; an unmarked statement describes the current code. Files is a governed snapshot store. It owns managed file content, versions, logical folders, shares, source provenance, and the evidence that another GovOPlaN module used a particular file version. It can browse selected external stores and import a frozen copy. Explicit, conditional S3 write-back is available only through an administrator-enabled two-way connector space. Files is not a general remote filesystem, a document collaboration engine, or a records-management system. ## Choose a reading path | If you need to... | Start with... | | --- | --- | | Upload, find, organize, download, or import a file | [User tasks](#user-tasks) | | Design a governed process that uses files | [Process perspective](#process-perspective) | | Decide who can use a file or connector | [Ownership and access](#ownership-and-access) and [Administration and policy](#administration-and-policy) | | Operate storage, connectors, backups, or recovery | [Operator runbook](#operator-runbook) | | Integrate another GovOPlaN module | [Capabilities and integration](#capabilities-and-integration) | | Review evidence, deletion, or security behavior | [Security, provenance, audit, deletion, and retention](#security-provenance-audit-deletion-and-retention) | | Verify a release or scenario | [Acceptance scenarios](#acceptance-scenarios) | | Check whether an idea exists today | [Implemented and planned boundary](#implemented-and-planned-boundary) | The Files-owned interface archetypes, consequence classes, disabled-state wording, and verification evidence are recorded in [Files Interface Pattern Migration](INTERFACE_PATTERN_MIGRATION.md). ## The service contract A managed file is a tenant-scoped logical asset with exactly one user or group owner, a normalized path, and a current version. The current version points to a blob record containing the storage location, SHA-256 checksum, byte size, and content type. A protected blob also records its Encryption envelope, protection discriminator, stored-ciphertext checksum, and stored-ciphertext size. Current service paths append a version when connector sync finds changed content; they do not mutate the previous version record. Unprotected content with the same tenant, plaintext SHA-256 checksum, size, and protection discriminator can reuse one blob. Protected content is deduplicated only inside the same vault/profile discriminator; ciphertext is never silently reused across protection boundaries. Plaintext checksums remain semantic version evidence, while download and integrity scans verify stored ciphertext before asking Encryption to open it. Neither digest proves authorship or source authenticity. The main domain objects are: | Object | Meaning | Lifecycle today | | --- | --- | --- | | File asset | The user-facing file identity, owner, logical path, description, metadata, current version, retention deadline, legal hold, and lifecycle revision | Created, organized, shared, soft-deleted, restored, or governed-purged | | File version | A numbered snapshot of one asset and its blob | Appended by connector sync when bytes change; retained until its asset is eligible for purge | | File blob | Stored plaintext semantics plus stored-byte integrity, backend key, optional Encryption envelope, reference count, and retention timestamp | Reused only within a tenant and matching protection boundary; collected only after a fenced reference recheck | | Folder | An explicit logical path in a user or group space | Created, moved/renamed, soft-deleted, and restored when its original path is free | | Share | A grant from an asset to a user, group, tenant, or campaign with `read`, `write`, or `manage` permission | Created or updated; no public revocation endpoint yet | | Connector profile | A governed external endpoint, scope, optional credential link, policy, and descriptive capabilities | Created, updated, disabled, or credential-scrubbed on deletion | | Connector credential | Reusable authentication material, optionally limited to a provider and scope | Encrypted when database-managed; immediately scrubbed on deletion | | Connector policy | Allow and deny rules inherited from system through tenant to one leaf scope | Evaluated before configuration and connector use | | Connector space | A manually synchronized remote folder/library linked to a user or group space; read-only by default, with explicit S3 two-way mode available | Created, updated, disabled, soft-deleted, and restored | | Campaign attachment use | Evidence connecting a campaign job or entry to an exact asset, version, blob, checksum, and stage | Retained for campaign execution evidence | | Form evidence upload grant | A one-use, hash-only bearer grant tied to an exact Form instance/revision, purpose, custodian, size, and media-type policy | Issued for at most 15 minutes, consumed by one managed upload, then retained as evidence provenance | ## Deployment configuration packages Files registers the `files.configuration` capability for `managed_storage` fragments. Managed content storage is deployment-owned: the installer selects local persistent storage, managed Garage, or an external S3-compatible service and mounts a validated non-secret infrastructure capability receipt. Files does not copy that endpoint or its credentials into module-owned tables. Preflight compares the receipt's `files.storage` capability with the effective runtime backend. It validates backend kind, sanitized S3 endpoint, bucket, Garage management marker or external trust marker, an absolute persistent path for local storage, and the presence of Files-owned `env:` secret references. Secret values are never read into a plan, diagnostic, export, or fragment. When runtime and receipt agree, the plan reports `skip`: the desired binding is already effective, and repeated apply is a no-op. A mismatch blocks import and explains which deployment setting must be reconciled. The provider deliberately does not mutate process environment, migrate stored objects, probe remote storage, or reinterpret an infrastructure replacement as safe. Use the Files integrity and Ops checks after deployment and complete migration/recovery review before changing an active backend. The Files infrastructure dependency provider makes that review concrete. Its authorized, non-secret Ops inventory reports the active runtime binding and aggregates persisted `FileBlob` rows by storage backend with blob counts and byte totals. A host apply that changes `files.storage` requires a fresh, complete inventory from the same installation and shows the migration and checksum-verification action before any service is replaced. Object keys, tenant identifiers and storage credentials are not exported. ## User tasks The Files page is available at `/files`. Actions appear only when the current principal has the required permission and resource access. The configured Help Center projects these sections as independently authorized tasks, so upload, ZIP import, organization, download, sharing, and deletion do not disappear merely because an unrelated permission is absent. It states the deployment's actual upload limits. External import appears as a task only when the actor has the required permissions and at least one actor-visible profile is eligible for the current fail-closed browse/import path; endpoint, path, and item policy are still enforced when the operation runs. ### Choose a space Every file user sees **My files**. Group spaces are added for active groups of which the user is a member. The space list also exposes every tenant group to a Files administrator; governed API operations can administer other tenant-owned Files resources when their owner is specified. Linked connector spaces appear beside managed spaces when they are active and visible to the user. A connector space is read-only by default. It is a view of an approved remote location and a starting point for importing a selected file or synchronizing a selected file or bounded folder tree into managed storage. Folder sync preserves relative remote paths in the linked owner's managed space and returns a reviewable result for every discovered or deliberately skipped item. An administrator may opt an S3 space into two-way mode only after enabling the profile's `write` capability. Even then, Files exposes explicit, conditional file write-back—not a mounted filesystem. Automatic remote delete, rename, move, and ACL propagation stay disabled. ### Upload files The managed-space UI supports upload and drag-and-drop. A caller chooses the user or group owner and destination folder. The normal per-file deployment limit defaults to 50 MiB through `FILE_UPLOAD_MAX_BYTES`. When a target path already exists, the operation must use one of these conflict strategies: - `reject` stops instead of silently replacing content; - `rename` selects the next available `copy` name; - `overwrite` soft-deletes the conflicting asset and creates the new asset; - an item-specific conflict resolution can also `skip` that item. An ordinary upload does not append a new version to an existing asset. The `overwrite` strategy retires the old asset at that path. Version-preserving updates currently belong to connector sync. ### Preview and unpack archives The UI previews ZIP, TAR, TAR.GZ, TAR.BZ2, and TAR.XZ before writing managed files. Users may select individual files or complete folders. The request is spooled to a bounded temporary file rather than buffered wholly in memory. The defaults are: - 250 MiB compressed request; - 50 MiB per extracted member; - 2 GiB total expanded data; - 10,000 declared entries; - 100:1 maximum expansion ratio; - a 30-minute preview token bound to the tenant, user, archive digest, and destination; - password-protected ZIP support with request-only password handling; - traversal, duplicate paths, links, devices, and other special entries are rejected; - actual bytes read are counted, not only archive header declarations. The browser retains the selected archive and password until confirmation. Confirmation reuploads the archive, verifies the token and digest, repeats all safety checks, and commits only the selected files. No preview archive or password is retained server-side. ### Organize files and folders With `files:file:organize`, a user can: - create logical folders; - rename one selection directly; - preview and apply bulk prefix, suffix, or replacement renames; - move or copy files and folder trees between accessible user/group spaces; - resolve target conflicts by rejecting, renaming, overwriting, or skipping; - use drag-and-drop for move operations in the file explorer. Copies create new assets and versions while reusing the immutable blob bytes. Moves keep the asset identity and change its owner/path. All source and target owners are validated; knowing an identifier does not bypass space membership. Recursive folder deletion is the default. It soft-deletes the selected folder, its child folders, and files below it. A non-recursive delete fails when the folder is not empty. ### Restore, retain, hold, and purge Soft deletion is reversible for callers with `files:file:restore`. Restoring a file preserves its asset identity, complete version history, blob references, and source provenance. Recursive folder restore reactivates the folder tree and its deleted files. Connector-space restore reactivates only the local link. Each restore fails if an active resource already occupies the original path or label. Retention and legal hold are independent lifecycle controls. A caller with `files:file:retention` supplies the current lifecycle revision, a reason, an optional retained-until time, and legal-hold state. Stale revisions fail rather than overwriting a concurrent decision. Hard purge is deliberately separate from ordinary delete: 1. A caller with `files:file:purge` previews 1–100 soft-deleted assets. 2. The preview reports retention, legal-hold, active-share, Campaign-evidence, and Form-evidence blockers and returns a SHA-256 over current lifecycle and blob-reference state. 3. Execution requires that exact hash, a stable idempotency key, an approval reference, and the literal `PURGE` confirmation. 4. The recovery-ledger operation and tenant purge fence are durable before the irreversible database transaction. A stale preview or blocker rejects the operation without erasure. 5. Purge removes eligible asset, version, and inactive-share rows and recalculates blob reference counts. It does not delete bytes inline. 6. A separately authorized bounded blob-GC call takes the same per-blob lease used by uploads, rechecks all `FileVersion` references, deletes the exact object, verifies absence, and only then deletes `FileBlob` metadata. Automatic time-based purge scheduling is not implemented. Operators initiate preview, execute, and garbage collection under their local retention process. ### Data-subject request coverage Files registers `privacy.dsar.files` when the module is active. The provider requires a corroborated tenant membership identifier (or a namespaced Files user reference), searches only that tenant, and fails explicitly if its bounded result limit would be exceeded. It reports managed assets, exact versions, folders, user-targeted shares, Form and Campaign evidence, connector configuration actor references, and integrity-operation evidence. The export includes only governed metadata. It never embeds file bytes, blob storage keys, passwords, tokens, environment-variable names, secret-provider references, or encrypted connector values. A reviewer follows the authorized version download route when the file itself must be inspected. The erasure plan deliberately separates four outcomes: - an active share aimed at the subject can be revoked idempotently; - mutable creator/updater references can be detached after tenant, subject, and current-value revalidation; - legal hold, active retention, submitted Form evidence, Campaign delivery evidence, connector configuration history, and integrity evidence are retained with a reason; and - unstructured file content, ownership, filenames, and paths require manual review. DSAR execution never invokes physical byte deletion. If the privacy decision authorizes erasure, the operator must use the separate Files soft-delete, purge preview, approval, execution, and blob-GC sequence. This preserves its distinct authority, evidence blockers, audit trail, distributed fencing, and recovery ledger semantics. Email, account, or identity selectors alone are insufficient because Files does not import the Access directory; the Access search supplies the corroborated membership reference for Files coverage. ### Find and download files Files can list by owner and path, use cursor pagination, and consume incremental changes through a watermark. The UI supports path/name pattern search and sorting. The pattern API can resolve campaign-style wildcard selections and can return unmatched files. A user with download permission and resource access can download one current version or create a ZIP archive from a selection. Downloads use an attachment content disposition. The archive is generated in a temporary file and removed after the response completes. There is no dedicated content-preview service in the Files API today. File responses expose content type, size, checksum, and current version metadata. ### Share files The API can grant or update a share for a user, group, the tenant, or a campaign. The supported permissions are `read`, `write`, and `manage`. Ownership remains unchanged. A write operation accepts a `write` or `manage` share; read/download accepts any of the three. The current Files page shows campaign linkage but does not offer a general user/group share editor. The API also has no share-revocation route yet. Treat share revocation and a complete share-management UI as planned work. ### Browse and import an external file With a visible connector profile, a user can browse the allowed remote path, select one file, and import it into an accessible managed space. The imported asset records the connector, provider, remote identity/path/URL, selected remote metadata, and source revision when supplied by the provider. Manual sync looks for an existing managed asset with the same source identity inside the chosen owner space: - no match creates a managed asset; - identical checksum and size updates provenance and returns `unchanged`; - changed bytes append a version and return `updated`. Folder-level manual sync uses `POST /api/v1/files/connector-spaces/{space_id}/sync`. The interactive request is bounded by file count and traversal depth, follows provider pagination, and can include or exclude subfolders. Every file is isolated in a database savepoint, so one conflict, policy denial, invalid provider result, or transport failure remains reviewable without discarding successful siblings. Results are classified as `created`, `updated`, `unchanged`, `skipped`, `conflict`, `policy_denied`, or `failed`. The UI defaults unrelated target collisions to `skip`; `rename`, `reject`, and `overwrite` require an explicit operator choice. The response marks a run as truncated when its bound is reached so the operator can continue with a narrower remote folder. This is interactive orchestration, not background scheduling. Browse, import, and inbound sync never mutate the remote source. An S3 space in explicit two-way mode can write a selected managed file to one remote object path through `POST /api/v1/files/connector-spaces/{space_id}/write-back`. Creating a path uses `If-None-Match`; overwriting requires the currently observed ETag or version and uses a conditional request. The provider object is then re-read and must contain both the expected content digest and the recovery operation marker. Uncertain or mismatching outcomes remain fenced and visible in Ops. Remote deletion, rename, move, and permission propagation are disabled. Connector administration separates endpoint profiles, reusable credentials, and inherited policy. Ordinary setup uses typed fields and provider discovery; provider metadata JSON is available only under advanced compatibility options. Read-only deployment entries explain where they must be changed, and disabled actions identify the missing permission, target, input, or running operation. The contextual help icon opens the configured Help Center topic when Docs is enabled and the hosted GovOPlaN documentation otherwise. ## Process perspective ### Managed ingestion The managed-file flow is: 1. Core authenticates the principal and evaluates the operation permission. 2. Files validates the tenant, owner, group membership, or applicable share. 3. Files normalizes the logical path and resolves conflicts explicitly. 4. Upload or connector response limits are enforced before content is retained. 5. Files calculates SHA-256 and stores or reuses a tenant blob. 6. Files creates the asset/version records and optional campaign share. 7. The database transaction commits and emits change-sequence entries. 8. Connector-originated operations also emit their connector audit evidence. Blob storage is not part of the database transaction. An object may therefore be left without committed metadata after a process or database failure. The operator integrity API scans database blobs and the tenant storage prefix in bounded, resumable phases. It reports orphan objects before any cleanup and never deletes them as part of a scan. ### Governed connector import The connector flow separates four concerns: 1. An administrator defines reusable credential material. 2. An administrator defines a scoped endpoint profile that may reference that credential. 3. System, tenant, and leaf policy sources narrow the allowed profile, credential, provider, URL, and remote path. 4. A user optionally links an allowed remote root as a user/group connector space, then browses and imports selected content. Before each network operation, Files checks profile visibility, connector policy, endpoint safety, transport support, and response size. A successful import becomes an independent managed snapshot. Later source changes have no effect until an explicit sync. ### Campaign attachment evidence When Campaign uses Files, the integration follows a freeze-before-send model: 1. The campaign refers to managed user/group sources and attachment patterns. 2. Files verifies access and resolves matching managed assets. 3. A prepared campaign snapshot records the exact asset, version, blob, checksum, size, relative path, and source provenance. 4. Files materializes those bytes for the campaign build without exposing its database models to Campaign. 5. Campaign job/entry use is recorded and later marked as sent. Changing the current file after preparation does not change the version already recorded as campaign evidence. A file response is marked `audit_relevant` once the asset has a sent campaign attachment-use record. ### Process ownership | Concern | Owner | | --- | --- | | Authentication, tenants, RBAC evaluation, audit service, change sequence, settings, and module lifecycle | GovOPlaN Core | | Managed assets, blobs, versions, folders, shares, connector baseline, provenance, and campaign attachment evidence | Files | | Campaign definition, recipient data, message build/send state, and delivery policy | Campaign | | Remote ACLs, remote source content, and upstream revision semantics | The external provider | | Storage durability, egress policy, master key, secret environment, backup, and recovery | Deployment operator | | Collaborative editing, comments, review, locks, and semantic document workflows | A future Documents/workflow/provider module | ## Ownership and access Files applies both permission checks and resource checks. A broad operation permission alone does not make another user's file visible. ### Resource access - A user owns their personal space. - A group member can use the group's file space. - A Files administrator can access all Files resources in the active tenant. - A file share can grant read or write access to a user, group, or the tenant. - Campaign shares are resolved only in a verified campaign context and do not become ordinary user shares. - Folders are owned by a user or group; they are not independently shared. - Soft-deleted resources are excluded from normal access and listing. - Tenant identifiers are checked on every managed object lookup. The `files.access` capability can explain why a principal has access: resource, owner, administrator scope, or active share. It also explains virtual folders that exist through child assets even when there is no explicit folder row. Deleting an organization/access group is vetoed while it owns Files assets, folders, connector spaces, or is the target of file shares. Reassign or remove those relationships first. ### Operation permissions | Permission | Allows | | --- | --- | | `files:file:read` | List and inspect accessible files, folders, spaces, and visible connectors | | `files:file:download` | Download an accessible current version or ZIP archive | | `files:file:upload` | Upload managed assets and import/sync selected connector files or bounded connector folders | | `files:file:organize` | Create folders, rename, move/copy, and manage linked connector spaces | | `files:file:share` | Create or update file shares | | `files:file:delete` | Soft-delete accessible writable files and folders | | `files:file:restore` | Restore owned or administered soft-deleted files, folders, and connector-space links | | `files:file:retention` | Set retention deadlines and legal holds with optimistic revision checks | | `files:file:purge` | Preview and execute irreversible metadata purge and collect unreferenced blobs | | `files:connector:write` | Write an accessible managed file to an explicitly writable connector space | | `files:file:admin` | Administer all Files spaces and connector settings in the active tenant | The `file_manager` role template grants all normal file operations except `files:file:admin`. The `file_viewer` template grants read and download. System and tenant settings permissions can also authorize the corresponding connector administration endpoints. ## Administration and policy ### Profiles, credentials, policies, and spaces Keep these definitions separate: - a **credential** holds reusable authentication material and may be restricted to a provider; - a **profile** holds the endpoint, scope, base path, credential reference, local policy, and descriptive operation capabilities; - a **policy** restricts what a scope may configure or use; - a **connector space** links one approved profile/library/path to one user or group, uses manual synchronization, and is read-only unless explicitly configured for the supported two-way S3 write boundary. Profile capability values such as `browse`, `import`, `sync`, and `write` are stored and returned. `write` is additionally enforced for S3 write-back, but provider implementation, connector-space mode, operation permission, resource access, and inherited policy all remain mandatory; never use the capability list as the sole security control. Profiles, credentials, and policies support `system`, `tenant`, `user`, `group`, and `campaign` scopes. A normal user sees system and active-tenant profiles plus leaf profiles that match the user, one of their groups, or an accessible campaign. Disabled definitions are visible only through authorized administrative reads. ### Policy evaluation For a leaf scope, the effective source chain is: ```text system -> tenant -> user | group | campaign ``` Policy fields are: - connector/profile IDs; - credential IDs; - providers; - external IDs; - external path prefixes or glob patterns; - external URL glob patterns. Rules use `allow` and `deny` objects. Legacy synonyms `allowlist`, `whitelist`, `denylist`, and `blacklist` are normalized. A matching deny at any source wins. Every allow field defined by a source must match, so lower sources can narrow an inherited set. The effective-policy response includes the contributing source path and applied fields for explanation. A parent may set `allow_lower_level_limits` for individual fields such as `allow.providers` or `deny.external_paths`. An explicit `false` prevents a lower scope from configuring that field. Absence does not lock the field. Example tenant policy: ```json { "policy": { "allow": { "providers": ["webdav", "nextcloud"], "external_urls": ["https://files.example.edu/*"], "external_paths": ["departments/finance"] }, "deny": { "external_paths": ["departments/finance/private"] }, "allow_lower_level_limits": { "allow.providers": true, "deny.external_paths": true } } } ``` Use `POST /api/v1/files/connector-policy/evaluate` for an explainable preflight. The normal profile browse/import/sync routes perform their own policy checks; preflight does not replace enforcement. ### Credential rules Database-created passwords and tokens use Core's Fernet encryption and require the deployment `MASTER_KEY_B64` outside development/test/local environments. Secret values, environment variable names, and local CA paths are never returned in connector profile responses. API-managed profiles and credentials cannot select process environment variables, create an external `secret_ref`, or conceal secret-like values in nested metadata. Deployment-owned JSON/file profiles may use `password_env` or `token_env` only when the exact environment variable name appears in `GOVOPLAN_CONNECTOR_SECRET_ENV_ALLOWLIST`. Custom CA bundles must be absolute existing files in `GOVOPLAN_CONNECTOR_CA_BUNDLE_ALLOWLIST`. TLS verification can be disabled only in a development/test runtime. ### Provider status | Provider | Current status | | --- | --- | | Seafile | Read-only native API browse/download-link import and manual sync implemented using the pinned HTTP transport; WebDAV opt-in supported | | Nextcloud | Read-only WebDAV browse/import/manual sync implemented using the pinned HTTP transport | | Generic WebDAV | Read-only browse/import/manual sync implemented using the pinned HTTP transport | | SMB | Read-only browse/import/manual sync implemented through a pinned smbprotocol transport for initial peers, reconnects, aliases, and DFS referral targets | | S3 connector | Bucket/prefix browse, import, manual inbound sync, and explicit conditional write-back implemented through pinned botocore pools covering retries, redirects, endpoint discovery, and provider aliases; automatic remote delete/rename/move/ACL propagation disabled | | SharePoint and OneDrive | Provider keys/descriptors reserved; live Microsoft Graph browse/import is planned | | NFS and local connector | Described as optional future providers; the local managed-storage backend is a different feature | Provider descriptors are available from `GET /api/v1/files/connectors/providers`. Use their `implemented`, `installed`, and support fields for display. An incompatible optional SDK release fails closed before it can return a usable client or session. ## Operator runbook ### Storage configuration | Setting | Default | Purpose | | --- | --- | --- | | `FILE_STORAGE_BACKEND` | `local` | Selects `local` or `s3` managed blob storage | | `FILE_STORAGE_LOCAL_ROOT` | `runtime/files` | Primary local write/read root | | `FILE_STORAGE_LOCAL_FALLBACK_ROOTS` | empty | Comma-separated older read-only roots checked after the primary root | | `FILE_STORAGE_S3_ENDPOINT_URL` and related `FILE_STORAGE_S3_*` values | deployment-specific | S3-compatible endpoint, region, credentials, and bucket | | `FILE_STORAGE_S3_DEPLOYMENT_MANAGED` | `false` | Installer-only trust marker for the exact `http://garage:3900` service; never use it for another endpoint | | `FILE_STORAGE_S3_ENDPOINT_TRUSTED` | `false` | Deployment-owner acknowledgement for one clean HTTPS external S3 origin; never expose this through connector configuration | | `GOVOPLAN_STATE_PROFILE` | `local` | Selects `local`, one-host `host-shared`, or multi-host `shared` state validation | | `FILE_UPLOAD_MAX_BYTES` | 50 MiB | Direct-upload and extracted archive-member maximum | | `FILE_UPLOAD_ZIP_MAX_BYTES` | 250 MiB | Compressed archive request maximum (legacy name retained for compatibility) | | `FILE_ARCHIVE_MAX_ENTRIES` | 10,000 | Maximum declared archive entries | | `FILE_ARCHIVE_MAX_EXPANDED_BYTES` | 2 GiB | Maximum expanded archive bytes | | `FILE_ARCHIVE_MAX_EXPANSION_RATIO` | 100 | Maximum expanded-to-compressed ratio | | `FILE_ARCHIVE_PREVIEW_TTL_SECONDS` | 1,800 | Lifetime of the sealed archive preview token | | `MASTER_KEY_B64` | development fallback only | Encrypts database-managed connector secrets | The local backend is the operational baseline. It resolves every storage key under the configured root and rejects escape attempts. Fallback roots support a controlled storage-root migration: new writes go to the primary root while reads can still find older objects. The supported installer may provision a deployment-owned Garage service at the exact `http://garage:3900` endpoint and set `FILE_STORAGE_S3_DEPLOYMENT_MANAGED=true`. An operator-selected external S3 backend instead requires a clean HTTPS origin and `FILE_STORAGE_S3_ENDPOINT_TRUSTED=true`. Both are deployment authority, not a general connector or private-network bypass. Core owns the shared backend implementation; Files owns metadata and the Files key namespace. Multiple API replicas require the same durable blob namespace. Separate local container filesystems will produce incomplete reads. Use `host-shared` with one durable shared mount only for same-host replicas. Independent hosts require the `shared` profile with external S3, PostgreSQL, Redis, a stable installation id, and one immutable module composition. ### Connector egress Connector access to private networks is a deployment-wide decision: ```text GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS=true|false ``` Production-like configuration validation requires an explicit value. Public-only mode rejects any hostname whose DNS answers include a non-public address. Private-enabled mode still rejects link-local, multicast, unspecified, and limited-broadcast addresses. The built-in HTTP transport: - resolves and validates every connection attempt; - connects the socket to the exact approved address while retaining the original hostname for HTTP Host, TLS SNI, and certificate verification; - does not inherit proxy settings; - refuses redirects instead of following a new peer implicitly; - bounds structured responses to 16 MiB and file transfers to 512 MiB by default. The S3 SDK adapter applies the same socket rule to every botocore pool selected for a retry, redirect, discovered endpoint, or virtual-host bucket alias. The original authority remains in the request and TLS SNI/certificate check. S3 connector clients use no outbound proxy and never discover ambient AWS credentials: configure both access and secret keys on the governed profile, or use an anonymous profile for a public source. The SMB adapter owns a separate connection cache and replaces smbprotocol's TCP factory process-wide with the stricter pinned socket. Initial peers, reconnects, server aliases, domain-controller connections, and DFS referral targets therefore pass the same policy at connection time. Signing is required by default; enable SMB encryption on the profile where the server supports it. Override the connector response limits with `GOVOPLAN_CONNECTOR_MAX_STRUCTURED_RESPONSE_BYTES` and `GOVOPLAN_CONNECTOR_MAX_FILE_TRANSFER_BYTES`. The smaller applicable limit wins when an import is also subject to `FILE_UPLOAD_MAX_BYTES`. Never work around a connector pinning failure by adding a raw IP, disabling TLS, or enabling private networks. A failure means the peer policy rejected an actual connection destination or the installed SDK no longer exposes the verified transport seam. The separately configured platform S3 backend is trusted only by the deployment owner and is not selectable by a user or connector profile. ### Backup and restore The database and blob namespace are one logical backup set. A usable backup must include: - Files database rows, including asset/version/blob relationships, shares, connector settings, and campaign attachment-use evidence; - every object below `FILE_STORAGE_LOCAL_ROOT` and any still-used fallback root; or the complete S3 bucket/prefix and version/lifecycle evidence for an S3 backend; - the exact `MASTER_KEY_B64` needed to decrypt retained connector credentials; - deployment-owned connector profile files, referenced CA bundles, and secret environment configuration where those definitions are in use. There is no Files backup/restore API. Use a write quiesce or coordinated snapshots so database references and objects represent the same recovery point. The integrity API verifies a restored set, but it does not replace a coordinated backup. Operators normally use **Administration > File integrity**. The equivalent API creates a scan with `POST /api/v1/files/integrity/scans`, then calls `POST /api/v1/files/integrity/scans/{scan_id}/run` with the scan's current `expected_revision` until it reports `completed`. Each call advances at most the persisted batch size, so a stopped operator or worker can resume from the committed blob/object cursors. Concurrent or stale actions receive `409` before the storage backend is invoked; reload the scan and inspect the newer state. Findings distinguish: - `missing`: metadata references an absent object; - `size_mismatch` or `checksum_mismatch`: bytes do not match immutable blob metadata and the blob is quarantined; - `orphan_object`: an object exists in the tenant Files prefix without a corresponding blob row. Missing or corrupt blobs fail closed for ordinary downloads and Campaign attachment materialization. After restoring the expected bytes, use the finding `recheck` action with its current `expected_revision`. Orphan cleanup starts with a dry-run preview and requires separate destructive confirmation. The confirmation reuses the finding revision from that preview, rechecks that no database reference exists, remains scoped to the scanned tenant prefix, and is idempotent. Both applied and dry-run actions emit audit evidence. A shared reference blocks orphan cleanup. Managed-asset purge separately enforces asset retention, legal hold, active shares, and retained Campaign/Form evidence before releasing a blob for fenced garbage collection. ### Recovery ledger for object effects On PostgreSQL, every managed blob creation or integrity repair starts a Core recovery operation in an independent committed transaction before Files protects or writes bytes. The operation records tenant/blob identifiers, an opaque object locator or locator digest, semantic SHA-256/size evidence, the recovery mode, and a distributed lease fence. It never records file contents, ZIP passwords, connector credentials, or a newly uploaded filename. New object keys are opaque; legacy filename-bearing keys remain readable but repair operations record only their digest and recover through the blob ID. SQLite is a supported local-development database but permits only one writer. Files therefore uses an explicit reduced-durability mode there: recovery intent and its lease are written in the caller transaction, while a process-local fence prevents competing effects in the same runtime. Commit makes the intent durable before independent verification. A handled rollback reconstructs a durable recovery operation and verifies compensation or forward completion. A hard process loss before commit can leave an object without a surviving ledger row, so SQLite is not a production recovery profile; after such a loss, run a complete Files integrity scan and reconcile every reported orphan before resuming writes. PostgreSQL retains the independent pre-effect durability guarantee. The Files business transaction then creates or updates the blob, version, and asset rows. Its actual SQLAlchemy commit or rollback settles every pending operation: - commit reloads the blob through an independent session and streams the object to verify its stored-byte SHA-256 and size before recording success; - rollback deletes only a newly reserved object after independently proving that no `FileBlob` references it, then records verified compensation; - a repaired existing object is forward-completed only when its identity, envelope, semantic evidence, and stored bytes all match; - missing or mismatched bytes quarantine a committed blob and leave the operation `recovery_required`; an unavailable probe remains `outcome_unknown` rather than becoming an ordinary upload failure. Applied orphan cleanup has its own forward-recovery operation. The database reference check and tenant-prefix check happen before deletion; object absence and the durable finding state are verified afterward. If the caller transaction rolls back after deletion, Files may forward-complete only that existing finding after rechecking that the key is still unreferenced. Hard purge uses an irreversible Core recovery plan with an approval reference and a tenant-wide purge lease. The preview hash binds target IDs, deletion state, lifecycle revisions, holds, deadlines, blockers, and blob IDs. Asset and version removal plus audit evidence commit together. Released objects are not deleted by that transaction. Blob GC later takes `files:blob::`, the same distributed resource fence as upload/repair, locks and rechecks the blob, and verifies object absence before removing metadata. Provider or database ambiguity remains recovery-required or outcome-unknown in Ops. Explicit S3 connector writes use forward recovery because the provider cannot join the database transaction. The durable request contains only tenant, profile, opaque target digest, content SHA-256/size, and expected revision. A conditional create or overwrite is followed by a metadata probe for the exact content and operation markers. Never retry an unresolved target with another request key; reconcile the owning Files operation from provider evidence first. Archive preview and confirmation use bounded process-local temporary staging. Staging is not authoritative and is removed on every handled exit; extracted members enter the same per-blob recovery boundary as direct uploads. A hard process loss may leave a temporary OS file for normal host temporary-file cleanup, but cannot make that staging path a managed Files object. Use the Ops recovery-operation view to inspect `files` operations. Do not retry a busy or unresolved blob or connector path blindly: first verify the FileBlob row and object hash, or the remote request/content markers and revision, plus any Encryption envelope named by a managed blob. After restore: 1. Verify the active tenant and module migration state. 2. Verify the storage backend and roots before allowing writes. 3. Verify that the original master key is available before testing connectors. 4. Download representative files and compare bytes with their recorded SHA-256. 5. Test one authorized and one unauthorized owner/share path. 6. Test a permitted pinned HTTP connector, if connectors are configured. 7. Review audit and change-sequence continuity around the recovery point. An inconsistent restore should fail closed for missing objects or undecryptable credentials. Do not repair it by deleting evidence rows without an approved, audited data-recovery decision. ### Disable, uninstall, and retire Disabling a module preserves its persistent data. Ordinary uninstall is guarded while Files tables contain persistent rows. Destructive retirement is separate and irreversible at the application level. The installer records a database snapshot, then Files scrubs and audits remaining encrypted connector material before its database tables are dropped. Legacy external secret references are detached and identified as non-owned; Files never calls a provider delete operation for them. The retirement executor drops database tables but does not delete corresponding objects from the configured blob backend. Operators must include those objects in the approved retention/destruction plan, report them with an integrity scan, and explicitly approve cleanup. Validate the installer snapshot and independent blob backup before retirement. ### Operational signals Use these symptoms as routing hints: | Symptom | Likely boundary | | --- | --- | | `Stored object does not exist` | Database/blob restore mismatch, wrong root, or missing shared storage | | `Stored secret cannot be decrypted` | Wrong or rotated `MASTER_KEY_B64` | | Private/non-public endpoint blocked | Deployment-wide egress policy is public-only or DNS returned a forbidden answer | | SDK peer-pinning seam is unavailable | Optional S3/SMB SDK is incompatible; keep access fail-closed and validate the supported dependency range before upgrade | | Connector response exceeds limit | Remote payload exceeds connector or upload limit | | Profile is not visible | Scope, disabled state, campaign access, or policy mismatch | | Group removal is vetoed | The group still owns a file/folder/connector space or is a share target | ## Capabilities and integration Other modules must integrate through Core contracts, Files capabilities, or the public HTTP API. They must not import Files ORM models or storage helpers. ### Provided capabilities | Capability | Purpose | | --- | --- | | `files.access` (`0.1.6`) | Explain resource access provenance for managed files, explicit folders, and virtual folders | | `files.campaign_attachments` (`0.1.6`) | Resolve managed attachment matches, prepare frozen campaign snapshots, annotate built messages, share assets with a campaign, and record/mark exact attachment use | | `records.source.files` (`1.0.0`) | Recheck current Files access and resolve one exact, integrity-approved managed file version for Records filing | | `forms_runtime.evidence.files` (`1.0.0`) | Issue a one-time managed attachment grant and re-verify the exact file/version/checksum at Form submission | Files requires Core principal resolution and permission evaluation. Campaign is an optional dependency; when installed, Files consumes the optional `campaigns.access` interface to verify campaign existence and access. Missing optional Campaign support fails explicitly rather than bypassing the check. Records is also optional. When enabled, the source capability returns the requested `FileVersion` identity, path snapshot, content metadata, SHA-256, integrity/protection state, and launch link. It rejects mutable aliases, cross-tenant requests, missing access, and quarantined or failed blobs. Records stores the filing decision; Files continues to own the version and bytes. Forms Runtime is optional as well. Its public or authenticated participant never receives general Files access through this integration. Forms Runtime asks for a purpose-bound grant, Files verifies an active same-tenant user custodian, stores only the token digest, accepts one bounded upload, and returns an immutable `EvidenceReference`. Draft save and final submit independently recheck the exact Form instance/revision, grant, asset, version, checksum, deletion state, and integrity state. An idempotent replay returns the existing grant without reproducing its bearer secret. ### API families All routes below are under `/api/v1/files`. | Area | Routes | | --- | --- | | Spaces and content | `GET /spaces`, `GET /`, `GET /folders`, `GET /delta` | | Upload and folders | `POST /upload`, `POST /upload-zip` (compatibility), `POST /archive-preview`, `POST /archive-confirm`, `POST /folders`, `POST /folders/delete` | | File access | `GET /{file_id}`, `GET /{file_id}/download`, `DELETE /{file_id}`, `POST /bulk-delete` | | Organization | `POST /bulk-rename`, `POST /transfer`, `POST /archive.zip`, `POST /resolve-patterns` | | Sharing | `POST /{file_id}/shares`, `POST /bulk-shares` | | Connector spaces | `GET/POST /connector-spaces`, `PATCH/DELETE /connector-spaces/{space_id}`, `POST /connector-spaces/{space_id}/restore`, `POST /connector-spaces/{space_id}/sync`, `POST /connector-spaces/{space_id}/write-back` | | Connector catalog/discovery | `GET /connectors/providers`, `POST /connectors/discover` | | Connector profiles | `GET/POST /connectors/profiles`, `GET/PATCH/DELETE /connectors/profiles/{profile_id}` | | Browse/import/sync | `GET /connectors/profiles/{profile_id}/browse`, `POST /connectors/profiles/{profile_id}/import`, `POST /connectors/profiles/{profile_id}/sync` | | Restore and lifecycle | `POST /assets/{file_id}/restore`, `POST /folders/restore`, `PATCH /{file_id}/lifecycle` | | Governed erasure | `POST /purge/preview`, `POST /purge/execute`, `POST /purge/blobs` | | Credentials | `GET/POST /connectors/credentials`, `GET/PATCH/DELETE /connectors/credentials/{credential_id}` | | Policy | `GET/PUT /connectors/policies/{scope_type}`, `POST /connector-policy/evaluate` | | Incremental connector settings | `GET /connectors/settings/delta` | | Form evidence | `POST /form-evidence/upload` with a short-lived `X-Form-Evidence-Token` issued by Forms Runtime | The Files workspace exposes **Remove space** only for connector spaces and only to actors with file-organization authority over the owning user or group space. Confirmation explains the exact boundary: removal soft-deletes the local connector-space definition and makes that virtual view disappear. It does not mutate or delete remote provider content, previously imported managed files, their metadata or shares, the connector profile, credentials, or remote object references. Those retained objects therefore do not block removal and the connector location can be linked again later. User and group managed spaces are intrinsic ownership scopes rather than removable records, so they never offer this action. A missing, already removed, cross-tenant, or inaccessible connector space fails through the backend lookup and owner-access checks. Consumers should use cursor/watermark contracts instead of assuming an unbounded complete list. The default full-list page size is 500 and public page sizes are capped at 1,000. ### Integration invariants An integrating module should: - ask Files/Core for access rather than trusting a submitted file ID; - retain an exact version/blob/checksum at every governed evidence point; - import external content before using it in a campaign, report, workflow, or generated document; - preserve Files provenance when producing a derived managed snapshot; - treat source provenance as captured context, not cryptographic attestation; - avoid writing remote providers through the baseline connector layer; - define a separate capability when it needs behavior beyond managed snapshots; - tolerate Files being absent when the integration is declared optional. Future Postbox, Templates/Reports, BI, Documents, DMS, and workflow modules should keep their own domain state and use Files for governed input/output snapshots. Long-running provider sync, OAuth, remote mutation, and provider-specific health belong in connector modules rather than expanding the Files baseline indiscriminately. ## Security, provenance, audit, deletion, and retention ### Security controls implemented - Core authentication, tenant scoping, CSRF/API handling, and RBAC protect the public routes. - Owner and share checks protect individual resources after operation-scope checks. - Logical paths reject traversal and storage keys cannot escape the local root. - Upload, archive extraction, connector response, and S3 stream code use bounded reads. Archive previews are sealed and short-lived; ZIP passwords are request-only. - Public Form evidence uploads require a custom-header bearer grant that is stored only as SHA-256, expires after at most 15 minutes, is bound to one exact submission and user custodian, and can create only one managed file. - Connector HTTP sockets use connection-time DNS/IP validation and pinning, redirects are refused, and unsafe SDK transports fail before client creation. - Database-managed connector passwords/tokens are encrypted; responses redact secrets and deployment references. - API metadata is recursively checked for secret-like values. - Downloads use sanitized attachment filenames. - Plaintext semantic and stored-byte SHA-256/size evidence are recorded for every protected blob; they are identical for unprotected blobs. - Upload and archive-confirm APIs can select an Encryption vault. Protected writes and reads fail closed if the optional Encryption capability is absent. - Managed-object writes and applied orphan cleanup start lease-fenced Core recovery operations before their physical effects; terminal success and compensation require independent database and object checks. The module does **not** currently provide malware scanning, content disarm and reconstruction, a file-type allowlist, per-user quota, automatic encryption policy assignment, client E2EE, or a dedicated preview sandbox. The optional server-envelope profile protects selected managed blob bytes at rest but remains server-decryptable. Deployments that require the other controls must supply them outside Files until explicit module contracts exist. ### Provenance Connector-originated assets can retain: - source type; - connector/profile ID and provider; - external ID, path, and URL; - revision and revision label; - observation/import timestamps when supplied; - selected provider metadata. The normalized provenance and source revision are returned in file responses, carried into campaign attachment matches, and included in connector audit events. External metadata is provider/user input and is not a digital signature. ### Audit and change evidence Files records canonical audit events for: - connector discovery attempts, before the attempted external I/O; - connector imports, selected-file syncs, and bounded folder sync summaries; - download/archive access to connector-originated managed files; - immediate connector profile and credential deletion/scrubbing; - credential scrubbing during destructive module retirement. Connector audit details include the managed asset/version/blob, checksum, size, operation, source revision, and provenance where applicable. Deletion audit details name secret/reference kinds but never the secret values. `files.connector.folder_synced` additionally records the connector profile, provider, library/root, managed target, traversal mode, truncation state, typed counts, and bounded per-item source/target/action references. It does not embed downloaded bytes, credentials, or transport content. Assets, folders, shares, profiles, credentials, policies, and connector spaces also feed Core's incremental change sequence for UI synchronization. A change entry is not equivalent to a canonical audit event. Ordinary local upload, rename, share, and soft-delete operations do not yet all emit dedicated Files audit events. Campaign attachment-use records provide separate domain evidence for an exact file version used in campaign preparation and delivery. ### Deletion semantics The word "delete" has different meanings by object type: | Object | Current delete behavior | | --- | --- | | File asset | Sets `deleted_at`; content, versions, blob references, and campaign evidence remain | | Folder | Sets `deleted_at`; recursive deletion also soft-deletes descendants | | Connector space | Sets `deleted_at` and disables the space | | Connector credential | Immediately disables the tombstone and clears username, encrypted password/token, environment references, legacy external reference, mode, and private metadata; dependent profiles are disabled and detached | | Connector profile | Immediately disables the tombstone and clears credential links/material/references and private metadata | | Legacy `secret_ref` | Detached and audited as an unowned external reference; no provider deletion is attempted or claimed | | Module retirement | Scrubs/audits credential material, then drops Files database tables; blob-backend cleanup is an operator responsibility | | Governed hard purge | Removes only preview-matched, soft-deleted asset/version rows without active retention, legal hold, active shares, Campaign evidence, or Form evidence | | Blob garbage collection | Deletes an exact managed object only after a fresh zero-reference check under the shared blob fence; metadata follows verified object absence | | Connector write-back | Explicit conditional S3 create/overwrite only; automatic provider delete, rename, move, and ACL propagation remain disabled | Credential/profile scrubbing and its audit event use the same database transaction. If audit creation fails, the deletion rolls back. Repeating a delete against an already scrubbed tombstone does not recreate secret evidence. ### Retention boundary Each asset has an enforceable retained-until value, legal-hold flag, reason, and optimistic lifecycle revision. These controls block hard purge; they do not automatically schedule it. Restore, purge preview/execute, and blob GC are explicit authorized operations. Blob `retained_until` remains an additional storage-level safeguard and must also have expired before automated collection is introduced. Campaign and Form evidence are hard blockers rather than silently cascaded references. Do not equate soft deletion with erasure. Erasure is complete only after the approved purge removes asset/version metadata, bounded GC verifies that no version references the blob and removes its bytes/metadata, and any owning Encryption retention/key-custody consequence has been handled under that module's policy. ## Acceptance scenarios These scenarios describe expected behavior at the current boundary. ### Personal and group ownership Given Alice has `files:file:upload` and belongs to Finance, when she uploads to her personal or Finance space, then the asset has the selected owner and tenant. Given Bob is outside Finance and has no share or Files admin permission, the same asset ID must not make the asset readable to Bob. ### Safe archive import Given an archive member contains `../../secret.txt`, is a link or special filesystem object, exceeds 50 MiB, pushes actual expanded bytes above 2 GiB, or exceeds the 100:1 expansion ratio, preview or confirmation must fail without a committed managed asset. A password-protected ZIP requires the correct request-only password. A valid confirmation must match its unexpired preview token and preserve normalized relative paths below the chosen logical folder. ### Explicit conflicts Given a target path already exists, `reject` must leave it unchanged, `rename` must choose a non-conflicting path, and `overwrite` must soft-delete the old asset before creating the replacement. A per-item `skip` must not create that item. ### Policy-denied connector Given the tenant permits WebDAV only below `departments/finance` and denies its `private` child, a browse/import/sync request below the denied child must return an explainable policy denial. Directly invoking the import endpoint must not bypass the same rule. ### Pinned connector transport Given public-only mode and DNS returns any private address, the connection must be rejected before a socket opens. Given private mode, every HTTP, S3, and SMB connection must still use an address from the answer validated for that exact attempt. Botocore retries, redirects, endpoint discovery, and aliases, plus SMB reconnects and DFS referrals, must pass through the pinned factories. A changed or unsupported SDK seam must fail before a usable client/session is returned. SMB deployments use `smbprotocol>=1.13`; signing is required by default and SMB encryption can be required per connector profile. Private targets additionally require the governed private-network setting, but that setting never bypasses per-connection address validation. The bundled development Samba target checks live initial browse/import behavior with `smoke.py --require-smb`. It is a single-share fixture rather than a DFS topology: reconnect and referral handling is enforced by the transport contract suite, while operators using DFS must run the same required smoke check against their institutional referral topology and record the initial and referred peers as deployment evidence. ### Imported evidence and sync Given a permitted WebDAV file is imported, its managed response and audit event must carry source identity, revision when available, current version, checksum, and size. Re-syncing identical bytes must return `unchanged`; changed bytes must create a higher version while preserving the previous version. Given a permitted linked connector folder contains changed, unchanged, colliding, and policy-denied files, a manual folder sync must preserve remote relative paths, commit the allowed siblings, return one typed outcome for every discovered/skipped item, and emit a matching summary audit event. Reaching the configured file bound must mark the run truncated rather than imply complete coverage. ### Campaign freeze Given a campaign snapshot selected version V1, when the managed asset later advances to V2, the prepared/sent attachment-use evidence must still identify V1 and its original blob/checksum. ### Immediate credential deletion Given a database credential contains an encrypted password and is referenced by two profiles, deleting it must scrub the credential, disable/detach both profiles, record non-secret audit evidence, and publish connector-setting changes in one transaction. If audit creation fails, no part of the deletion may commit. ### Recovery Given a coordinated database/blob backup and the original master key, restoring it must allow representative downloads whose bytes match recorded SHA-256 values. A missing blob or wrong key must surface an error rather than silently returning different content or credentials. ## Implemented and planned boundary | Area | Implemented now | Planned or explicitly outside the current boundary | | --- | --- | --- | | Managed storage | Core local/S3 backend, exact managed-Garage or explicitly trusted HTTPS external S3, state-profile validation, fallback local read roots, tenant blob deduplication, checksums, bounded resumable integrity scans, quarantine, dry-run-first orphan cleanup, and Core-ledger verification/forward recovery | Scheduled scan execution and deployment-specific S3 HA/backup automation | | Upload | Bounded direct upload, drag-and-drop UI, archive preview/selective extraction, password-protected ZIP support, explicit conflicts, opaque new object keys, and rollback compensation | Malware scanning, quotas, type policy, resumable/chunked upload | | Organization | Folders, bulk rename preview/apply, move/copy, drag-and-drop, ZIP download, pattern resolution, and API restoration preserving versions/provenance | General file-history UI and user-driven append-version UI | | Sharing | User/group/tenant/campaign grants, expiry, idempotent revocation, searchable share-management UI, and campaign linkage display | Richer policy-driven share lifecycles | | Deletion/retention | Soft-delete and restore assets/folders/spaces; optimistic retention and legal-hold controls; preview-bound, approval-referenced hard purge; reference-checked blob GC; immediate audited connector-secret scrubbing | Automatic time-based purge scheduling and richer lifecycle administration UI | | Privacy requests | Tenant-scoped bounded DSAR metadata search; retained/manual/revoke/detach planning; idempotent share revocation and mutable actor-reference detachment; explicit separation from byte purge | Content-specific automated redaction and policy-specific approval remain manual or belong to the owning process | | Connector governance | Scoped profiles/credentials/policies, effective source explanation, separate credentials, linked user/group spaces | Provider-owned external secret lifecycle; API `secret_ref` remains rejected | | HTTP connectors | Pinned, bounded, no-redirect Seafile and WebDAV/Nextcloud browse/import, selected-file sync, and folder sync | Background sync, remote mutation, long-running transfer workers | | SMB and S3 connectors | Provider descriptors, browse/import/manual sync, pinned SDK transports, redirect/retry/referral transport-contract tests, and explicit conditional S3 write-back with Core-ledger recovery | Live topology smoke evidence, provider-specific OAuth, additional provider writes, and background indexing remain separate deployment or connector-module concerns | | Other providers | Reserved SharePoint/OneDrive keys and NFS/local descriptors | Graph/OAuth/provider paging, NFS deployment integration, DMS connectors | | Connector spaces | User/group link, browse, bounded manual file/folder sync, edit/disable/delete/restore, read-only default, and opt-in S3 two-way mode | Background sync and automatic remote delete/rename/move/ACL propagation | | Profile capabilities | Stored, displayed, and enforced for explicit connector write-back | Broader provider-specific capability negotiation | | Audit | Connector discovery/import/sync/access and connector deletion; campaign exact-use evidence | Dedicated canonical audit events for every ordinary Files mutation | | Preview | File metadata and attachment download | Dedicated safe content-preview service | | Campaign | Stable capability-based frozen attachments and sent-use evidence | Campaign-specific process state remains in Campaign | | Forms Runtime | One-time managed attachment grants plus exact-version, checksum, deletion, tenant, submission, and integrity verification | Malware scanning and advanced/qualified signature providers remain separate assurance depth | | Collaboration | Governed input/output snapshots | Co-editing, comments, review, presence, locks, and semantic document versions belong to Documents/workflow/provider modules | ## Release and change checklist Before releasing Files: 1. Keep `pyproject.toml`, root `package.json`, WebUI `package.json`, and the module manifest version aligned. Version alignment is a release gate. 2. Run the Files Python tests and security/static-analysis gates from the GovOPlaN meta repository. 3. Build/type-check the WebUI through the Core host application. 4. Verify database migrations on an upgrade copy and a clean database. 5. Exercise an allowed and denied owner/share path. 6. Exercise upload, ZIP bounds, conflict handling, download, and soft deletion. 7. Exercise connector policy explanation and one pinned HTTP provider where configured; verify the deployment-managed or trusted external S3 backend if selected, and verify one configured S3 and SMB connector while recording the actual target topology and private-network policy. 8. Verify credential deletion scrubs dependents and produces audit evidence. 9. Verify a campaign attachment snapshot still identifies its exact version and checksum after the current file changes. 10. Exercise a committed upload, a rolled-back upload, object tamper detection, and applied orphan cleanup; inspect their `files` operations in Ops. 11. Exercise a Form evidence grant, token replay, wrong-submission reference, expired token, unsupported media type, and quarantined-file rejection. 12. Exercise a Files DSAR search/plan, repeat an approved reversible action, and confirm retained file content can only be erased through the separate purge authority and recovery path. 13. Update the implemented/planned table whenever a boundary changes. ## Related documents - [Repository overview](../README.md) - [Connector ownership boundary](CONNECTOR_BOUNDARY.md) - [Connector spaces design and implementation history](CONNECTOR_SPACES.md) - [Document collaboration boundary](DOCUMENT_COLLABORATION_BOUNDARY.md) Where an older planning section conflicts with current code or this handbook's implemented/planned table, verify the code and update both documents in the same reviewable documentation slice.