1150 lines
65 KiB
Markdown
1150 lines
65 KiB
Markdown
# 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:<tenant>:<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.
|