feat: govern file lifecycle and connector writes
This commit is contained in:
@@ -177,6 +177,26 @@ PERMISSIONS = (
|
||||
"Delete files",
|
||||
"Delete or hide managed files and folders where policy allows it.",
|
||||
),
|
||||
_permission(
|
||||
"files:file:restore",
|
||||
"Restore files",
|
||||
"Restore soft-deleted managed files, folders, and connector spaces.",
|
||||
),
|
||||
_permission(
|
||||
"files:file:retention",
|
||||
"Govern file retention",
|
||||
"Set managed-file retention deadlines and legal holds.",
|
||||
),
|
||||
_permission(
|
||||
"files:file:purge",
|
||||
"Purge files",
|
||||
"Irreversibly purge eligible file records and unreferenced managed blobs.",
|
||||
),
|
||||
_permission(
|
||||
"files:connector:write",
|
||||
"Write connected files",
|
||||
"Write managed file bytes to explicitly writable remote connector spaces.",
|
||||
),
|
||||
_permission(
|
||||
"files:file:admin",
|
||||
"Administer file spaces",
|
||||
@@ -196,6 +216,8 @@ ROLE_TEMPLATES = (
|
||||
"files:file:organize",
|
||||
"files:file:share",
|
||||
"files:file:delete",
|
||||
"files:file:restore",
|
||||
"files:file:retention",
|
||||
),
|
||||
),
|
||||
RoleTemplate(
|
||||
@@ -370,7 +392,7 @@ REMOTE_STORAGE_PROVIDER = ExternalProviderDeclaration(
|
||||
module_id="files",
|
||||
label="Remote file storage mirror",
|
||||
maturity="synchronize",
|
||||
operations=("discover", "search", "read", "synchronize", "preview"),
|
||||
operations=("discover", "search", "read", "synchronize", "preview", "write"),
|
||||
objects=(
|
||||
ProviderObjectDeclaration(
|
||||
object_type="remote_folder",
|
||||
@@ -394,17 +416,18 @@ REMOTE_STORAGE_PROVIDER = ExternalProviderDeclaration(
|
||||
idempotency="Source profile, remote object reference, and revision/digest suppress duplicate managed versions.",
|
||||
retry="Operators repeat bounded browse/import after a classified transport failure; effects are not blindly retried.",
|
||||
timeout_seconds=30,
|
||||
conflicts="Managed-file conflict policy is explicit; the current provider never mutates the remote source.",
|
||||
outcome_unknown="An interrupted download is discarded unless its complete digest and managed version commit are confirmed.",
|
||||
conflicts="Managed-file conflicts are explicit. S3 write-back requires a conditional create or a matching expected revision and never overwrites blindly.",
|
||||
outcome_unknown="Interrupted downloads are discarded unless their managed version commits. An uncertain S3 write remains outcome-unknown or recovery-required until provider request/content markers are reconciled.",
|
||||
outcome_unknown_supported=True,
|
||||
evidence="Managed versions retain connector profile, remote object identity, source revision, digest, and acquisition time.",
|
||||
audit_event_types=(
|
||||
"files.connector.accessed",
|
||||
"files.connector.imported",
|
||||
"files.connector.synced",
|
||||
"files.connector.written",
|
||||
),
|
||||
correction="A later acquisition creates a new managed version and preserves prior provenance.",
|
||||
rollback="Remote reads require no remote rollback; incomplete local objects are reconciled as orphans.",
|
||||
correction="A later acquisition creates a new managed version and preserves prior provenance; a later S3 correction is another explicitly revision-guarded write.",
|
||||
rollback="Remote reads require no remote rollback. S3 writes use forward recovery because provider effects cannot join the Files database transaction.",
|
||||
compensation="A wrongly imported managed version can be retired under Files policy without deleting the source.",
|
||||
reconciliation="Re-read source metadata and digest, then compare the committed managed version and object-store inventory.",
|
||||
outage="Previously imported managed versions remain available while remote spaces report unknown or stale state.",
|
||||
@@ -1041,7 +1064,7 @@ manifest = ModuleManifest(
|
||||
summary="Soft-delete accessible managed files or a folder tree where current policy allows it.",
|
||||
body=(
|
||||
"Deletion hides the selected managed assets rather than hard-purging their stored evidence. Folder deletion is recursive by default and includes child folders and files; a non-recursive request fails for a non-empty folder. "
|
||||
"There is no self-service restore or hard-purge workflow today."
|
||||
"Authorized restoration reactivates the same asset, versions, provenance, folder tree, or connector-space link when its path or label is still free. Irreversible purge is a separate administrator workflow with retention and legal-hold checks."
|
||||
),
|
||||
layer="configured",
|
||||
documentation_types=("user",),
|
||||
@@ -1081,7 +1104,7 @@ manifest = ModuleManifest(
|
||||
],
|
||||
"limitations": [
|
||||
"Deletion is soft deletion, not a hard purge.",
|
||||
"There is no self-service restore or hard-purge workflow.",
|
||||
"Restore fails closed when an active file, folder, or connector space already uses the path or label.",
|
||||
],
|
||||
"outcome": "The selected content is hidden from active Files views under the current soft-delete model.",
|
||||
"verification": "Confirm the deleted paths no longer appear in the active space; do not treat the action as physical erasure.",
|
||||
@@ -1091,6 +1114,72 @@ manifest = ModuleManifest(
|
||||
],
|
||||
},
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="files.workflow.restore-retain-and-purge",
|
||||
title="Restore, retain, hold, and irreversibly purge managed files",
|
||||
summary="Restore soft-deleted resources and govern irreversible erasure with retention, legal-hold, preview, approval, and recovery evidence.",
|
||||
body=(
|
||||
"Restore keeps the original asset identity, versions, blob references, and connector provenance. A retention administrator can set an explicit retained-until time or legal hold with a reason and revision check. Hard purge requires the dedicated purge permission, a current immutable preview hash, a caller idempotency key, the literal PURGE confirmation, and an approval reference. Active retention, legal hold, active shares, Campaign evidence, and Form evidence block purge. Purge removes eligible database records first and releases blobs; a separate bounded garbage-collection action rechecks every FileVersion reference under the same distributed blob fence used by uploads before deleting bytes and metadata. Every irreversible action is visible through the Core recovery ledger and audit evidence."
|
||||
),
|
||||
layer="configured",
|
||||
documentation_types=("admin", "user"),
|
||||
audience=("file_manager", "file_admin", "records_manager", "operator"),
|
||||
order=46,
|
||||
conditions=(
|
||||
DocumentationCondition(
|
||||
required_modules=("files",),
|
||||
any_scopes=(
|
||||
"files:file:restore",
|
||||
"files:file:retention",
|
||||
"files:file:purge",
|
||||
),
|
||||
),
|
||||
),
|
||||
links=(
|
||||
DocumentationLink(label="Files", href="/files", kind="runtime"),
|
||||
DocumentationLink(
|
||||
label="Purge preview API",
|
||||
href="/api/v1/files/purge/preview",
|
||||
kind="api",
|
||||
),
|
||||
DocumentationLink(
|
||||
label="Files handbook",
|
||||
href="govoplan-files/docs/FILES_HANDBOOK.md",
|
||||
kind="repository",
|
||||
),
|
||||
),
|
||||
related_modules=("audit", "ops", "campaigns", "forms-runtime"),
|
||||
unlocks=(
|
||||
"Authorized operators can distinguish reversible removal from policy-governed physical erasure.",
|
||||
),
|
||||
metadata={
|
||||
"kind": "workflow",
|
||||
"route": "/files",
|
||||
"screen": "Files lifecycle",
|
||||
"help_contexts": ["files.list"],
|
||||
"prerequisites": [
|
||||
"The actor has the distinct restore, retention, or purge permission required by the intended action.",
|
||||
"A purge target is already soft-deleted and all dependent evidence or shares have been lawfully resolved.",
|
||||
],
|
||||
"steps": [
|
||||
"Set or review retention and legal-hold state using the displayed lifecycle revision.",
|
||||
"For restoration, verify the original path or label is free and restore the same resource.",
|
||||
"For erasure, create a bounded purge preview and resolve every named blocker.",
|
||||
"Execute the matching preview with an idempotency key, PURGE confirmation, and approval reference.",
|
||||
"Run bounded blob garbage collection and inspect any recovery-required or outcome-unknown operation in Ops.",
|
||||
],
|
||||
"limitations": [
|
||||
"Automatic time-based purge scheduling is not included; an authorized actor starts preview, execute, and blob collection.",
|
||||
"A protected blob's Encryption envelope follows the Encryption module's own retention and key-custody policy.",
|
||||
],
|
||||
"outcome": "Eligible metadata and unreferenced bytes are erased with separately reviewable policy, audit, and recovery evidence.",
|
||||
"verification": "Verify the asset/version rows are absent, the blob remains while referenced, unreferenced bytes are absent after GC, and every operation has a valid recovery evidence chain.",
|
||||
"related_topic_ids": [
|
||||
"files.workflow.delete-managed-files",
|
||||
"files.reference.integrity-recovery-and-fail-closed-transports",
|
||||
],
|
||||
},
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="files.governed-connectors-and-provenance",
|
||||
title="Govern file connections and credential deletion",
|
||||
@@ -1098,7 +1187,7 @@ manifest = ModuleManifest(
|
||||
body=(
|
||||
"System, tenant, and one user/group/campaign leaf form the effective policy chain: deny rules win and every configured allow rule must match. "
|
||||
"Responses redact secret values and deployment references. Deleting a database-managed credential or profile immediately scrubs Files-owned encrypted material and private metadata in the same transaction as a non-secret audit event; dependent profiles are disabled, while legacy non-owned references are only detached and audited. "
|
||||
"Removing a connector space is a separate owner-authorized operation: it retires only the local virtual-space link and leaves provider content, imported managed files and shares, profiles, credentials, and remote references untouched. Intrinsic user and group managed spaces cannot be removed."
|
||||
"Removing a connector space is a separate owner-authorized operation: it retires only the local virtual-space link and leaves provider content, imported managed files and shares, profiles, credentials, and remote references untouched. Intrinsic user and group managed spaces cannot be removed. Connector spaces remain read-only by default. An administrator may explicitly enable two-way mode only for an S3 profile carrying the write capability; each write requires separate authority, inherited path policy, a conditional create or expected revision, and durable recovery evidence. Automatic remote deletion, rename, and move propagation remain disabled."
|
||||
),
|
||||
layer="configured",
|
||||
documentation_types=("admin",),
|
||||
@@ -1169,6 +1258,7 @@ manifest = ModuleManifest(
|
||||
"Deletion and destructive retirement scrub Files-owned encrypted connector material before completion and emit non-secret audit evidence.",
|
||||
"Legacy non-owned external references are detached and audited, never sent to an arbitrary provider delete operation.",
|
||||
"Connector-space removal is local and soft; it never claims to delete remote or previously imported managed content.",
|
||||
"Two-way connector mode is explicit and S3-only; it never enables automatic remote delete, rename, move, or ACL propagation.",
|
||||
],
|
||||
"related_topic_ids": [
|
||||
"files.workflow.import-managed-snapshot",
|
||||
@@ -1183,7 +1273,7 @@ manifest = ModuleManifest(
|
||||
summary="Back up database evidence, blob ciphertext, and Encryption custody as one recovery unit, and pin every SDK-managed connector peer.",
|
||||
body=(
|
||||
"Local durable storage is the operational baseline. Recover Files from a coordinated database/blob snapshot with the matching Encryption tables and original deployment master key, then run the bounded resumable integrity scan from Administration and verify representative protected and unprotected access paths. Each scan batch and finding action requires the revision shown to the operator, so a stale screen cannot recheck or delete after concurrent reconciliation. Protected scans verify stored ciphertext before decryption and then verify plaintext semantic evidence. On PostgreSQL, managed blob creation/repair and applied orphan cleanup commit lease-fenced Core recovery intent before object effects; success, compensation, and forward completion require independent database and object checks, while mismatch is quarantined and unresolved work remains visible in Ops. Development SQLite instead records blob intent in the caller transaction to avoid a second-writer deadlock, uses a process-local fence, verifies after commit, and reconstructs durable compensation evidence after handled rollback. A hard process loss before the SQLite caller commits can therefore leave an unrecorded object; SQLite is not a production recovery profile and operators must run an integrity scan after such a loss. Missing or mismatched blobs are quarantined; orphan objects are reported before dry-run-first, explicitly authorized cleanup. "
|
||||
"S3 connector pools pin every retry, redirect, discovered endpoint, and provider alias while retaining the configured TLS authority; outbound proxies and ambient credential discovery are disabled. SMB initial connections, reconnects, aliases, and DFS referrals use a Files-owned pinned transport and cache. Both apply the deployment private-network policy immediately before each socket opens and fail closed if an SDK no longer exposes the verified transport seam. Installer-owned Garage storage is supported only at the exact deployment service endpoint with its explicit trust marker. Destructive module retirement drops database tables but does not remove backend blob objects."
|
||||
"Hard purge records irreversible intent before deleting database evidence, and blob garbage collection separately rechecks references under the shared blob lease before deleting a managed object. S3 connector write-back records digest-only forward-recovery intent before a conditional provider effect; request and content markers prove success, while mismatches remain visible in Ops and fence later writers. S3 connector pools pin every retry, redirect, discovered endpoint, and provider alias while retaining the configured TLS authority; outbound proxies and ambient credential discovery are disabled. SMB initial connections, reconnects, aliases, and DFS referrals use a Files-owned pinned transport and cache. Both apply the deployment private-network policy immediately before each socket opens and fail closed if an SDK no longer exposes the verified transport seam. Installer-owned Garage storage is supported only at the exact deployment service endpoint with its explicit trust marker. Destructive module retirement drops database tables but does not remove backend blob objects."
|
||||
),
|
||||
layer="configured",
|
||||
documentation_types=("admin",),
|
||||
@@ -1337,7 +1427,7 @@ manifest = ModuleManifest(
|
||||
summary="Check a process against the implemented Files boundary, exercise permitted and denied paths, and retain evidence before approving a release or operational use.",
|
||||
body=(
|
||||
"A process owner must distinguish implemented controls from planned capabilities before relying on Files. A release is not ready until all package and manifest versions align and representative authorization, upload limits, conflict handling, download, deletion, connector, and recovery paths have been exercised. "
|
||||
"Current limitations include no self-service restore or hard purge, no enforced retention or legal hold, and no dedicated canonical audit event for every ordinary Files mutation. Share grant, change, expiry, and revocation operations do emit dedicated audit records. Record remaining limitations in the process assessment instead of treating soft deletion or change-sequence entries as stronger evidence."
|
||||
"Restore, retention, legal hold, governed hard purge, blob garbage collection, share lifecycle, and explicit S3 write-back now emit dedicated policy, recovery, or audit evidence. Ordinary organization mutations still rely primarily on the Files change sequence rather than a dedicated canonical audit event. Record remaining limitations in the process assessment instead of treating one evidence stream as another."
|
||||
),
|
||||
layer="configured",
|
||||
documentation_types=("admin", "user"),
|
||||
@@ -1584,7 +1674,7 @@ manifest = ModuleManifest(
|
||||
documentation_ref="docs/FILES_HANDBOOK.md",
|
||||
test_ref="tests/test_storage_backends.py",
|
||||
known_limits=(
|
||||
"Target-environment multi-node recovery drills and writable remote connector effects are not reference-ready; hard purge and legal hold remain unimplemented.",
|
||||
"Target-environment multi-node recovery drills remain deployment evidence; two-way connector writes are currently limited to explicit conditional S3 writes, with automatic remote delete, rename, move, and ACL propagation disabled.",
|
||||
),
|
||||
supported_authority_modes=(
|
||||
"native_authoritative",
|
||||
|
||||
Reference in New Issue
Block a user