Epic 2 — Project Management
Covers project creation, editing, listing with search and filters, soft-delete (archive/deactivate), running-state guard on archive, restore/reactivate, and the global zero-state onboarding flow. Hard-delete (DELETE /projects/{id}) is intentionally absent from the UI. Depends on planned projects.archived_at TIMESTAMPTZ column (migration V7).
Personas: BU (create/edit/archive) OP (all) AU (read-only)
Shared modules:
OptimisticMutationHelper
CorrelationChip
LastSyncedBadge
EnvProvenance
Non-goals: Hard-delete of projects. Run history under an archived project remains intact and fully reachable from Epics 8 and 15.
Story 2.1 — View Project List
- As a
- BU
- I want
- to see all my projects in a searchable, filterable list
- So that
- I can navigate quickly to the correct project
Scenario: Default project list loads
/projects
ThenGET /projects is issued; one card per project is rendered showing name, github_repo_full_name, created_at, and the most recent runs.status
Andprojects with projects.archived_at IS NOT NULL are hidden by default
Scenario: Show archived projects via filter chip
GET /projects?include_archived=true is issued; archived projects appear tagged with an Archived badge and run history links remain intact
| Endpoint / DB | Purpose |
|---|---|
GET /projects | List projects; ?include_archived=true to include archived |
DB projects.archived_at | null = active, non-null = archived |
Story 2.2 — Create Project
- As a
- BU
- I want
- to create a new project by binding it to a GitHub repository
- So that
- I can trigger AMTP pipeline runs against that repository
Scenario: Successful project creation
POST /projects is issued; the new project card appears in the list; the user is navigated to the new project's detail page
Scenario: Duplicate project name rejected
"acme-api" already exists
Whenthe user submits the create form with name: "acme-api"
Thenthe API returns 409; the form renders a Name already in use inline validation error; no navigation occurs
UI clones the current projects store slice into prior_state, applies the optimistic write atomically (new card appears at the top with a loading indicator), then issues POST /projects. On 4xx / 5xx / network-loss, the store slice is restored from prior_state exactly and the CorrelationChip surfaces the failure cause. Regression: zero zombie entries remain in the list or in client state.
| Endpoint / DB | Purpose |
|---|---|
POST /projects | Create project |
DB projects.name (UNIQUE) | Duplicate-name guard |
Story 2.3 — Edit Project
- As a
- BU
- I want
- to update a project's name, description, and pipeline defaults
- So that
- I can correct configuration without recreating the project and losing run history
Scenario: Successful project edit
name, description, depth_level default, target_framework default, or base_branch and saves
ThenPATCH /projects/{project_id} is issued with the changed fields; updated values are reflected immediately in the project detail and list views
Scenario: Edit blocked for Auditor role
PATCH /projects/{project_id} is never issued
| Endpoint / DB | Purpose |
|---|---|
PATCH /projects/{project_id} | Update project metadata |
Story 2.4 — Archive (Deactivate) Project
- As a
- BU
- I want
- to archive a project I no longer need
- So that
- it is hidden from default views while its complete run history remains intact
Scenario: Archive succeeds — no active children
runs.status IN ('pending','running') rows and zero stages.status = 'awaiting_approval' rows
Whenthe user clicks Deactivate / Archive and confirms the modal
ThenPATCH /projects/{project_id} is issued with { "archived_at": "<ISO timestamp>" }; the project card is hidden from the default list and reachable via the Show archived filter chip
Scenario: Archive blocked — active run (running-state guard)
runs.status = 'running'
Whenthe user clicks Deactivate / Archive
Thena blocking modal lists the offending run_id(s) with a deep-link to the run timeline (Epic 4, cancel run); PATCH is not issued
Scenario: Archive blocked — pending approval
stages.status = 'awaiting_approval'
Whenthe user clicks Deactivate / Archive
Thena blocking modal lists the offending stage with a deep-link to the approval inbox (Epic 6); PATCH is not issued
After all running-state guards pass, the UI clones prior_state, tags the project card Archived and removes it from the default list, then issues PATCH. On server error, the store slice is restored from prior_state exactly. Regression: project reappears in the active list with zero lingering Archived badge states.
| Endpoint / DB | Purpose |
|---|---|
PATCH /projects/{project_id} | Write archived_at value (or clear it) |
DB projects.archived_at | Planned column (migration V7) |
DB runs.status | Guard check values pending, running |
DB stages.status | Guard check value awaiting_approval |
Story 2.5 — Restore (Reactivate) Archived Project
- As a
- BU
- I want
- to restore an archived project to active status
- So that
- I can resume triggering runs against it without losing any prior history
Scenario: Successful restore
archived_at IS NOT NULL is visible
Whenthe user clicks Restore
ThenPATCH /projects/{project_id} is issued with { "archived_at": null }; the project reappears in the default active list without the Archived badge
UI clones prior_state, removes the Archived badge and moves the card to the active list, then issues PATCH. On server error, store slice is restored and the card reverts to the archived list with the badge restored (regression assertion).
Story 2.6 — Zero-State Onboarding (First Project)
- As a
- BU
- I want
- a guided prompt when my tenant has no projects
- So that
- I know how to connect my first repository and start a run without reading external docs
Scenario: Empty-tenant CTA
/projects
Thena zero-state CTA is rendered with heading Create your first project, sub-copy explaining the AMTP pipeline purpose, a primary button Create project, and a secondary link Install GitHub App (Epic 13)
Scenario: Auditor lands on empty or inaccessible tenant
Story 2.7 — Project Detail View
- As a
- BU
- I want
- to see a project's configuration and recent runs from a single page
- So that
- I can navigate quickly to run triggering, history, and artifact inspection
Scenario: Project detail loads for active project
/projects/{project_id}
WhenGET /projects/{project_id} resolves
Thenthe project's name, github_repo_full_name, description, configured defaults, and the 5 most recent runs (status + created_at) are rendered
Scenario: Archived project — banner shown and run trigger disabled
projects.archived_at IS NOT NULL
Thenan Archived banner is shown; the Trigger new run button is disabled with tooltip Archived project — restore to trigger runs
| Endpoint / DB | Purpose |
|---|---|
GET /projects/{project_id} | Project detail + recent runs |
DB projects.archived_at | Archived state check |
DB runs.status, runs.created_at | Recent-runs list |