The gap
DELETE /datasets/{dataset_id}/assets/{asset_id} has been on the wire since M3 (#30) and is the API's only curation operation over the trunk. No screen calls it. DatasetScreen.tsx says so in its own module docstring, listing the absence as a deliberate scope fact rather than an oversight — but the scope that justified it has since moved.
Why it is worth revisiting now
Promotion became observable in the 2026-08 run (#308, findings F5/F17): BatchOut.promoted_asset_count reports how many of a batch's assets are in the trunk right now, deliberately as current membership rather than as a promotion log — precisely so that a curator's removal is reflected. That field is currently describing an operation the product cannot perform.
The Dataset screen also became a first-class project tab in the same run (#310), so it is now one click from anywhere in a project rather than behind an overflow menu. It is where somebody would look for this.
What the kernel already guarantees
DatasetService.remove_asset is one of exactly two service methods with no confirm= gate, and ConfirmationRequired's own docstring records why: curation destroys nothing. The asset, its annotations and its blob all stay; only the membership row goes, and the DatasetChange log keeps the prior state on the record.
- Promotion is a union, so re-promoting the batch restores what a curator took out — removal is not permanent and is not a decision anybody has to be frightened of.
- The change log already carries
remove_asset entries with subject_ids = [asset_id].
Together those make this a low-risk control: reversible, audited, and refusing nothing.
Suggested shape
A per-asset action in the Dataset screen's asset listing, capability-gated if a declaration is added for it, otherwise plain. Refusal rendering goes through data/refusals.ts like every other mutation since #307, and the mutation must invalidate the dataset stats and ["batches"] — promoted_asset_count is derived per read and would otherwise show the pre-removal number.
Notes
- Pre-existing; not introduced by the audit run. Recorded here because the run's "found, not fixed" list is being retired into issues.
- The trunk is assets only — an annotation has no membership row — so removing an asset takes its labels out of the dataset with it. Worth saying in the confirmation copy even though the kernel needs no
confirm=.
The gap
DELETE /datasets/{dataset_id}/assets/{asset_id}has been on the wire since M3 (#30) and is the API's only curation operation over the trunk. No screen calls it.DatasetScreen.tsxsays so in its own module docstring, listing the absence as a deliberate scope fact rather than an oversight — but the scope that justified it has since moved.Why it is worth revisiting now
Promotion became observable in the 2026-08 run (#308, findings F5/F17):
BatchOut.promoted_asset_countreports how many of a batch's assets are in the trunk right now, deliberately as current membership rather than as a promotion log — precisely so that a curator's removal is reflected. That field is currently describing an operation the product cannot perform.The Dataset screen also became a first-class project tab in the same run (#310), so it is now one click from anywhere in a project rather than behind an overflow menu. It is where somebody would look for this.
What the kernel already guarantees
DatasetService.remove_assetis one of exactly two service methods with noconfirm=gate, andConfirmationRequired's own docstring records why: curation destroys nothing. The asset, its annotations and its blob all stay; only the membership row goes, and theDatasetChangelog keeps the prior state on the record.remove_assetentries withsubject_ids = [asset_id].Together those make this a low-risk control: reversible, audited, and refusing nothing.
Suggested shape
A per-asset action in the Dataset screen's asset listing, capability-gated if a declaration is added for it, otherwise plain. Refusal rendering goes through
data/refusals.tslike every other mutation since #307, and the mutation must invalidate the dataset stats and["batches"]—promoted_asset_countis derived per read and would otherwise show the pre-removal number.Notes
confirm=.