Feature Specification: Deployment Lifecycle

Created: 2026-09-23

Status: Implemented

Input: Reconstructed from the code at 0bf4ecd36 (panel/src/panel/views.py, models.py, forms.py, tokens.py, api_client.py, templates/deployment_list.html, templates/deployment_form.html, static/deployment_submit.js, static/deployment_delete.js, static/centrifuge_session.js), api/specification.md (sections "Two-store model and deletion" and "Browser-orchestrated delete"), pull requests #897, #969, #1011, #1036, #1052, #1186, #1311, #1339, #1777, #1839 and #1842, and the design notes of the maintainers.

User Scenarios & Testing (mandatory)

User Story 1 - Keep a draft and deploy it (Priority: P1)

An operator makes a deployment, edits its configuration, and saves it as a draft. The operator can save an incomplete draft. When the operator deploys, the browser validates the value, saves it, and submits it to api with a bearer token. The page follows the build until it ends, and shows the last lines of the log on a failure.

Why this priority: this is the main task of the panel.

Independent Test: in apps-panel, an operator logs in, makes a deployment, deploys it, and sees a final state.

Acceptance Scenarios:

  1. Given a new deployment, When the operator makes it, Then it is "Untitled", has no value, and opens in the form.
  2. Given a draft, When the operator saves it, Then the panel stores the value, and the page says "Configuration saved.". A JSON request gets {ok: true}, or 400 with the error.
  3. Given a valid draft, When the operator deploys, Then the browser submits {configuration, mode, module_source} to api for the identifier str(pk), and waits for active, idle or failed.
  4. Given a build that fails, When the page gets the final state, Then the page shows the last 40 lines of the log as text, and the operator can deploy again.
  5. Given a full build queue, When the operator deploys, Then the page shows a hint for fediversity.error.queue_full.

User Story 2 - See the state of all my deployments (Priority: P1)

The list shows the deployments of the operator with their state, domain and last build, from api. The states change live through the event stream.

Why this priority: the operator must see what is running and what failed.

Independent Test: run panel-python-tests (test_deployment_list.py, test_deployment_state.py, test_api_down.py).

Acceptance Scenarios:

  1. Given deployments with builds, When the operator opens the list, Then each row shows the state, the domain and the last build that api gives.
  2. Given a running build, When api publishes a new state, Then the row changes without a reload.
  3. Given that api does not answer, When the operator opens the list, Then the rows show "unknown" and the page shows a warning.

User Story 3 - Delete a deployment and its infrastructure (Priority: P1)

The operator confirms the delete. The browser asks api to destroy the infrastructure, waits until the destroy is done, and then removes the record in the panel. If the browser stops before the end, api calls the reconcile callback of the panel when the destroy is done.

Why this priority: a delete that removes the record but not the infrastructure is a leak that nobody sees.

Independent Test: run panel-python-tests (test_deployment_delete.py) and the JavaScript tests (deployment-delete.test.js).

Acceptance Scenarios:

  1. Given a deployed deployment, When the operator confirms the delete, Then the browser sends DELETE to api with a callback URL and a callback token, and waits for idle.
  2. Given a destroy that succeeds, When the browser sees idle, Then it confirms the delete to the panel, and the panel removes the record.
  3. Given a destroy that fails, When the browser sees failed, Then the record stays, and the operator can try again.
  4. Given a deployment that api does not know, When the operator deletes it, Then api answers 404, and the panel removes the record.
  5. Given that the browser closed during a destroy, When api calls the reconcile callback with the correct token, Then the panel removes the record. A wrong token gets 403.

User Story 4 - Duplicate a deployment (Priority: P3)

The operator copies a deployment to start a new one with the same name, value and module source pin.

Independent Test: run panel-python-tests (test_deployment_form.py).

Acceptance Scenarios:

  1. Given a deployment, When the operator duplicates it, Then the panel makes a new record with the same value, which api does not know yet.

User Story 5 - Do an action of api on a deployment (Priority: P2)

The operator suspends or resumes a deployment, or does another action that api offers. api names the actions in the actions of deployment_get ("Deployment actions" in api/specification.md), thus a new action of api needs no new code in the panel.

Independent Test: run panel-python-tests (test_deployment_form.py, test_deployment_application_form.py) and panel-js-tests (deployment-action.test.js).

Acceptance Scenarios:

  1. Given a deployment that api can suspend, When the operator opens the deployment form, Then the form shows a Suspend button with the description of api as its tooltip.
  2. Given that the operator clicks the button and accepts the confirmation, When the build of the action succeeds, Then the page reloads and shows the actions of the new state.
  3. Given a suspended deployment, When the operator opens the deployment form or the form of one application, Then Deploy is disabled and the page shows why. Only the deployment form shows Resume.

Edge Cases

  • A save does not change the module source pin, and ignores a pin in the request. The pin is not visible in the form.
  • The event stream stops during a deploy: the page shows a warning, not a failure, and polls the state.
  • api does not know a deployment yet: the state proxy answers building with known: false.
  • The browser gets events of an earlier build: the page acts only on the events of its own build.

Requirements (mandatory)

Functional Requirements

  • FR-001: The panel MUST store the draft value, the name, the owner and the module source pin of a deployment, and MUST accept an incomplete draft.
  • FR-002: The panel MUST send the deploy and the delete from the browser to api, through the public HTTP interface of api, with a bearer token of the panel.
  • FR-003: The panel MUST get the state, the domain and the builds of a deployment from api, and MUST NOT store them.
  • FR-004: The panel MUST remove the record of a deployment only after api reports that the destroy is done, or that it does not know the deployment.
  • FR-005: The reconcile callback MUST accept only a token that the panel made for that deployment.
  • FR-006: The panel MUST show an "unknown" state, not an error page, when api does not answer.
  • FR-007: Duplicate MUST copy the name, the value and the module source pin, also when the form does not show the pin.
  • FR-008: The deployment form MUST show a control for each action of deployment_get other than deploy and delete, and MUST disable Deploy while api refuses a deploy.

Key Entities

  • Deployment: identifier, owner, name, draft value, module source pin, time of creation.
  • Reconcile token: a signed token for one deployment that lets api confirm a destroy.
  • Build state: active, building, deploying, idle, failed, destroying or unknown, as api reports it.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: A delete never removes the record of a deployment whose infrastructure api still has.
  • SC-002: The list stays usable when api is down.

Assumptions

  • api enqueues a destroy for DELETE, and does not destroy at once.
  • Save and submit are two steps. A submit that fails after a save leaves a saved draft.

Known Deviations

None.