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]

Reply via email to