Provide managed generated artifact storage

This commit is contained in:
2026-08-02 12:38:15 +02:00
parent 359c4e9570
commit b993d8e31a
6 changed files with 228 additions and 2 deletions
+87 -1
View File
@@ -8,6 +8,11 @@ from sqlalchemy import or_
from govoplan_core.core.access import AccessDecisionProvenance, PrincipalRef
from govoplan_core.core.files import FileAccessProvider
from govoplan_core.core.files import (
ManagedArtifactRef,
ManagedArtifactStore,
ManagedArtifactWriteRequest,
)
from govoplan_core.core.modules import ModuleContext
from govoplan_core.security.module_permissions import scopes_grant_compatible
from govoplan_files.backend.db.models import FileAsset, FileFolder, FileShare
@@ -20,7 +25,11 @@ from govoplan_files.backend.storage.campaign_attachments import (
share_assets_with_campaign,
)
from govoplan_files.backend.storage.campaign_usage import record_campaign_attachment_uses_for_jobs
from govoplan_files.backend.storage.files import current_version_and_blob
from govoplan_files.backend.storage.files import (
create_file_asset,
current_version_and_blob,
sync_file_asset_from_source,
)
from govoplan_files.backend.storage.paths import normalize_folder
from govoplan_files.backend.storage.share_state import effective_file_share_clause
@@ -60,6 +69,83 @@ def campaign_capability(context: ModuleContext) -> FilesCampaignCapability:
return FilesCampaignCapability()
class FilesArtifactStore(ManagedArtifactStore):
def store_artifact(
self,
session: object,
principal: object,
*,
request: ManagedArtifactWriteRequest,
) -> ManagedArtifactRef:
if not hasattr(session, "query") or not hasattr(session, "flush"):
raise TypeError("Files artifact storage requires a SQLAlchemy session.")
if not hasattr(principal, "has") or not principal.has("files:file:upload"):
raise PermissionError("Managed artifact storage requires files:file:upload.")
user = getattr(principal, "user", None)
user_id = str(getattr(user, "id", "") or "")
tenant_id = str(getattr(principal, "tenant_id", "") or "")
if not user_id or not tenant_id:
raise PermissionError("Managed artifact storage requires a tenant user principal.")
metadata = dict(request.metadata)
if request.idempotency_key:
metadata["source_provenance"] = {
"source_type": "generated_artifact",
"connector_id": "files.artifact_store",
"provider": str(metadata.get("producer_module") or "platform"),
"external_id": request.idempotency_key,
"revision": str(metadata.get("output_sha256") or "") or None,
}
stored, _action, _previous_version_id = sync_file_asset_from_source(
session,
tenant_id=tenant_id,
owner_type="user",
owner_id=user_id,
user_id=user_id,
filename=request.filename,
data=request.payload,
metadata=metadata,
folder=request.folder,
content_type=request.content_type,
conflict_strategy="rename",
is_admin=principal.has("files:file:admin"),
)
else:
stored = create_file_asset(
session,
tenant_id=tenant_id,
owner_type="user",
owner_id=user_id,
user_id=user_id,
filename=request.filename,
data=request.payload,
folder=request.folder,
content_type=request.content_type,
description=request.description,
metadata=metadata,
conflict_strategy="rename",
is_admin=principal.has("files:file:admin"),
)
return ManagedArtifactRef(
file_asset_id=stored.asset.id,
file_version_id=stored.version.id,
filename=stored.version.filename_at_upload,
display_path=stored.asset.display_path,
content_type=stored.version.content_type or request.content_type,
size_bytes=stored.version.size_bytes,
sha256=stored.version.checksum_sha256,
provenance={
"module": "files",
"owner_type": stored.asset.owner_type,
"managed": True,
},
)
def artifact_store_capability(context: ModuleContext) -> FilesArtifactStore:
configure_runtime(registry=context.registry, settings=context.settings)
return FilesArtifactStore()
class FilesAccessService(FileAccessProvider):
def explain_resource_provenance(
self,