Skip to content

Soft delete and restore media assets - #2555

Merged
pournasserian merged 36 commits into
v2from
2553-media-soft-delete
Oct 26, 2026
Merged

pournasserian merged 36 commits into
v2from
2553-media-soft-delete

Conversation

@pournasserian

@pournasserian pournasserian commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Closes #2553

Built on media storage (#2543), media in change sets (#2546) and the alt text check (#2549), all merged.

What

The last slice of F-MED (#2427): assets are soft deleted and can be restored within the retention window, as entries are (#2502, CNT-06, REL-03).

  • Core (IMediaLibrary):
    • DeleteAsync(id, baseVersion) and RestoreAsync(id).
    • ListDeletedAsync(), which returns each DeletedAsset with when it was deleted and until when it can be restored, most recent first.
    • New codes asset_not_deleted and retention_expired.
  • Gone from every read: a deleted asset is not found by get, versions, the live version, open or metadata updates, and it leaves lists and folders. An entry write that references it fails with reference_not_found. A change set operation pointing at it cannot be added, and a set that already holds one fails to publish until the asset is restored, as for deleted entries.
  • Restore clears the deletion while DeletedAt + Content__DeletedRetention is still in the future. Versions, bytes and the published pointer are never touched, so the asset comes back exactly as it was.
  • Permissions: delete and restore take the delete operation of an agent's scope (MCP-05).
  • Infrastructure:
    • New column MediaAssets.DeletedAt.
    • Delete and restore are conditional updates, so concurrent calls get version_conflict or asset_not_deleted instead of both succeeding.
    • Events media.deleted (with the latest version's row as previous version) and media.restored, appended in the same unit of work.
    • Migrations for SQLite and PostgreSQL.

Why

CNT-06 and REL-03 require that nothing is hard-deleted inside the window. Media follows the entry design so that a person restores both the same way.

Choices worth a look:

  • No purge, and no tool, as for entries. A purge needs a decision on what it removes, then a job. Deleting stays a person's action. delete_entry and media delete operations in change sets only unpublish.
  • The where_used tool still lists the pages that show a deleted asset, so an agent can say what the delete affects. The admin asset page shows a deleted asset as not found, like every other read.
  • The media library screen is not in this epic's PRs: it depends on the desk's layout (F-DSK).

Tests

Integration tests, each on SQLite and PostgreSQL:

  • Deleted asset is gone: it is not found by get, version, open or update. It leaves the list and the folders. A product whose hero names it fails with reference_not_found. media.deleted names it and its version row.
  • Restore inside the window: a restore one minute before the window closes brings back version 2 with its alt text, published version 1 and its bytes. It empties the deleted list and appends media.restored.
  • Restore after the window: at the end of the window, restore fails with retention_expired and appends nothing.
  • Failures: each of these fails with its own code:
    • a stale base version;
    • restoring an asset that is not deleted;
    • unknown assets;
    • a second delete.
  • Deleted list: it is ordered by deletion time and says until when each asset can be restored.
  • Approved set: a set whose media create was approved fails to publish once the asset is deleted, and a new set cannot add it. After a restore, the set publishes.
  • Agent scope: an agent without the delete operation is refused delete and restore, and one with it can delete.

The full suite ran locally against a postgres:17 container as well as SQLite. The migration was regenerated on top of ProposedMediaVersions (#2549). The migration tests apply and revert it on SQLite and PostgreSQL. No new package and no new project reference.

Review

An independent review gave MERGE, with Low findings:

Checklist

  • Linked to an issue a maintainer has confirmed
  • Targets v2
  • Tests or docs updated where behaviour changed
  • No tool-generated co-author trailers or footers

@pournasserian pournasserian added this to the M1 Governance core milestone Oct 19, 2026
@pournasserian pournasserian added type:feature New capability v2 FluentCMS v2 (v2 branch) area:media Media library (MED) labels Oct 19, 2026
# Conflicts:
#	CONTRIBUTING.md
#	src/FluentCMS.Infrastructure/Persistence/Migrations/PostgreSql/PostgreSqlFluentCmsDbContextModelSnapshot.cs
#	src/FluentCMS.Infrastructure/Persistence/Migrations/Sqlite/SqliteFluentCmsDbContextModelSnapshot.cs
# Conflicts:
#	tests/FluentCMS.IntegrationTests/Mcp/McpAgents.cs
# Conflicts:
#	tests/FluentCMS.IntegrationTests/Mcp/McpAgents.cs
# Conflicts:
#	CONTRIBUTING.md
#	src/FluentCMS.Core/Builtin/Tools/ToolJson.cs
#	src/FluentCMS.Core/Media/IMediaLibrary.cs
#	src/FluentCMS.Core/Tools/ToolArgumentsReader.cs
#	src/FluentCMS.Infrastructure/Media/MediaAssetRows.cs
#	src/FluentCMS.Infrastructure/Media/MediaLibrary.cs
#	tests/FluentCMS.IntegrationTests/Mcp/ConnectedAgent.cs
#	tests/FluentCMS.IntegrationTests/Mcp/McpAgents.cs
#	tests/FluentCMS.Tests/Tools/McpCoverageTests.cs
#	tests/FluentCMS.Tests/Tools/ToolProviderConformanceTests.cs
# Conflicts:
#	src/FluentCMS.Core/Builtin/Tools/MediaTools.cs
#	tests/FluentCMS.IntegrationTests/Mcp/MediaToolsTests.cs
# Conflicts:
#	src/FluentCMS.Core/Builtin/Checks/AltTextCheckType.cs
#	src/FluentCMS.Core/Builtin/Tools/CheckTools.cs
#	src/FluentCMS.Core/Checks/ChangeSetChecks.cs
#	src/FluentCMS.Core/Checks/CheckInput.cs
#	tests/FluentCMS.IntegrationTests/Mcp/MediaToolsTests.cs
#	tests/FluentCMS.IntegrationTests/Media/AltTextCheckTests.cs
@pournasserian
pournasserian merged commit 5ad30ce into v2 Oct 26, 2026
2 checks passed
@pournasserian
pournasserian deleted the 2553-media-soft-delete branch October 26, 2026 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:media Media library (MED) type:feature New capability v2 FluentCMS v2 (v2 branch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Soft delete and restore media assets

1 participant