Feature Specification: Deployment lifecycle
Created: 2026-09-23
Status: Implemented
Input: Reconstructed from the code of panel/ at 0bf4ecd36, api/specification.md, 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, which can be incomplete. To deploy, the browser validates the draft, saves it, and submits it to api. The page follows the build until it ends. The list shows the state, the domain and the last build of each deployment, and changes live through the event stream of api. The operator can copy a deployment to start a new one.
Why this priority: this is the main task of the panel.
Independent Test: a VM test where an operator makes a deployment, deploys it and sees a final state, and unit tests of the list with an api that does not answer.
Acceptance Scenarios:
- Given a new deployment, When the operator makes it, Then it is "Untitled", has no configuration, and opens in the form.
- Given a draft, When the operator saves it, Then the panel stores it and confirms the save. A save from a script gets a success answer, or a refusal with the reason.
- Given a build that fails, When the page gets the final state, Then the page shows the last lines of the build log as plain text, and the operator can deploy again.
- Given a full build queue at
api, When the operator deploys, Then the page says that the queue is full. - Given a running build, When
apipublishes a new state, Then the row in the list changes without a reload.
User Story 2 - 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 ends, and then tells the panel to remove the record. If the browser stops before the end, api calls back the panel with a reconcile token when the destroy is done. To remove the record, the panel marks it deleted: the operator does not see it any more, but the row stays, so that the provider can bill the deployment with usage_list.
Why this priority: a delete that removes the record but not the infrastructure is a leak that nobody sees.
Independent Test: unit tests of the delete flow in the panel and in the browser script.
Acceptance Scenarios:
- Given a destroy that succeeds, When the browser sees the end, Then the panel removes the record.
- Given a destroy that fails, When the browser sees the failure, Then the record stays, and the operator can try again.
- Given a deployment that
apidoes not know, When the operator deletes it, Then the panel removes the record. - Given that the browser closed during a destroy, When
apicalls back with the correct token, Then the panel removes the record.
User Story 3 - Do an action of api on a deployment (Priority: P2)
The operator suspends or resumes a deployment, or does another action that api offers for the current state ("Deployment actions" in api/docs/design/deployment-actions.md). A new action of api needs no change in the panel.
Independent Test: unit tests of the deployment form, of the form of one application, and of the action script.
Acceptance Scenarios:
- Given a deployment that
apican suspend, When the operator opens the deployment form, Then the form shows a Suspend button with the description ofapias its tooltip. - Given that the operator accepts the confirmation, When the action succeeds, Then the page reloads and shows the actions of the new state.
- 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.
User Story 4 - Choose how a deployment updates (Priority: P2)
A deployment without a module source pin deploys the default source of api, so a new revision of api brings an update, which a deploy applies. The update policy of a deployment says who starts that deploy: the operator (manual), api when it finds the update (automatic), or api in a maintenance window. api stores the policy ("Update policy" in api/docs/design/update-policy.md). The account page holds the defaults of the operator: the policy of a deployment that api does not know yet, the default of the "Apply the available update" check box beside Deploy, and the update notices.
Independent Test: unit tests of the update settings and controls.
Acceptance Scenarios:
- Given a new operator, When the operator opens the account page, Then the default policy is manual, the check box is ticked by default, and the notices are off.
- Given an available update, When the operator unticks the check box and deploys, Then
apideploys the configuration with the source of the last deploy. - Given a deployment that
apiknows, When the operator saves a maintenance window, Thenapistores it and starts the update in the window. - Given that the notices are on, When the operator opens a page, Then a banner lists the deployments with an available update and the updates that
apistarted itself. When the operator closes the second list, it stays hidden untilapistarts a new update.
Edge Cases
- The event stream stops during a deploy: the page shows a warning, not a failure, and asks for the state at intervals.
- The browser gets events of an earlier build: the page acts only on the events of its own build.
apidoes not know a deployment yet: the panel reports it as building and not yet known. The form shows the default policy of the operator, and the first deploy stores it.- The check box is unticked and the default source differs from the deployed source: Deploy stays enabled, and a deploy builds the deployed configuration again.
Requirements (mandatory)
Functional Requirements
-
FR-001: The panel MUST store the draft configuration, the name, the owner and the module source pin of a deployment, and MUST accept an incomplete draft. The form MUST NOT show the pin, and a save MUST NOT change it.
-
FR-002: The browser MUST send the deploy and the delete to
apidirectly, through the public HTTP interface ofapi, with a bearer token from the panel. A deploy MUST send the configuration, the deployment mode and the module source. -
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
apireports that the destroy is done, or that it does not know the deployment. -
FR-005: The panel MUST accept a call back about a destroy only with a reconcile token that the panel made for that deployment.
-
FR-006: The panel MUST show an unknown state and a warning, not an error page, when
apidoes not answer. -
FR-007: A duplicate MUST copy the name, the configuration and the module source pin into a new deployment.
-
FR-008: The deployment form MUST show a control for each action that
apioffers for the deployment, other than deploy, export and delete. It MUST disable Deploy whileapirefuses a deploy. -
FR-009: The list and the deployment form MUST mark each deployment for which
apireports an available update, for each update policy. -
FR-010: A deploy MUST tell
apinot to apply the available update when the operator unticks the check box. The check box MUST be disabled when no update is available. -
FR-011: The panel MUST NOT offer a policy other than manual for a deployment with a module source pin, because such a deployment gets no update.
-
FR-012: The panel MUST show the update banner only to an operator who turns on the notices.
-
FR-013: The panel MUST keep the row of a removed record until the removal of the operator account, and MUST answer
usage_listanddomain_listwith the deleted deployments of the operator too. Adomain_claimMUST be able to take the domain of a deleted deployment of the operator. All other procedures MUST NOT see a deleted deployment. -
FR-014: The page of a deployment that
apideployed MUST link to the SBOM of each of its nodes. The panel MUST show the status of each node (ready, orunavailablewith the reason), with the build that gave it and the time, and MUST serve areadySBOM as a file download. The panel MUST NOT store the SBOMs. The panel MUST answer 404 for a deployment of another operator without a call toapi. -
FR-015: When the stored configuration of a deployment registers a domain at the registrar account of the provider, the deployment form MUST show the status of the domain from
domain_list. When the domain isassignable, the form MUST offer to give the domain to the deployment withdomain_claim. The panel MUST show the reason whenapirefuses the claim. The domains page MUST give a superuser a form that gives a deployment of each operator the domain of its stored configuration, withdomain_assign, and the panel MUST show the reason whenapirefuses. -
FR-016: When the deployment of the page has a domain with domain terms, the domain card MUST show the documents of the terms with their dates from
domain_list, the current version, and the accepted version. When the deployment did not accept the current version, the card MUST tell that the acceptance is pending and MUST offerdomain_terms_acceptfor the version that the page shows. The acceptance MUST NOT start a deploy. The card MUST tell that the acceptance also applies to the other deployments of the operator that need only these terms. -
FR-017: For each domain of a deployment of an operator with the current version of the domain terms not accepted, the panel MUST send one email per version to the operator, and MUST link to the deployment page in that email. The panel MUST tell
apiof each email withdomain_terms_noticeafter it sends the email, so that the audit chain has the notice and the next run does not send it again. The panel MUST NOT tellapiof an operator with no email address, and MUST try that operator again on the next run. The panel MUST make this call as the service, with the operator asX-On-Behalf-Of, andapiMUST refuse the call from each other caller. -
FR-018: When the deployment of the page holds a domain that has a registration of its own, and
domain_listgivesrenewal_off, the domain card MUST offerdomain_renewal_offto turn off a renewal that is on, and MUST tell that this starts a deploy and that the registration ends at its expiry. Whenrenewal_offistrue, the card MUST offerdomain_renewal_curatedto use the renewal of the hosting provider again. The card MUST NOT offer to turn on a renewal that the hosting provider turned off. -
FR-019: The panel MUST have a domains page that lists the domains of the deployments of the operator from
domain_list, also of the deleted deployments, with?id=always in the call, so a superuser sees only their own domains. For a domain that a deployment of the operator has, the page MUST link to the deployment page. For a domain with the statusassignable, the page MUST offerreleased_domain_renewal_off,released_domain_renewal_curatedanddomain_take_out, anddomain_take_out_cancelwhentransfer_pendingis true. The answer ofdomain_take_outMUST show the auth code directly, and the panel MUST NOT store the code or put it in the session. -
FR-020: For a domain where
domain_listgivestransfer_in, the domains page MUST have a form that sends the auth code todomain_transfer_infor that deployment. The form field MUST NOT show the code, and the panel MUST NOT keep the code. After the call, the page MUST tell the operator that the deploy fails until the registry completes the transfer, and that they deploy again then. Whendomain_listgivestransfer_failed, the page MUST tell the operator that the registry refused the transfer, and MUST NOT offer the actions of a held domain. Withtransfer_in, the form MUST tell the operator to check the auth code and the transfer lock, and that the code sends the transfer again. -
FR-021: The deploy controls MUST have a field for the auth code of the domain, which the page sends to
deployment_submitasdomain_auth_code, so an operator can transfer a domain in at the first deploy of a deployment. The field MUST NOT show the code, and the panel MUST NOT keep the code. The field MUST be empty after a submit thatapiaccepts. The label of the field MUST tell that the deploy fails until the registry completes the transfer, and that the operator then deploys again. -
FR-022: After
domain_terms_acceptsucceeds, the panel MUST calldomain_terms_acceptwith the token of the operator for each other deployment of that operator with pending domain terms, when the parts of the version that the deployment did not accept are all parts of the accepted version. The call MUST use the current version of the domain of that deployment. The panel MUST NOT store an acceptance. The page MUST tell the operator for which domains the acceptance applies, and for which it does not apply. A failure on a different deployment MUST NOT change the first acceptance. -
FR-023: The deployment page and a public page MUST show what the hosting provider declares in the
provideroption of the panel: the legal entity, the data location and the sub-processors. The public page MUST NOT need an account. Both pages MUST show a provider that declares nothing as "Not declared". -
FR-024: When the operator has a saved domain contact whose handle is not
owner_handleofdomain_list, the domains page MUST have a form that selects one of these contacts as the owner contact of the deployment and sends the domain todomain_owner_change, for a domain with a registration of its own whose status isheldorassignableand whose holder is a deployment of the operator. The form MUST NOT send a handle. When api/ does not change the owner, the panel MUST keep the earlier owner contact of the deployment. The page MUST tell that the owner is the registrant and that the registry can lock the transfer of the domain after the change. The page MUST NOT have the form whiletransfer_pendingis true or the domain hastransfer_in. Whendomain_termsof the domain has a version that is not itsaccepted, the form MUST link the documents and MUST have a required box that sends that version asaccepted_domain_terms. -
FR-025: An operator MUST be able to save zero or more domain contacts, each with a label, and MUST be able to select for each deployment one saved contact per role:
owner,admin,techandbilling. A role other thanownerwithout a contact takes the owner contact. Fordeployment_submit,migration_importanddomain_owner_changeon a deployment of the operator, the authorization callback MUST givedomain_contacts, a map from each role to the ID of its contact, or an empty string for a role without a saved contact. The callback MUST NOT write a row.apirefuses to register or transfer a domain without a saved owner contact, thus a panel that sends no ID cannot make the hosting provider the owner. A new deployment of an operator with one saved contact MUST have that contact as its owner contact. -
FR-026: Before the first save of a domain contact, the domain contact form MUST have the first name, the last name and the e-mail address of the account, and MUST tell the operator that these come from the account and that the registrar needs real data.
Key Entities
- Deployment: identifier, owner, name, draft configuration, module source pin, time of creation, time of the delete.
- Operator settings: the default update policy, the default of the check box, the notice switch, and the time when the operator last closed the notice of the automatic updates.
- Reconcile token: a signed token for one deployment, with which
apiconfirms a destroy. - Build state:
active,building,deploying,idle,failed,destroying, or unknown whenapidoes not answer.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: A delete never removes the record of a deployment whose infrastructure
apistill has. - SC-002: The list stays usable when
apiis down.
Assumptions
apiqueues a destroy when it gets a 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.