asf-tooling commented on issue #510:
URL:
https://github.com/apache/tooling-trusted-releases/issues/510#issuecomment-4404197172
<!-- gofannon-issue-triage-bot v2 -->
**Automated triage** — analyzed at `main@837830e8`
**Type:** `new_feature` • **Classification:** `actionable` •
**Confidence:** `high`
**Application domain(s):** `project_committee_mgmt`, `release_lifecycle`,
`api_and_admin`
### Summary
Issue #510 requests that project deletion/archival be gated by release state
conditions (no current/archived releases for deletion, only archival if such
releases exist, no action if the project is the sole project on a committee).
@dave2wave noted (2026-02-19) that the existing ownership check in
`atr/storage/writers/project.py` L184-190 is incorrect — any PMC member should
be able to delete, not just the creator. @alitheg noted (2026-04-21) that PR
#1186 is ready for review, meaning this work is already in-flight and should
not be duplicated here.
### Where this lives in the code today
#### `atr/storage/writers/project.py` — `CommitteeMember.delete` (lines
193-213)
_needs modification_
This is the current delete implementation that needs to be expanded with
phase-aware logic (allow deletion when only candidates exist, add archival path
for projects with current/archived releases, check for
sole-project-on-committee constraint).
```python
async def delete(self, project_key: safe.ProjectKey) -> None:
project = await self.__data.project(
key=str(project_key), status=sql.ProjectStatus.ACTIVE,
_releases=True, _distribution_channels=True
).get()
if not project:
raise storage.AccessError(f"Project '{project_key}' not found.",
status=404)
# Prevent deletion if there are associated releases or channels
if project.releases:
raise storage.AccessError(
f"Cannot delete project '{project_key}' because it has
associated releases.", status=409
)
await self.__data.delete(project)
await self.__data.commit()
self.__write_as.append_to_audit_log(
asf_uid=self.__asf_uid,
project_key=str(project_key),
)
return None
```
#### `atr/get/projects.py` — `_render_delete_section` (lines 370-386)
_needs modification_
The UX for project deletion/archival lives here. @dave2wave noted it needs
an archive/delete form that shows what will happen.
```python
async def _render_delete_section(project: sql.Project) -> htm.Element:
section = htm.Block(htm.div)
section.h2["Actions"]
delete_form = await form.render(
shared.projects.DeleteProjectForm,
action=util.as_url(post.projects.view, name=str(project.key)),
form_classes="",
submit_classes="btn-sm btn-outline-danger",
submit_label="Delete project",
defaults={"project_key": str(project.key)},
confirm="Are you sure you want to delete this project? This cannot
be undone.",
empty=True,
)
section.div(".my-3")[delete_form]
return section.collect()
```
#### `atr/post/projects.py` — `_process_delete_project` (lines 223-235)
_needs modification_
POST handler for project deletion that calls wacm.project.delete(); needs to
also support an archive action.
```python
async def _process_delete_project(
session: web.Committer, delete_form: shared.projects.DeleteProjectForm
) -> web.WerkzeugResponse:
project_key = delete_form.project_key
async with storage.write(session) as write:
wacm = await write.as_project_committee_member(project_key)
try:
await wacm.project.delete(project_key)
except storage.AccessError as e:
return await session.redirect(get.projects.projects,
error=f"Error deleting project: {e}")
return await session.redirect(get.projects.projects, success=f"Project
'{project_key}' deleted successfully.")
```
### Where new code would go
- `atr/storage/writers/project.py` — after symbol CommitteeMember.delete
An `archive` method is needed alongside `delete` to handle the case where
a project has current/archived releases.
- `atr/shared/projects.py` — after symbol DeleteProjectForm
An ArchiveProjectForm would be needed for the archive action UX.
### Proposed approach
PR #1186 (noted by @alitheg as ready for review on 2026-04-21) implements
this issue. The required changes include: (1) modifying
`CommitteeMember.delete()` in `atr/storage/writers/project.py` to check release
phases — allowing deletion only when no RELEASE or archived releases exist,
cascading deletion of candidate releases; (2) adding a new `archive()` method
for projects with current/archived releases that transitions the project status
and archives current releases; (3) adding a sole-project-on-committee guard;
(4) fixing the UX gating in `atr/get/projects.py` so any PMC member (not just
the creator) can see the delete/archive actions; (5) adding a confirmation form
page as described by @dave2wave. Since PR #1186 is in-flight and ready for
review, no additional diff is proposed here — the maintainer should review that
PR directly.
### Open questions
- What does PR #1186 use as the project status value for archived projects?
(e.g., sql.ProjectStatus.ARCHIVED — need to confirm this enum exists in sql.py)
- Does the archive operation need to also archive RELEASE_PREVIEW phase
releases, or only RELEASE phase?
- Should the 'sole project on committee' check consider only ACTIVE
projects, or also archived ones?
### Files examined
- `atr/storage/writers/release.py`
- `atr/post/projects.py`
- `atr/storage/writers/project.py`
- `atr/shared/projects.py`
- `atr/get/projects.py`
---
*Draft from a triage agent. A human reviewer should validate before merging
any change. The agent did not run tests or verify diffs apply.*
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]