Repository navigation
Check alt text of the assets media fields name - #2549
Merged
Merged
Conversation
4 tasks done
# 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
…that is neither published nor in the set
… the set once per run
…ite counts as live
This was referenced Oct 26, 2026
pournasserian
added a commit
that referenced
this pull request
Oct 26, 2026
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: - content that shows a deleted image passes the alt text check and can publish, as content that references a deleted entry can today: #2665; - media permissions let an engine write assets, where content permissions refuse one; this already exists on v2: #2666; - the live version of a deleted asset was untested; the deleted-asset test now covers it, and this description is updated. ## Checklist - [x] Linked to an issue a maintainer has confirmed - [x] Targets `v2` - [x] Tests or docs updated where behaviour changed - [x] No tool-generated co-author trailers or footers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2548
Built on media in change sets (#2546) and the check engine on change sets, both merged.
What
This is the next F-MED (#2427) slice. The built-in
altTextcheck (#2535) now also checks media fields, so S2's image without alt text fails the check with a finding that names the field (CHK-01, MED-02).AltTextCheckType: when the subject has a content type, each media field, and each item of a list of media, is checked against the version of its image that readers will see:An image with no live version, one that only a change set proposes, fails with its own finding outside that set. Whatever version another set publishes later, nothing checks this content against it.
An image whose version has no alt text is a finding. The finding points at the field (
/hero,/gallery/1, or/photo/valueon an entity), names the file, and says to set alt text withupdate_media. The asset's id is the excerpt. Media that is not an image, such as a PDF, needs no alt text.IMediaLibrary.LiveVersionAsync(id, publishing): the version readers see. That is the newest version a person wrote in the library after the published one, since library writes take effect at once; otherwise it is the published version.publishingnames a version a set will publish, to ask what readers see once it has. A version that any change set has proposed counts only once its set publishes it.ProposedMediaVersions(new table, with migrations for SQLite and PostgreSQL): every media version a change set proposes, kept when the set later moves on to a newer version. This is how the library tells a person's write from a set's. The version rows cannot record it themselves, since a trigger keeps them unchanged. The change set directory adds a row whenever it records a media operation or extends one. The migration fills the table from the operations stored today, and itsDowndrops the table.CheckSubject.SetOperations(optional): the operations of the set the content goes live with, so a check can see what that set proposes.ChangeSetCheckspasses the set's operations when it runs its checks, so the set is read once per run, not once per item.check_drafttakes an optionalchange_set_id. An agent can then check a draft against the media versions its set proposes, such as alt text it just added withupdate_media. It refuses a set the agent may not read and one that is published, rejected or reverted.ChangeSet: a newer version of an asset the set already proposes moves its operation on, as for entries. Soupdate_mediaafterupload_mediain one set, which is S2, extends the upload instead of being refused as a duplicate. A media duplicate now says which version to update from, as an entry's does.MediaFields.Holdsdecides what counts as a media field.MediaToolsand the check share it.Why
The check already covered images in text, and the comment in it left assets of the library to F-MED. What goes live is what counts:
An asset the library does not hold is no finding. Entry writes already refuse such references, and check examples name made-up ids.
Choices worth a look:
ProposedMediaVersionsto read the version that goes live. A person's library write is live at once, while a set's version is live only when the set publishes it. The operation rows keep only the last version a set proposes. Without the table, a version the set moved past would pass for a library write, as the fourth review showed. Nothing else reads the table.Tests
Integration tests, each on SQLite and PostgreSQL:
/heroand/gallery/1, naming the file. The gallery item with alt text passes./photo/value. Once a version with alt text is published, the page and the entity pass. An unknown asset passes.check_draftwith the set turns from fail to pass. The set's own checks pass when it is submitted and published. Outside the set afterwards, the page passes on the person's version, while the published version stays 1.check_draftfails at/hero.update_mediaadds alt text in the same session and extends the upload to version 2.check_draftthen passes, and so does the set's own alt-text check when it is submitted./photo/valueand passes onceupdate_mediadescribes the photo in the same set.check_draftrefuses an unknown change set and a rejected one.check_draftwith thatchange_set_idfails at/hero.update_mediaadds alt text in the same set.check_draftnow passes.Unit tests:
The existing unit tests of the check, conformance included, pass unchanged, and so do the
check_drafttests. The migration tests apply and revert the new migration on both providers. The full suite ran locally against apostgres:17container as well as SQLite. There is no new package or project reference.Review
The media stack review found a High problem: the check read the asset's latest version rather than the one that goes live. That is fixed above. Its Low findings are fixed too: only images need alt text, and the acceptance is tested through
update_media.A second review blocked again, for two reasons:
update_mediaafterupload_mediawas refused as a duplicate.Both are fixed above, together with its Low findings:
check_draftvalidateschange_set_id, the entity half is tested throughupdate_media, and a stale comment is gone.A third review blocked on a High problem: an image a person uploads or describes in the admin library never goes through a change set. So it was never published, and content using it could never pass. The check now reads the live version, as described above. Its Low findings are fixed too:
A fourth review blocked on two problems in that fix:
ProposedMediaVersionsnow keeps those versions.A fifth review gave MERGE, with three Low findings:
Checklist
v2