Manual testing and demos
Repeat the application scenarios and operate retained demo instances.
On this page
- Secret variable exports
- Curated template library
- Agent revocation navigation
- Isolated development stacks
- Release provenance and CI retries (#18)
- Change your own password (#141)
- Reusable project templates (#76)
- Preceding stages for promotion (A18)
- Saved version for runbook Retry (A17)
- Phone runbook execution history (A16)
- Add Step ordering (A15 follow-up)
- Queued cancellation step explanations (A15)
- Dedicated agent port
- Four permission levels
- Project deployment summaries
- Full release content (A14)
- Variable identifier guidance (A13)
- Cancel variable edits
- Duplicate release recovery (A12)
- Connected project variable previews (A11 follow-up)
- Release variable guidance (A11)
- Deployment re-run inputs (A10)
- Reachable Step form actions (A09)
- Swagger request target
- Project saves after permission changes
- Project-scoped API tokens
- Project member roles
- Software build identity
- Manual deployment step panels
- Follow live logs
- Step execution locations
- Deployment staging transfer
- Releases with a deferred package pull
- Bounded log history
- Server step resource policy
- Phase 1 regression checks
- List and authentication checks
- Populated demo
- SMTP delivery warnings
- Shared lifecycle variables
- Step variable viewer (#142)
- Deployment notes in history
- Project groups
- Empty deployment filters
- Lifecycle stage ordering
- Stale-form permission navigation (V02)
- Browser Back after a confirmed re-run (V03)
- Device, certificate, and passkey checks (V04)
- Database TLS policy
- Split default credential redaction
Secret variable exports
Use a disposable project and lifecycle with harmless secret values. Retain these examples for repeat checks. Keep schedules disabled.
- Sign in as an instance administrator.
- Open the project’s Variables page.
- Select Export secrets.
- Verify the displayed project name.
- Select Confirm identity.
- Enter an incorrect password.
- Verify that no download starts.
- Enter the correct password.
- If MFA is enabled, complete the factor check.
- Select Download JSON.
- Verify that the file contains only secret-marked project variables and their environment scopes.
- Repeat the export with Use single sign-on.
- Verify that the identity provider requests fresh authentication.
- Repeat these checks from the lifecycle’s Shared variables section.
- Cancel a pending export.
- Verify that its download endpoint returns
404. - Leave another export pending for five minutes.
- Verify that it expires without returning values.
- Sign in as a project administrator without the global
adminrole. - Verify that export controls are hidden and direct export requests return
403.
For Authentik 2026.2.7, repeat the SSO export after an earlier SSO confirmation. Use an SSO-only account without a local password. Keep the Authentik session active. Verify that a new sign-in permits the export without an Authentik logout. If the previous sign-in just completed, wait two seconds before you start the confirmation.
For API checks, follow Export secret variables in the shipped agent skill.
Verify that a bearer token without browser approval cannot download secrets.
Verify that the original claim permits one download after password or OAuth/OIDC confirmation.
Verify that logout invalidates an approved API export.
Inspect Admin → Audit log for export_secrets events without secret values.
Run the automated API and browser cases with these commands:
bash scripts/go_test.sh -tags=e2e -run '^TestSecretExport' ./internal/handler/api
DURPDEPLOY_CONTAINER_RUNTIME=podman bash scripts/ci_browser_shard.sh -- \
bash scripts/go_test.sh -tags=e2e,packagebrowser \
-run '^TestSecretExportBrowserE2E$' ./internal/handler/api
Curated template library
The running suite retains 19 projects named e2e-library-<slug>-<run-id>.
It prints each project and configuration-preview URL.
The offline agent label exists only for configuration checks.
It has no paired identity and cannot execute work.
Replace it with paired Linux host labels before testing a real deployment.
These checks create no schedules or deployments.
- Open Templates → Template Library.
- Confirm that 12 individual steps and seven complete workflows appear.
- Open Deploy a Linux service.
- Inspect all four ordered scripts, required variables, execution target, dependencies, and permissions.
- Choose a writable disposable project.
- Submit the form without a required value.
- Confirm that the form reports the missing value without changing project steps or variables.
- Supply the required values and registered agent labels.
- Supply a fresh optional artifact token through its secret input.
- Confirm review and apply the template.
- Confirm that four editable steps append in order and no deployment starts.
- Apply the same template again.
- Confirm a conflict message and unchanged project configuration.
- Confirm that the token input stays empty after the rejected request.
- Sign in as a Viewer or Deployer.
- Confirm that previews remain readable and no application form appears.
Repeat the preview and application checks for Deploy a Compose application and an individual step. Repeat the checks for the PowerShell 7 and Python 3 variants. Confirm that each card and preview shows its actual interpreter. At phone width, confirm readable scripts, scrolling forms, visible feedback, and no page overflow. Use the existing step and variable editors to customize a retained copy. Confirm that the library preview and an earlier release snapshot remain unchanged.
Use GET /api/v1/projects/{id}/template-library/{slug} to compare public configuration with the preview.
Use POST /api/v1/projects/{id}/template-library/{slug}/apply with the preview’s version and variable keys.
Expect 201 for successful creation, 422 for invalid configuration, and 409 for stale versions or variable conflicts.
Expect 403 for Viewer, Deployer, cross-project, or insufficient token permissions.
Inspect Admin → Audit for apply_library_template with the destination and library version.
Confirm that audit and variable API responses omit secret values.
For terraform-plan-apply, inspect both scripts and the plan approval paths.
Supply a reviewed toolchain image and its configuration directory.
Confirm that the form has no agent-label input.
Supply fresh optional HTTP backend credentials through the secret input.
Apply the template and confirm that both copied steps use the selected image.
Confirm that copying starts no deployment.
For execution checks, use a disposable backend and configuration with built-in terraform_data only.
Create a release and deploy it separately.
Confirm that the deployment pauses after the saved plan and before any state write.
Approve the exact artifact and confirm one state update with locking.
Repeat with rejection and confirm no state update.
Repeat with changed remote state and confirm that apply fails without changing state.
In a disposable deployment, create terraform-context in staging before the plan step.
Confirm failure before backend access or artifact approval.
For Bash download, create an artifact directory in staging before the download step.
Confirm failure and unchanged directory contents.
Retain the projects, releases, decisions, and deployment history.
Do not use production infrastructure for these checks.
See template library for host prerequisites and supported deployment boundaries.
Agent revocation navigation
Use disposable agents without production work. Retain each revoked agent for inspection. These checks create no schedules.
- Open Admin > Agents as an administrator.
- Select Revoke for a disposable agent.
- Cancel the confirmation and confirm that the agent status stays unchanged.
- Select Revoke again and accept the confirmation.
- Confirm that the Agents list shows revoked and hides that agent’s Revoke button.
- Confirm that the page has one navigation bar and no page content inside an action button.
- Repeat from another disposable agent’s detail page with Revoke agent.
- Confirm that revocation returns to
/admin/agents. - Call
POST /api/v1/admin/agents/{id}/revokewith an administrator bearer token. - Confirm a
204response and a revoked status fromGET /api/v1/admin/agents/{id}.
Run the API and browser regressions:
bash scripts/go_test.sh -count=1 -run '^TestAgentRevokeEndToEnd$' ./internal/server
bash scripts/go_test.sh -tags=e2e,agentbrowser -count=1 -run '^TestAgentDeleteBrowserE2E$' ./internal/handler/api
Isolated development stacks
Run make dev-stack-test with the selected local container socket active.
For Docker, set DEV_STACK_RUNTIME=docker and DEV_STACK_SOCKET.
The regression starts stacks from two detached worktrees.
It builds the app once before it starts both stacks concurrently.
Each stack receives its own labelled image layer and resources.
Set DEV_STACK_IMAGE to reuse a previously built app image.
The regression checks authenticated API and browser access through verified HTTPS. It checks hostname validation, separate cookies, separate keys, and separate agent identities. It checks deployment containers and staging volumes while another stack restarts. It removes stack A while stack B runs a deployment. It checks successful deployment in B after removal. It checks partial startup failure, interrupted startup, and rejected foreign ownership. It also checks inspection errors after cleanup starts and while an app is stopped. Expect cleanup to remain unconfirmed when an existing resource cannot be inspected. Expect a later cleanup retry to succeed without affecting B. The regression also invokes the configured T3 settle command from a worktree. It checks copied registrations, missing records, repeated settlement, and cleanup retry. It checks that edited JSON records cannot bypass the resource worktree label. Expect the registered stack and its database volumes to disappear. Expect the standalone B fixture and its authenticated API to remain available.
Read the printed state directory for retained examples.
Stack B retains its project, environment, release, and deployment history.
The recreated stack A retains a fresh administrator for manual testing.
Read each private login.json file for its test login.
Use separate browser profiles and the CA file from each connection record.
Keep certificate trust inside those profiles.
Use separate cookie jars for CLI checks.
Do not enable schedules for this test.
Repeat the health check with the resolver and CA arguments in development.
Open B’s retained project and release through its API or browser.
Deploy the retained release to the retained environment.
Expect succeeded and stack-deployment-ok in its logs.
Use bash scripts/dev_stack.sh status STATE_DIRECTORY to inspect a retained stack.
Use bash scripts/dev_stack.sh logs STATE_DIRECTORY caddy to inspect its proxy.
Use bash scripts/dev_stack.sh down STATE_DIRECTORY to delete one stack’s resources and database.
Repeat down for the other retained stack when manual testing is complete.
Keep the state directories when you need diagnostic logs.
These retained fixtures are not registered with the thread wrapper.
Settling the coding thread does not remove them.
Release provenance and CI retries (#18)
- Open a disposable project’s Releases page as a Maintainer or administrator.
- Enter a unique version and optional public source identifiers.
- Use HTTPS URLs without credentials, query parameters, or fragments.
- Select Create Release.
- Open the release and confirm all source identifiers.
- Refresh the release and confirm that provenance stays unchanged.
- Deploy the release and confirm its provenance on the deployment page.
- Check the deployment notification and creation audit details.
- Repeat creation through the API with an
Idempotency-Keyheader. - Retry the same request and confirm the original
201response and resource ID. - Confirm the
Idempotency-Replayed: trueresponse header. - Restart the server and repeat the same request.
- Change one request field and confirm a
409response. - Submit a credential-bearing URL and confirm rejection without echoing credentials.
- Confirm that a Viewer can read provenance but cannot create releases.
- Check the form and detail pages at phone and desktop widths.
These checks create no schedules. Keep the project, releases, and deployment history for repeat checks.
Run the backend, API, web, and browser checks:
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -timeout=6m -v -run '^TestReleaseProvenance.*E2E$' ./internal/handler/api
Change your own password (#141)
Use a disposable local-password account for each role. Retain the accounts for repeat checks. This check creates no schedules.
- Sign in and open Security from the account menu.
- If required, select Confirm identity and complete the password and configured factor checks.
- Expand Password in Sign-in methods.
- Enter an incorrect current password and matching new passwords.
- Select Change password.
- Confirm that the form stays expanded, shows an inline error, and clears every password field.
- Select Cancel and confirm that the form closes and clears its error.
- Expand Password again.
- Repeat with mismatched confirmation and confirm that the password remains unchanged.
- Enter the correct current password and matching new passwords with at least eight characters.
- Select Change password and confirm that the login page opens.
- Confirm that all previous browser sessions require a new sign-in.
- Confirm that the old password fails and the new password succeeds.
- Repeat as Viewer, Deployer, Maintainer, and Admin.
- Check labels, Tab order, scrolling, and error focus at phone and desktop widths.
- At tablet and desktop widths, confirm that Sign-in methods and MFA & recovery appear in separate columns.
- At phone width, confirm that the groups stack without horizontal scrolling.
- For an SSO-only account, confirm that Security shows identity-provider guidance instead of the password form.
- Send the same credentials to
POST /api/v1/users/me/passwordwith an unrestricted global bearer token. - Confirm a
204response and confirm that the bearer token remains valid. - Confirm that project-scoped tokens and tokens with permission ceilings receive
403. - Confirm that invalid credentials receive
422and cannot change another account.
Run the focused API and browser regressions:
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -timeout=4m -v -run '^TestSelfPasswordChange' ./internal/handler/api
Reusable project templates (#76)
The running-server suite retains a source project, template, destination project, frozen release, and restricted token metadata. The suite prints links to each project, template preview, and release. It creates no schedules or deployments for this scenario.
Both template types share the Create template menu. Select New step template to open the step template form. Select Save from project to choose a source project. Select its card to open the Save as Template form directly. After saving, confirm that this route returns to Project Templates. After saving from a project’s Project menu, confirm that the form returns to that project. Select How templates work to open the help dialog. Switch between template types and confirm that the header and cards keep their vertical positions.
- Open the retained source project as a Maintainer or administrator.
- Open Project menu and select Save as Template under Settings.
- Review the ordered steps, scripts, execution settings, variable definitions, and copied non-secret defaults.
- Confirm that secret values and package credentials are absent.
- Enter a unique template name and description.
- Select Save Template.
- Open Templates and select Project Templates.
- Select the saved template card.
- Enter a new project name.
- Map each source environment to an existing environment.
- Supply a fresh value for each secret definition.
- If a package source exists, review its settings and supply fresh credentials.
- Review the agent selectors.
- If a selector is unavailable, replace it or clear it explicitly.
- If you are a global administrator, select an authorized lifecycle or No lifecycle.
- Confirm the review checkbox.
- Select Create Project.
Leave Use replacement value clear to copy a non-secret default exactly, including line breaks. To replace a default, select Use replacement value and enter its replacement value.
Expect new project, step, variable, and package identities. Expect copied scripts, interpreters, ordering, restrictions, timeouts, retries, images, modes, network settings, and approval settings. Expect only the creator membership in the new project. Expect no copied history, notification settings, schedules, tokens, or runbooks.
Change a source step and variable after saving the template. Confirm that the saved template and destination project keep their previous configuration. Change a destination step after creating a release. Confirm that the release keeps its snapshot.
Submit missing secrets or invalid environment mappings through the public API.
Expect 422 and no partial project.
Repeat a creation request with an existing name.
Expect 409.
Check both creation audit events through /api/v1/admin/audit.
Remove source-project membership from a test user.
Confirm that template lists and previews no longer expose the source template.
Confirm that Viewers and Deployers see no save or apply controls.
Confirm that their API writes return 403.
Confirm that scoped tokens cannot create a project from a template.
Templates use current source-project access and disappear when the source project is deleted. Created projects remain independent. Runbooks and shared lifecycle configuration are excluded from this initial feature.
Preceding stages for promotion (A18)
As an Admin, force a release to production before test succeeds. Confirm that production still names test as its prerequisite for normal deployment.
- Create a lifecycle with dev, test, and prod in that order.
- Assign the lifecycle to a project with an executable release.
- Open its deploy form and select the release.
- Confirm that the environment guidance requires dev before test and test before prod.
- Attempt early production deployment through the public API and confirm a 422 response.
- Deploy the release successfully to dev.
- Reload the form and confirm that test is available while prod still requires test.
- Deploy successfully to test and confirm that prod becomes available.
- Deploy successfully to prod.
- Repeat the guidance checks at 375, 390, 768, and 1280 pixels as a project Deployer.
- Confirm that a Deployer cannot use Force to bypass the restriction.
- Select a different release and confirm that its promotion sequence starts again.
- Retain the lifecycle, project, release, environments, and deployments.
This check creates no schedules.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -timeout=4m -v -run '^TestPromotionGuidance(APIWeb|Browser)E2E$' ./internal/handler/api
Saved version for runbook Retry (A17)
- Execute a failing runbook version 1.
- Save version 2 with a different marker and a successful exit.
- Execute version 2 and wait for success.
- Open the original failed execution at 375, 390, 768, and 1280 pixels.
- Select Retry.
- Confirm that the prompt names saved runbook version 1 and excludes later edits.
- Cancel the prompt and confirm that no execution was created.
- Select Retry again and accept the prompt.
- Read the new execution through the public API.
- Confirm that version 1 runs with its original marker and fails.
- Confirm that the original execution and logs remain unchanged.
- Repeat the read check as a project Viewer and confirm that Retry is absent.
- Retain both versions and all executions for repeat checks.
This check creates no schedules.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -timeout=4m -v -run '^TestRunbookRetryVersion(APIWeb|Browser)E2E$' ./internal/handler/api
Phone runbook execution history (A16)
- Create a runbook with a step that exits with an error.
- Execute the runbook and wait for failure.
- Retry the execution and wait for failure.
- Save another version and execute it in a different environment.
- Open the project runbook history at 375 and 390 pixels.
- Confirm that each card shows its execution ID, runbook name, version, environment, status, and creation date.
- Confirm that long names wrap without horizontal scrolling or overlapping fields.
- Select the environment or date area of an execution card.
- Confirm that the correct execution opens, then repeat with Tab and Enter.
- Repeat the history check at 768 and 1280 pixels.
- Repeat the checks as a project Viewer.
- Read
/api/v1/projects/{id}/runbook-executionsand compare the execution identities. - Retain the runbook, versions, environments, and executions for repeat checks.
This check creates no schedules.
Run the focused API and browser regressions with:
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -timeout=4m -v -run '^TestRunbookHistoryPhone(APIWeb|Browser)E2E$' ./internal/handler/api
Add Step ordering (A15 follow-up)
Open a disposable project’s Steps page at phone and desktop widths. Add two steps through the modal. Confirm that their order values are 0 and 1. Submit an empty name before saving the second step. Confirm that validation retains the form and does not create a step. Move the second step up and down.
Create a release before adding another step.
Add a step through the API with sort_order: 0.
Confirm that it appends after the current steps.
Confirm that the saved release retains its original steps.
Run a disposable release whose first step fails.
Confirm that later steps have no execution output.
The saved deployment shows State unavailable when a step has no recorded state event. This label does not claim that the step ran or succeeded. Existing deployment snapshots and logs must stay unchanged.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestStep(AddOrderAPIWeb|AddPreservesSavedFailureAPIWeb|AddModalBrowser|EditModalBrowser)E2E$' ./internal/handler/api
Queued cancellation step explanations (A15)
- Create a release with at least two steps.
- Start a slow deployment in its project and environment.
- Start a second deployment in the same project and environment.
- Confirm that the second deployment shows
queued. - Open the second deployment at 375, 768, and 1280 pixels.
- Select Cancel while its step panels remain collapsed.
- Confirm that each step shows
Not run: cancelled while queuedwithout reloading. - Open a step with Enter or its summary control.
- Confirm that its log explains why no output exists.
- Reload the page and repeat the panel check.
- Read
/api/v1/deployments/{id}/logsand its structured stream. - Confirm that each frozen step has a
cancelledrecord with the explanation. - Confirm that the first deployment remains active.
- Cancel the first deployment and wait for confirmed cleanup.
- Read the second deployment as a project Viewer.
- Retain both deployments and their release for repeat checks.
The cancelled queued steps must never print script output. Cancellation after execution starts must retain its existing states and output. This check creates no schedules.
Run the focused API and browser regression with:
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -timeout=4m -v -run '^TestQueuedCancellation(APIWeb|Browser)E2E$' ./internal/handler/api
Dedicated agent port
Run make agent-e2e-sqlite to check explicit ports and the default dedicated port 23187.
Keep port 23187 available before running this isolated suite.
The suite checks web pairing, API deployments, polling, and streamed logs with the pinned standalone agent source.
Browser screenshots and request results appear under .omo/evidence/continue-remote-agent-rollout/.
The isolated suite removes its temporary servers and databases.
For an existing installation, follow the port migration instructions.
Open Admin, Agents and confirm that the existing agent returns to healthy status.
Run a deployment against its environment through the web UI.
Confirm that streamed logs arrive and the deployment succeeds.
Repeat deployment through POST /api/v1/projects/{id}/deployments.
Confirm that GET /api/v1/deployments/{id}/status reports success.
Keep the deployment links for repeat checks.
This port check creates no schedules.
Four permission levels
Keep the printed Permission role project, release link, and four account logins.
The suite retains deployment history and creates no schedules for these examples.
Sign in with each printed account.
Confirm that Viewer cannot deploy or edit content.
Confirm that Deployer can deploy the existing release and read its logs.
Confirm that Deployer cannot edit, delete, refresh, or create project content.
Confirm that Maintainer can author content and execute runbooks.
Confirm that Maintainer cannot approve deployments or manage global users.
Retry and cancel a deployment with the Deployer account.
Confirm that the retry retains the original deployment snapshots.
Open each forbidden form through its direct URL.
Confirm that it shows a permission message.
Submit a forged configuration request through the browser or API.
Expect a browser HTMX toast, a native browser 403, or an API JSON 403.
Read roles for membership ceilings and upgrade behavior.
Project deployment summaries
Create two projects that share one environment.
Deploy a release from the first project through POST /api/v1/projects/{id}/deployments.
Wait for the deployment to succeed.
Deploy six releases from the second project into the same environment.
Read GET /api/v1/projects/{id}/deployments?env_id={environment_id} for the first project.
Confirm that the API lists only that project’s deployments, newest first.
When creation times match, confirm that larger deployment IDs come first.
Open the dashboard, project card, project environment table, and successful deployment detail. Confirm that each view shows the first project’s own release and status. Repeat at desktop and phone widths. Repeat with a Viewer and a Deployer. Repeat with a lifecycle assigned to the project.
Create another project without deployments. When an environment is available, expect No deployments yet on its card. When no environments are available, expect No environments. After a failed attempt, confirm that the environment table retains the earlier successful release. These checks require no schedules.
bash scripts/go_test.sh -race -count=1 -run '^TestProjectEnvironmentDeploymentHistory$' ./internal/repository
bash scripts/go_test.sh -tags=e2e -race -count=1 -run '^TestProjectPanelScopeAPIWebE2E$' ./internal/handler/api
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestProjectPanelScopeBrowserE2E$' ./internal/handler/api
Full release content (A14)
Create a release with long scripts, long variable names, and a secret variable. Change the project’s current script and variable name after creating the release. Open the release at 375, 390, 768, and 1280 pixels. Select View full script below each step. Confirm that the full saved script wraps within the page. Confirm that each complete saved variable name appears once in its table cell.
Confirm that long names wrap without horizontal scrolling. Confirm that variable names have no disclosure controls. Press Tab to focus each script disclosure control. Press Enter to close and reopen each script disclosure. Repeat these checks with a Viewer account. Confirm that secret values remain masked.
Read the same release through the public API. Confirm that scripts and names match the saved release content. Keep the release for repeat checks. These checks require no deployment or schedule.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestReleaseContent(APIWeb|Browser)E2E$' ./internal/handler/api
Variable identifier guidance (A13)
Open a disposable project’s variable forms at 375, 768, and 1280 pixels.
Confirm that Name guidance explains the ASCII format, 255-character limit, first character, and reserved names.
Enter bad name and attempt to submit.
Confirm that validation retains the draft and explains the required format.
Confirm that a red invalid-name message stays below Name until the input is corrected.
Create Mixed_2 with an empty value.
Edit that variable and attempt to rename it to 1BAD.
Confirm that the edit form and guidance fit within the viewport.
Confirm that rejection leaves the saved variable unchanged.
Correct the name and save again.
Repeat invalid creation and updates through the public variable API.
Expect 422 for NUL, equals signs, embedded whitespace, leading digits, hyphens, Unicode, and names longer than 255 characters.
Confirm that invalid requests store no changes.
Confirm that empty values, masked secrets, and environment overrides still work.
Keep all examples for repeat checks.
These checks require no schedules.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^Test(VariableNames(APIWeb|Browser)E2E|ProjectVariableNamesRejectInvalidWrites)$' ./internal/repository ./internal/handler/api
Cancel variable edits
Open a project’s Variables page at phone and desktop widths. Edit an existing variable. Change Name, Value, Environment, and Secret. Select Cancel. Select Edit again. Confirm that all fields show their saved settings. Repeat with a secret variable. Confirm that Value stays empty and uses a password field. Repeat Cancel and Edit twice. Read the variable through the public API. Confirm that Cancel stores no changes. Change Name and select Save. Confirm that the public API returns the new name and the original scope and Secret setting. Keep the variables for repeat checks. These checks require no schedules.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestVariableCancelBrowserE2E$' ./internal/handler/api
Duplicate release recovery (A12)
Create a disposable project and a release named edge-v1.
Open the releases page at phone and desktop widths.
Submit edge-v1 again.
Confirm that the error retains edge-v1 and instructs you to enter a different version.
Confirm that the page shows error feedback and creates no duplicate release.
Change the version and submit again.
Confirm that the new release appears beside the original release.
Post the original version through POST /api/v1/projects/{id}/releases.
Expect 409 with the same recovery instruction.
Change the API version before retrying.
Expect 201 and a new release ID.
Confirm that the original release snapshot remains unchanged.
Keep all examples for repeat checks.
These checks require no schedules.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestReleaseDuplicateRecovery(APIWeb|Browser)E2E$' ./internal/handler/api
Connected project variable previews (A11 follow-up)
Open a project with local variables and inherited lifecycle variables. Confirm that both sections share one card. Confirm that a divider separates the inherited section. Repeat at 375, 390, 768, and 1280 pixels. Repeat with a Viewer account. Confirm that secret values stay masked.
Select Manage to open the project Variables page. With more than six local variables, confirm that the preview shows a count and only one Manage link. Confirm that inherited variables retain their separate card and permitted actions. Repeat with a project that has only inherited variables. Repeat with a project that has only local variables. Repeat with an empty project. Confirm that no empty inherited section or divider appears without inherited variables.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestLifecycleVariables(APIWeb|Browser)E2E$' ./internal/handler/api
bash scripts/go_test.sh -race -count=1 -run '^TestVariablesPreview_' ./internal/handler
Release variable guidance (A11)
Create a disposable project with a lifecycle and a stage environment.
Create a project variable named VALUE with value old.
Create a lifecycle variable named SHARED with value initial.
Create a step that prints both variables.
Create a release.
Change the project value to new and the lifecycle value to current.
Open the release at phone and desktop widths.
Confirm that separate Project variables and Lifecycle variables instructions explain their refresh behavior.
Confirm that the table retains the saved project value old.
When secret variables exist, confirm that their values remain masked.
Deploy the existing release without Refresh.
Expect project=old shared=current in its logs.
Select Refresh on the release.
Accept the confirmation.
Confirm that the table shows new.
Deploy the refreshed release.
Expect project=new shared=current.
Read the release through GET /api/v1/projects/{id}/releases/{relId} before and after Refresh.
Confirm that its project variable values agree with the visible table.
Keep all examples for repeat checks.
These checks require no schedules.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestReleaseVariableGuidance(APIWeb|Browser)E2E$' ./internal/handler/api
Deployment re-run inputs (A10)
Create a disposable project with a non-secret variable named RERUN_VALUE set to base-one.
Create a step that prints the variable with an identifying script marker.
Create a release, then deploy it in a disposable environment.
Keep the original deployment link and logs.
Change the project variable to base-two.
Change the step marker, then refresh the release.
Change the project variable to base-three without another refresh.
Open the original deployment, then select Re-run.
Confirm that the message identifies current release and lifecycle variables, original steps, and unchanged original logs.
Select Cancel, then confirm that no deployment was created.
Select Re-run again, then accept the confirmation.
Confirm that the new deployment prints base-two with the original script marker.
Read the original logs, then confirm that they remain unchanged.
Repeat at phone and desktop widths.
Repeat through POST /api/v1/deployments/{id}/redeploy.
Keep all deployment examples for repeat checks.
These checks require no schedules.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestRerunInputs(APIWeb|Browser)E2E$' ./internal/handler/api
Reachable Step form actions (A09)
Open a disposable project’s Steps page at 375 × 844.
Select Edit for a retained step.
Scroll through the form.
Confirm that Save and Cancel remain visible above the scrolling fields.
Focus Container image, then scroll it toward the top of the fields.
Confirm that the action header does not cover the field or its focus indicator.
Repeat with a shortened viewport at 375 × 420.
Repeat at desktop width and through Add Step.
Change Timeout and Max retries, then select Cancel.
Confirm that the API still returns the original values.
Submit an empty Name with changed Timeout and Max retries.
Confirm that validation keeps both lower fields.
Correct Name, then select Save.
Confirm that GET /api/v1/projects/{id}/steps/{stepId} returns the saved values.
Keep the project and step for repeat checks.
These checks create no executions or schedules.
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestStepFormActionsBrowserE2E$' ./internal/handler/api
Swagger request target
Scoped-token verification (V01)
Retained example: project B (example route: /projects/21), deployment45 (example route: /deployments/45).
Projects20 and22 retain the removed and unselected examples. Environment37 and release29 remain available.
The disposable token is revoked. No schedules were created.
- Create a disposable Viewer token with selected projects A and B.
- Open
/api/swagger/and verify the displayed API target. - Enter the raw token through Authorize. Keep its value out of saved output.
- Execute
GET /projects. Expect only A and B, with total2. - Execute
GET /projects/{id}for unselected project C. Expect403. - Execute
POST /projects/{id}/deploymentsfor B with a valid release and environment. Expect403 and no new deployment. - Change the same token to Deployer access for B only through its owner settings or API.
- Keep the original Swagger authorization. Execute
GET /projects. Expect only B, with total1. - Execute
GET /projects/{id}for removed project A. Expect403. - Execute the same valid deployment request for B. Expect201 and one new deployment.
- Execute
GET /deployments/{id}after completion. Expect200 and statussucceeded. - Revoke the token through its owner settings or API.
- Repeat the project list and deployment requests with the cached Swagger authorization. Expect401 and no additional deployment.
- Clear Swagger authorization. Redact generated curl credentials before saving screenshots.
The native check used the backend on port53987. A02 separately verifies HTTPS and a non-default proxy port.
Some denied operations show Undocumented beside the actual403 response. Read the numeric status and returned JSON.
The auxiliary global deployment-list operation currently requests an unused required id in Swagger.
That auxiliary check did not send an HTTP request. It is outside the V01 project-list and deployment-creation checks.
Run the focused regression:
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -timeout=4m -v -run '^TestSwaggerTokensBrowserE2E$' ./internal/handler/api
Request-target verification (A02)
Open /api/swagger/ through the instance’s HTTPS proxy on a non-default port.
Before authorization, confirm that API target shows the current HTTPS origin and /api/v1.
Expand GET /healthz in Swagger.
Select Try it out and Execute.
Expect status 200 and {"ok":true} from the current instance.
Confirm that Request URL uses the proxy’s HTTPS host and port.
Repeat through the direct HTTP listener.
Confirm that the request uses its HTTP host and port.
Before authorization, execute GET /projects and expect 401.
Authorize a valid token and repeat the request.
Expect the token’s permitted projects.
Clear authorization after the check.
Run the focused API and browser regressions:
bash scripts/go_test.sh -count=1 -run '^TestSwaggerInstanceAPIE2E$' ./internal/handler/api
bash scripts/go_test.sh -tags=e2e,packagebrowser -count=1 -run '^TestSwaggerInstanceBrowserE2E$' ./internal/handler/api
Project saves after permission changes
Use a project Maintainer and an administrator in separate browser sessions. Open Edit Project as the Maintainer. Enter an unsaved description.
In the administrator session, change the Maintainer’s project membership to Viewer. Submit the open project form. If the form opens in a modal, expect one error inside that modal. Confirm that no error toast appears behind the modal. If the form uses a standalone page, expect the existing error toast and inline message. Confirm that no success toast appears. Confirm that the form, description, and page URL remain unchanged.
Read GET /api/v1/projects/{id} as the administrator.
Confirm that the saved description remains unchanged.
Restore the project membership to Maintainer. Save the retained form. Confirm that the description now appears on the project page and through the API.
Repeat the check with a project Deployer downgrade. Repeat the check with global Viewer and Deployer downgrades. Restore both roles after each check. Repeat through the project edit dialog. These checks create no schedules.
Run the focused API and browser regressions:
bash scripts/go_test.sh -count=1 -run '^TestProjectStaleSaveAPIWebE2E$' ./internal/handler/api
bash scripts/go_test.sh -tags=e2e,packagebrowser -count=1 -run '^TestProjectStaleSaveBrowserE2E$' ./internal/handler/api
Project-scoped API tokens
Run make e2e-test against the running server.
Keep the printed selected-project, denied-project, and token management links.
The suite retains e2e-token-a-*, e2e-token-b-*, and e2e-token-foreign-* projects.
It also retains token metadata for all four roles and a revoked token.
These scenarios create no schedules.
Open /settings/tokens as an administrator.
Confirm that a new token defaults to All accessible projects.
Confirm that the permission selector defaults to viewer for either scope.
Create an all-project token for each role and check the same permission limits.
Confirm that permission-limited tokens cannot create or edit tokens through the API.
Select Selected projects.
Select Choose projects.
Search for each printed selected project and select its checkbox.
Confirm that filtering preserves checked projects outside the current results.
Select Done and confirm that both names appear below the picker button.
Create a token for each supported role.
Copy each bearer value from its one-time banner.
Return to the list and confirm that the bearer value disappears.
Submit a selected-project token without any projects. Expect a validation message and no new token. Use Edit scope to change a token’s role and project selection. Confirm that the list shows the saved role and projects. Confirm that editing scope does not display the bearer value again.
Check the page at widths of 375, 430, 540, 768, 980, and 1280 pixels.
Confirm that each form control fills its row without overlap.
Confirm that Projects appears directly below Scope and above Token role in the new and edit forms.
Below 1280 pixels, confirm that token cards show names, access details, and actions.
Open Edit scope on a phone and save a changed selection.
Confirm that Choose projects opens a full-screen picker on the phone.
Confirm that long project names wrap and each project row is easy to tap.
Confirm that the selection count, Clear selection, and Done remain visible while scrolling.
Search for an unmatched name and confirm that the picker shows an empty result message.
Press Enter in the search field and confirm that the token form does not submit.
Close the picker and confirm that focus returns to Choose projects.
Select All accessible projects in the new token form.
Confirm that the project control disappears and the permission selector stays visible.
Select Selected projects and confirm that the project control returns.
Enter a token name and leave the project selection empty.
Select Create token.
Confirm that Select at least one project appears beside Choose projects.
Confirm that the name, scope, and role remain selected.
Select an accessible project and submit again.
Confirm that the bearer value appears once.
Reload the page and confirm that the bearer value disappears.
Edit the token scope and clear its project selection.
Select Save scope.
Confirm that the same error appears beside that token’s Choose projects control.
Confirm that the edit stays open and retains the submitted role.
Confirm that GET /api/v1/tokens shows unchanged saved grants after the rejected edit.
Repeat empty selections with POST /api/v1/tokens and PATCH /api/v1/tokens/{token_id}.
Expect 422 with select at least one project for an otherwise valid scoped request.
Call GET /api/v1/projects with each token.
Expect only selected projects and matching pagination totals.
Request the denied project and its deployments, variables, releases, runbooks, packages, artifacts, and logs.
Expect denial without unrelated metadata.
Attempt project creation, editing, and deletion with Viewer and Deployer tokens.
Expect 403.
Deploy with the deployer token in a selected project.
Expect the owner’s existing membership and approval rules to apply.
Request /api/v1/admin/stats or create another token with any scoped token.
Expect 403, including for administrator-owned tokens.
Remove the owner’s project membership and repeat the request.
Expect denial unless the owner remains a global administrator.
Open a log stream with a scoped token.
Change its selection to remove that project through the owner’s browser session or a global token.
Expect the stream to close within the existing authorization recheck interval.
Revoke the token and repeat an API request.
Expect 401.
Run the isolated API and browser regressions with these commands:
bash scripts/go_test.sh -tags=e2e -race -count=1 -timeout=5m -run '^TestProjectTokens.*APIE2E$' ./internal/handler/api
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -timeout=5m -run '^TestProjectTokensBrowserE2E$' ./internal/handler/api
Project member roles
Open the printed Member role editor link as the global administrator or printed project administrator. Select Edit role for an existing member. Confirm that the selector shows the current project role. Select another role, then select Cancel. Open the editor again and confirm that the saved role remains selected.
Select admin, then select Save.
Confirm that the role changes and a success message appears.
Repeat with viewer, deployer, and maintainer.
Repeat inside the project edit dialog and at a phone viewport width.
Confirm that unsaved project settings remain unchanged.
Read GET /api/v1/projects/{id}/members after each change.
Use PUT /api/v1/projects/{id}/members/{userId} with a JSON role to repeat API changes.
Confirm that the membership creation time, global application role, and second project membership remain unchanged.
Use a project deployer, viewer, and user outside the project to repeat denied attempts. Confirm that role controls remain hidden and forged API or web requests cannot change the role. Use the printed viewer account from the retained checks. Keep all test accounts and project memberships for repeat checks.
Run bash scripts/go_test.sh -run '^TestProjectMemberRolesAPIWebE2E$' ./internal/handler/api for API and web contracts.
Run bash scripts/go_test.sh -tags=e2e,packagebrowser -run '^TestProjectMemberRolesBrowserE2E$' ./internal/handler/api for rendered browser coverage.
Software build identity
Run durpdeploy version, durpdeploy --version, and durpdeploy -v without runtime configuration.
Confirm that each command exits successfully with the same version, build number, and source commit.
Call GET /api/v1/version with a bearer token.
Confirm that the response matches the executable identity.
Open Version in the account menu as an administrator and a viewer.
Confirm that both users can read the same build information at /settings/version.
Repeat at a mobile viewport width.
Open the Agents list and an agent detail page. Confirm that the reported software version matches the fleet API. For an agent without a reported version, confirm that the page shows Not reported. Keep protocol identifiers separate from software versions. Retain the agents and accounts for repeat checks.
Run bash scripts/build_version_test.sh to verify tagged and development executable output.
Run bash scripts/go_test.sh -run '^TestBuildInfoAPIWebE2E$' ./internal/handler/api for API and web permissions.
Run bash scripts/go_test.sh -tags=e2e,agentbrowser -run '^TestBuildInfoBrowserE2E$' ./internal/handler/api for rendered browser coverage.
Manual deployment step panels
Open running, waiting, failed, and completed deployments. Confirm that each step panel starts collapsed. Confirm that step names, status badges, and the current-step summary remain visible. Select a step heading to expand its panel. Select the heading again to collapse its panel. Repeat with a keyboard and a phone.
Keep one panel open and another panel closed while live logs arrive. Confirm that each panel keeps your selection through step changes, failure, and completion. Open the closed panel and confirm that it shows retained live output. Reload the page and confirm that each step panel starts collapsed again. Manual selections apply while the current page remains open.
Retain the deployments and releases for repeat checks.
Run TestDeploymentStepPanelsBrowserE2E for automated browser coverage.
Run TestDeploymentStepLogsAPIWebE2E and TestDeploymentStepLogOutcomesAPIWebE2E for the existing API contracts.
Follow live logs
Start a deployment with two steps that print log lines for at least 30 seconds. Open the deployment page. Expand the step panels that you want to watch. Scroll to the latest visible log line. Expect Follow logs to show On. Confirm that new output stays visible through step changes and completion. Confirm that each step panel keeps your open or closed selection.
Close the next step panel while its logs arrive. Select Follow logs. Confirm that the page follows output in the panel you left open. Confirm that the closed panel stays closed.
Scroll up to read earlier output. Expect Follow logs to show Paused. Confirm that retained lines keep their screen position when older lines leave the preview window. If the preview removes the line you read, use Older logs to read it again. Select Follow logs with the mouse or keyboard. Confirm that the page returns to the latest visible output. Scroll up again, then return to the live end to resume following.
Print a single wrapped log line that is taller than the viewport. Scroll to the middle of that line. Select Follow logs. Confirm that the end of the line becomes visible.
Switch to another browser tab while logs arrive. Return to the log page. Confirm that background updates did not force the page to the latest output. Select Older logs. Confirm that historical pages have no Follow logs control and receive no live updates.
Repeat these checks for a runbook execution and a phone viewport.
Run TestLogFollowingBrowserE2E for API deployment, log API, and browser coverage.
The browser test checks the visibility handler with real SSE output and an injected visibility event.
Use two browser tabs for the manual background check.
The running-server suite retains both log examples and prints their links.
Retain the project, release, environment, runbook, and execution history for repeat checks.
Keep schedules disabled.
Step execution locations
Create a release with a server container step, an agent host step, and an agent container step. Assign the agents to the deployment environment. Use distinct step names and a short delay to inspect live output. Deploy the release through the public API or web form.
Check the execution location entry before each step’s script output.
Confirm that the physical hostname matches the machine that runs the step.
Confirm that server steps identify their container name and image.
Confirm that agent steps identify the selected agent and distinguish container from host/shell.
Configure one retry. Repeat the deployment. Confirm that each attempt has its own location entry and container name. Assign two matching agents to one remote step. Confirm that the fan-out logs identify both agents and execution locations.
Reload the deployment page after completion.
Read GET /api/v1/deployments/{id}/logs.
Download /deployments/{id}/logs.txt.
Confirm that both surfaces retain the same execution locations.
Repeat the check with a viewer account.
For a containerized host agent without runtime information, set DURPDEPLOY_AGENT_HOSTNAME before starting the agent.
Confirm that the configured physical hostname appears in its logs.
Without this setting, an unavailable physical hostname appears as unknown.
Retain the project, release, environment, deployments, and agent assignments for repeat checks.
Keep schedules disabled.
Automated API and web coverage uses TestExecutionLocationAPIWebE2E.
Rendered browser coverage uses TestDeploymentStepLogsBrowserE2E.
AGENT_TEST_SOCKET=unix:///run/user/1000/podman/podman.sock DURPDEPLOY_AGENT_E2E_BINARY=/absolute/path/to/durpdeploy-agent bash scripts/go_test.sh -tags 'e2e agentstaging' -race -count=1 -timeout=5m -run '^TestExecutionLocationAPIWebE2E$' ./internal/agentserver
Deployment staging transfer
Run the transfer timeout API checks with the current server and agent builds:
AGENT_TEST_SOCKET=unix:///run/user/1000/podman/podman.sock DURPDEPLOY_AGENT_E2E_BINARY=/absolute/path/to/durpdeploy-agent bash scripts/go_test.sh -tags 'e2e agentstaging' -count=1 -timeout=5m -v -run '^TestDeploymentStagingTransferPausesStepTimeoutAPIE2E$' ./internal/agentserver
The download and upload checks delay transfers beyond the three-second step timeout. Both deployments must succeed. The slow script must fail. All three checks must confirm staging cleanup.
Build both the server and a staging-capable agent.
Assign two agents to the test environment.
Give each agent a distinct selector label.
Create a deployment with a server container step that writes a file under DURPDEPLOY_STAGE_DIR.
Add an agent host step that reads this file and writes another file.
Add a container step on the second agent that reads both files.
Add a server container step that reads the final staging files.
Deploy the release and confirm that every step succeeds.
Create a parallel agent step that changes the same file differently on each agent.
Deploy this release and confirm that the deployment fails with conflicting staging changes in its logs.
Repeat with identical changes and confirm that the deployment succeeds.
Repeat with different file names and confirm that the next step receives both files.
After success, failure, or cancellation, request GET /api/v1/deployments/{id}/staging.
Confirm that the endpoint returns [].
Request GET /api/v1/admin/audit?action=publish_staging with an administrator token.
Read details.String as JSON.
Check its agent_id for each agent that published staging.
Confirm that agent staging directories and deployment staging volumes are empty.
Start another deployment and confirm that its staging directory contains no previous files.
Use the isolated API suite in docs/deploy.md to repeat these checks automatically.
Add a host step that starts a detached writer with setsid and then exits.
Log the detached child’s process ID before the step exits.
Deploy the release and confirm that the detached process stops before the next step starts.
Repeat with a double-forked child and with deployment cancellation.
Confirm that all logged child process IDs are absent after completion.
Add a host step that moves its workspace outside the staging root. Confirm that publication fails and the moved directory contains no files. Repeat with a step that moves the entire staging root. If supervisor confirmation fails, restart the agent through its service manager. Confirm that restart cleanup purges moved staging directories before it sends the saved failed result.
Releases with a deferred package pull
Repeat the staging transfer checks with an HTTPS ZIP repository attached to the project.
Read the package through ARTIFACT_PATH in each server and agent job.
Copy its files into DURPDEPLOY_STAGE_DIR before modifying them.
Confirm that agent host jobs and agent container jobs receive the same pinned package.
Confirm that containers reject writes to /artifacts.
Confirm that later jobs receive staging outputs from each execution location.
Check cleanup after success, failure, cancellation, and conflicting parallel changes. Replace the upstream ZIP after pinning and confirm that the deployment fails.
Run the package transfer API checks against the current agent binary:
AGENT_TEST_SOCKET=unix:///run/user/1000/podman/podman.sock DURPDEPLOY_AGENT_E2E_BINARY=/absolute/path/to/durpdeploy-agent bash scripts/go_test.sh -tags 'e2e agentstaging' -count=1 -timeout=5m -v -run '^TestDeploymentStagingPackageAPIE2E$' ./internal/agentserver
- Attach a package repository to a disposable project. Use a missing version
or an unreachable repository such as
https://asdf/{version}. - Enter a version and click Create Release. Creation succeeds without a network request. Release detail shows Package awaiting first pull.
- Deploy the release. A missing package, DNS/TLS/authentication failure or invalid ZIP fails the deployment before any steps execute. Inspect its logs.
- Repeat via
POST /api/v1/projects/{id}/releaseswith{"version":"missing"}: expect201, with the package still attached.GET /api/v1/projects/{id}/releases/{releaseId}/artifactreturnspending: true, emptysha256, andsize: 0. - Publish the package and deploy again. The first valid pull locks its
checksum and size before steps start; the artifact API reports
pending: false. Repeated pulls reject different bytes at that URL. - Replace the package bytes and click Refresh (or
POST /api/v1/projects/{id}/releases/{releaseId}/refresh). Refresh validates and replaces the release checksum, updates steps/variables, and refuses active/unconfirmed deployments. New deployments use the refreshed pin; re-runs and rollback retain the original deployment pin. - Save a runbook from a pending release to check shared first-pull pinning. Refreshing or deleting the source release preserves the copied generation.
Retain the project, releases, runbooks and deployment history for repeat checks.
Keep schedules disabled. Automated API/web/container coverage is
TestReleaseDeferredPackageAPIWebE2E; rendered browser coverage is
TestReleaseDeferredPackageBrowserE2E. Run scripts/e2e_artifacts.sh for API checks.
Bounded log history
Startup indexes step metadata and backfills existing log history once. Allow extra startup time for databases with large log histories.
Create a disposable project with a Bash container step. Use this script to produce more than two pages of output:
for i in $(seq 1 1200); do
printf 'history-%04d\n' "$i"
done
Deploy the release and open its deployment page.
Expand the step panel.
Confirm that the page shows at most 500 recent entries.
Confirm that the viewer retains at most 1 MiB of previews.
Select Older logs and confirm that earlier entries appear.
Confirm that the page states that live updates are paused.
Confirm that completed step states remain visible on older pages.
Select Latest logs to return to recent output.
Select Export all logs and confirm that both history-0001 and history-1200 appear.
Repeat these checks with a runbook containing the same step.
Repeat navigation with the keyboard and a narrow browser window.
Request GET /api/v1/deployments/{id}/logs/page.
Confirm that items contains at most 500 entries with increasing IDs.
Confirm that the JSON response contains at most 1 MiB, including its envelope.
Check byte_limit and line_byte_limit for the documented limits.
Pass next_before as ?before=ID to read an older page.
Confirm that pages do not overlap.
Repeat with /api/v1/projects/{id}/runbook-executions/{executionId}/logs/page.
Confirm that the existing /logs endpoint still returns complete history.
Use a nonmember account and confirm that page and export requests return 403.
Repeat the deployment and runbook checks with oversized lines:
line=$(printf '%020000d' 0)
for i in $(seq 1 180); do
printf 'long-%03d:%s:complete-tail-%03d\n' "$i" "$line" "$i"
done
Confirm that oversized lines show a truncation marker.
Confirm that the viewer can retain fewer than 500 entries because of the byte budget.
Check that previews contain at most 16 KiB per text field, including their markers.
Confirm that shortened API entries contain truncated: true.
Page through the history and confirm that each persisted ID appears once.
Export all logs and confirm that every complete-tail-* marker remains present.
Use format=structured&preview=true to request bounded live previews through the public API.
Omit preview and confirm that the full stream preserves complete lines.
Retain the project, release, runbook, and execution history.
Keep schedules disabled.
Automated checks include TestDeploymentLogHistoryPagesAPIWebE2E and TestRunbookLogHistoryPagesAPIWebE2E.
Browser checks include TestDeploymentLogHistoryBrowserE2E and TestRunbookLogHistoryBrowserE2E.
TestOpenLogStreamRevocationAPIWebE2E checks open streams after session or token revocation.
TestRunbookLogRevocationBrowserE2E checks browser access after project membership removal.
Runbook deletion
Use disposable runbooks for deletion checks. Retain the corresponding project, completed executions, and logs. Retain a second runbook with the same steps for repeat checks. Keep schedules disabled after testing.
- Create a runbook with two saved versions.
- Execute the first version and wait for completion.
- Create a schedule for the runbook.
- Open the runbook detail page and select Delete runbook.
- Check the runbook identity and deletion effects in the confirmation.
- Cancel confirmation and check that the runbook remains available.
- Repeat confirmation and accept deletion.
- Check that the runbook leaves the active list.
- Open the retained execution and check its original logs.
- Check that Retry is absent.
Repeat deletion with DELETE /api/v1/projects/{id}/runbooks/{runbookId} and an empty body.
Expect 204 without a response body.
Expect 404 for repeated deletion and inaccessible runbooks.
Expect 409 while executions or cleanup remain unfinished.
Expect 403 for viewer API writes.
Check that viewer pages omit the Delete action.
Check that web writes without valid CSRF tokens fail.
Deletion retains saved versions, backing releases, artifact pins, completed executions, and logs. Deletion disables schedules and prevents future execution, retry, version saving, and schedule creation. Deleted names remain reserved within the project. Existing project and environment deletion policies still apply to retained history.
Automated checks include TestRunbookDeletionAPIWebE2E and TestRunbookDeletionBrowserE2E.
Server step resource policy
Server-side deployment steps have no DurpDeploy-imposed RAM ceiling or CPU quota. PID limits, temporary-filesystem bounds, and other isolation remain. Helper/infrastructure containers and remote agents retain their existing resource policy; the agent follow-up is issue #14.
For a manual check on a running instance, create a disposable project through
POST /api/v1/projects, add a local Bash step using
harbor.durp.info/dockerhub/library/bash:5.2 through POST /api/v1/projects/$PID/steps, and use
printf payload > "$DURPDEPLOY_STAGE_DIR/data"; sleep 120 as its script.
Create its release and deployment through the corresponding project API
endpoints. During execution, inspect its durpdeploy-$DID-* step container:
podman inspect --format '{{json .HostConfig}}' STEP_CONTAINER
Use docker inspect on Docker hosts. Expect Memory and NanoCpus to be 0,
CpuQuota to be 0 or -1, PidsLimit to be 128, and ReadonlyRootfs to
be true. Check step containers rather than the staging keeper. External
host/cgroup limits can still constrain execution. Add a second step that reads
"$DURPDEPLOY_STAGE_DIR/data" to repeat the check with artifact handoff.
Let each deployment finish, or cancel through
POST /api/v1/deployments/$DID/cancel and wait for confirmed cleanup. Retain
the project, release, and history for repeat checks; no schedule is needed.
The isolated API regression check exercises live containers and both steps:
go tool templ generate
make swagger-ui-copy
DURPDEPLOY_CONTAINER_RUNTIME=podman \
DURPDEPLOY_CONTAINER_URL="unix://${XDG_RUNTIME_DIR}/podman/podman.sock" \
go test -tags=e2e -race -count=1 -run '^TestDeploymentStepResourcesE2E$' ./internal/handler/api
Phase 1 regression checks
Create two projects with different members.
Sign in as each member and inspect / and /dashboard/deployments.
Verify every deployment section and count excludes the other project.
Sign in as an administrator and verify both projects remain visible.
Open deployment and runbook log streams with browser sessions and API tokens. Remove the stream user’s project membership through the API. Verify responsive streams close within approximately two seconds. Verify reconnect requests fail and new output does not appear. A stalled consumer can delay closure up to 62 seconds.
Configure project and global notification credentials through their API endpoints. Inspect GET and PUT responses and both settings pages. Verify credential fields remain empty and configured indicators remain visible. Save blank fields to preserve credentials. Enter replacements to update credentials. Use clear flags or Clear credential controls to remove credentials. Verify delivery history does not expose provider credential URLs or tokens.
On each settings page, enter a Gotify URL without http:// or https://.
Select Save.
Verify an error toast appears and all entered values remain.
Correct the URL.
Select Save.
Verify the Notification settings saved toast appears.
Enter an invalid Slack or Discord webhook URL. Select its Clear credential control. Select Save. Verify the credential shows not configured.
Deploy a trusted disposable script that exceeds one MiB of output without a newline. Verify the attempt fails, reports its pending-output limit, and removes its container. Verify raw pending output does not appear in stored or streamed logs.
Keep retained schedule examples disabled after checks. Enable a disposable schedule through its existing toggle control to repeat scheduling checks.
List and authentication checks
Open a protected project URL in a separate signed-out browser session.
Confirm that the login URL contains the requested local destination.
Enter an incorrect password and submit the form.
Confirm that the email remains visible and the password field is empty.
Confirm that the error remains generic.
Enter the correct password and submit again.
Confirm that the requested project opens with its original query parameters.
Repeat at phone and desktop widths.
Repeat with an MFA-enrolled account and through configured SSO.
Confirm that completion returns to the requested page.
Use an account without project access and repeat the login.
Confirm that the project still denies access.
Open /login?next=//example.invalid and complete login.
Confirm that the dashboard opens on the same instance.
Keep the test project and accounts available for repeat checks.
Run the public login and browser regressions:
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -timeout=4m -run '^TestLoginDestination(APIWeb|HTMXAPIWeb|Browser)E2E$' ./internal/handler/api
Schedules show 50 entries per page.
Create 51 disabled schedules in a disposable project.
Open the second page with Next.
Select New Schedule.
Select Cancel.
Confirm that the second page remains selected.
Edit one schedule and select Save.
Confirm that the second page remains selected.
Toggle the schedule twice to leave it disabled.
Delete the last entry on the last page.
Confirm that the list shows the preceding page.
Compare the list with GET /api/v1/projects/{id}/schedules?limit=50&offset=50.
Home deployment panels refresh every five seconds.
Home charts refresh every 30 seconds.
Open browser network tools and filter requests by /dashboard/activity.
Confirm that deployment panel refreshes do not send extra chart requests.
Open Projects and confirm that chart requests stop.
Return to Home and confirm that chart requests resume.
Passkey names use a limit of 255 UTF-16 code units after whitespace trimming.
Most characters use one unit.
Some symbols, including many emoji, use two units.
Open Security and enter a passkey name.
Confirm that the field limits input to 255 units.
Submit an oversized name through the session-authenticated registration or rename endpoint.
Confirm that the response is 422 and the credential remains unchanged.
Open navigation at phone, tablet, and desktop widths. Check Security, role-dependent Tokens, theme switching, and Logout. Open a lifecycle with multiple stages. Move a stage and confirm that keyboard focus remains usable. Confirm that boundary stages show only their available move controls. Use a viewer account and confirm that write controls remain hidden.
Populated demo
For a complete local demo, run make demo (or ask a coding agent to run it).
The launcher builds the server, serves https://citadel.durp.loc:<random-port>
through Caddy with a certificate for that hostname, pairs a
separate containerized agent, verifies a remote deployment, and runs
make e2e-test against the same server. It then exits while the app and agent
remain running. Sign in as admin@durp.info with the generated demo-…
password printed by the launcher. The login details are also in the private
login.txt in its printed data directory.
Each invocation uses its own database, server identity, encryption key, agent
state volume and container names, so multiple demos can run together. The
HTTPS port listens on the computer’s network interfaces; citadel.durp.loc
must resolve to this computer. The plaintext backend stays on loopback
(Podman) or Docker’s private bridge. The agent’s authenticated TLS listener
also has a random port and is reachable by the local container runtime.
The exact HTTPS origin configures secure cookies and passkeys; TOTP and
password login remain available. Each demo uses a temporary self-signed
certificate in its private tls/cert.pem; browsers may require trusting it.
The script validates that certificate and hostname on its API calls without
disabling TLS verification, and passes the certificate to make e2e-test
through DURPDEPLOY_E2E_CA_FILE. Requirements: Linux with setsid, Go, Node/npm,
make, OpenSSL,
Python 3, SQLite CLI, curl, and working local Podman or Docker. The existing development agent
image is pulled by make dev-agent; Caddy uses the existing development
proxy image harbor.durp.info/dockerhub/library/caddy:2-alpine.
Loopback-only demo
Start the rootless Podman socket before launch.
Run DEMO_LOCAL_ONLY=1 DURPDEPLOY_CONTAINER_RUNTIME=podman make demo for access from this computer only.
The HTTPS URL uses https://127.0.0.1:<random-port>.
The HTTPS proxy, HTTP backend, and agent listeners bind to loopback.
The certificate includes the 127.0.0.1 IP address and localhost name.
The launcher skips the firewall watcher, even when its helper is installed.
Docker and rootful Podman are unsupported in this mode.
Set DEMO_LOCAL_ONLY to 0 or 1.
Server-only refresh preserves the listener addresses and URL.
For full refresh, use DEMO_LOCAL_ONLY=1 DURPDEPLOY_CONTAINER_RUNTIME=podman make demo-refresh-full DEMO_DIR=/absolute/demo/path.
Optional firewall access
The helper trusts the authorized local operator to select ports. This scope intentionally permits any TCP port from 32768 through 60999. The helper does not maintain an administrator-approved list of demo ports. Grant this permission only to operators trusted to expose services in that range. Leases expire after 120 seconds without renewal.
Optional firewalld access: install scripts/demo-firewall-helper.sh root-owned
at /usr/local/libexec/durpdeploy-demo-firewall (mode 0755), and authorize only
that executable through a passwordless sudoers entry for your account. Run
bash scripts/install-demo-firewall.sh once as your regular account; it asks
for sudo authentication and validates its sudoers file before installing it.
To undo authorization, remove /etc/sudoers.d/durpdeploy-demo with sudo;
existing leases expire within 120 seconds when renewal fails. Future
make demo runs automatically start an unprivileged watcher. For an existing
demo, run `nohup setsid bash scripts/demo-firewall-watch.sh DEMO_DIRECTORY
DEMO_DIRECTORY/firewall.log 2>&1 </dev/null &
. The helper grants TCP ports 32768-60999 in thepubliczone using a tagged runtime rich rule. It removes only that tagged rule before renewing its fixed 120-second lease every 30 seconds; this briefly interrupts permission for new connections during renewal. The watcher checks the original HTTPS container identity every second and releases the rule when it stops. If the watcher dies, the last lease expires within 120 seconds. Existing port/service permissions remain intact. This permission allows the authorized account to lease any port in that range; it does not verify that an individual port belongs to a demo. No permanent firewall permissions are changed. Rule logging is limited to one per minute. If setup or renewal fails, inspectfirewall.log`; initial failure aborts a new demo instead of reporting a reachable URL. Ports outside the allowed range require a new demo or administrator adjustment of the installed helper.
The demo agent enables agent/3 container execution through the same local
Docker/Podman Unix socket used by the server. Start that socket before launch
(on Citadel: systemctl --user start podman.socket). It verifies Bash, Python
and PowerShell container capabilities, then retains a deployment that runs
all three interpreters remotely as well as a host step. Inspect the printed
agent deployment and agent-capabilities.json / agent-deployment-logs.json
in its data directory. The rootless Podman agent maps the operator to its
service UID/GID using keep-id; :U adjusts only the private named state
volume’s ownership. Host socket permissions and labels stay unchanged. The
supervisor’s SELinux label separation is disabled for socket access; each
container-mode workload retains its seccomp, non-root user, resource limits,
read-only root and no-network boundary, with no socket or host directory mounted. The agent
supervisor has the operator’s runtime API authority, as does the server.
Host-mode scripts run inside that supervisor and inherit its socket access;
use only scripts trusted with the operator’s account.
For server/template/asset changes, finish script edits first, then run
make demo-refresh DEMO_DIR=/absolute/demo/path. It rebuilds the server and
CSS/JS, then restarts only the server on the same ports. URL, login, database,
encryption key, server identity, paired agent, proxy, firewall watcher and
certificate remain in place. No re-pairing or E2E repopulation occurs. Wait for
deployments to finish and confirm cleanup; unfinished or lost remote work blocks
refresh.
The final idle check and old-server shutdown hold a database write barrier;
new deployments cannot be admitted until the replacement is ready to start.
The existing server must be running so its configuration can be retained.
Startup settings are preserved rather than taken from the caller’s environment.
Inspect server-refresh-build.log and server.log if refresh fails.
Replacement startup/health failure restores the previous server when idle and
retains server-refresh-failed.log and bin/durpdeploy.failed for inspection.
Fix the source and retry the same server-only refresh command.
Database query/schema/migration changes require
make demo-refresh-full DEMO_DIR=/absolute/old/demo/path. Also use full refresh
for agent version/configuration or demo startup configuration changes.
It stops the old demo and builds/populates a new one. Return the new URL,
login, data directory and certificate path; the old data remains on disk.
Let active deployments finish or cancel them and wait for confirmed cleanup.
Use the printed stop command, or make demo-stop DEMO_DIR=/absolute/demo/path.
Stopping stops the HTTPS proxy, server and agent, and retains the database,
certificate, logs, encryption key and agent identity for
inspection. It does not stop other demos or your development server. Startup
failure stops the resources started by that invocation and retains logs.
build.log, bootstrap.log, server.log, agent.log, and e2e.log are in
the private data directory. The retained Demo agent execution project has
a successful remote deployment and can be run again. All E2E schedules stay
disabled; the table below explains how to enable one for a manual check.
Run make e2e-test against your running server. Use the printed project links.
Projects, lifecycles, and templates use stable names with no run ID. Each run
replaces the previous E2E fixtures and their test history. Projects carry a
Managed by make e2e-test. description; unmanaged name collisions stop the
suite. The template names python-template and python-step are reserved
for the interpreter example. Unrelated records are preserved. All projects
reuse the shared dev, test, and prod environments. Environment CRUD
checks create temporary fixtures and delete them. Existing environments from
older runs are left in place. Log in as the
configured E2E_ADMIN_EMAIL (default e2e-admin@test.local); use the password
you configured, or the default test password e2e-admin-password-1234.
The command also ensures the test admin admin@durp.info exists.
For concurrent agent work, use a separate worktree and isolated server per
agent. make e2e-test-isolated chooses its own browser port, agent listener,
SQLite database, identity directory, and container namespace. Ordinary Go
test processes also replace inherited container namespaces before constructing
runners. Keep generated assets current in each worktree before Go tests.
Startup cleanup can only affect containers belonging to that test process.
Run DURPDEPLOY_CONTAINER_RUNTIME=podman bash scripts/e2e_parallel_test.sh
to exercise two simultaneous API/web staging and retry suites. It also checks
valid passkey registration and assertion against each allocated origin,
that an occupied explicit port fails, and that a neighboring container survives
Go test startup. It requires the installed Playwright Chromium browser.
CI runs the same regression with Docker.
The isolated suite prints its chosen URL and namespace. Set
DURPDEPLOY_E2E_PORT or DURPDEPLOY_CONTAINER_NAMESPACE only when you own
that port or namespace; explicit overrides must be unique across concurrent
runs. Browser and HTTP failure evidence uses a separate run directory by
default. Explicit evidence-directory overrides must also belong to one run.
make e2e-test intentionally changes its configured running instance and
retains examples; concurrent agents must each point it at their own instance.
Projects, environments, lifecycle stages, release snapshots, deployment logs, templates, and test users remain in the database. Steps removed during tests remain in immutable release snapshots: deploy the named release to repeat them. Temporary cookies and plaintext API tokens are removed. Stage files are temporary execution data; rerun the release to reproduce file handoff.
The variable-scope browser checks run in isolated CI fixtures.
The populated demo does not run these checks or create their variable-before-deploy-* projects.
For manual variable-scope checks, create a disposable project with zero deployments.
Test direct links and the project’s Variables menu.
For lifecycle-bound projects, select a stage environment.
Create first-scoped-release after you set the variable scopes.
Inspect the release through GET /projects/{id}/releases/{release_id}.
For API checks, create your own token at /settings/tokens. Open
/api/swagger/index.html, use Authorize, and use Try it out with IDs
from the printed projects. API paths below start with /api/v1. Web checks
use the browser forms; for invalid values excluded by a form, edit the request
in browser developer tools and retain its session cookie and CSRF token.
Use only the disposable E2E examples for edits and deletion checks.
| Scenario | Retained example and manual action | Expected result |
|---|---|---|
| Releases list | Open TestProject → Project menu → Releases. Create a new release and open its version link. Repeat on a phone. Use the project’s Deploy action to start a deployment. |
The list has Version, Created At, and Actions columns, with no environment selector or Deploy/Force controls. Creation, snapshot details, and deletion still work. Deployment choices remain on the Deploy page. |
| Mobile form focus | On a phone, open New/Edit Project, Environment, Lifecycle, Template, project steps, runbook steps, and the fullscreen script editor. Open Variables and add a variable. Tap a field, then dismiss and reopen the form. Repeat with a desktop mouse and keyboard. | Modals open with a header control focused, and variable forms do not select a field on load or refresh. The phone keyboard stays closed until a field is tapped. Desktop forms retain initial field focus, Tab navigation works, and dismissal restores focus to the opener. |
| Project menu and deployment history | Open TestProject, open Project menu, and select Deployment history. Also dismiss the menu with Close, Escape, and an outside click. Repeat as a project viewer and on a phone, resize with the drawer open, then enable reduced motion. Open Runbooks and use Back. |
The drawer and its backdrop stay below the top bar and fill the remaining screen height. It slides from the right and back out on dismissal; reduced motion disables travel. Dismissal restores focus to Project menu. Selection opens deployments filtered to TestProject. Viewers have no Deploy or Schedules control. Runbooks has Back as its last header action and returns to the previous page. |
| Delete agent (#143) | In Admin > Agents, pair a disposable idle agent. Cancel Delete once on its detail page, then confirm. Repeat from the list with a previously revoked disposable agent. Recreate a disposable agent to retain a repeatable example. For the API, call DELETE /api/v1/admin/agents/{id} with an admin token. Drain and finish any work and log delivery first. |
Cancellation leaves it visible; confirmation returns to the list and removes it. Detail GET and repeated DELETE return 404; successful API delete is 204 and audited as delete_agent. Historical deployments remain. Unresolved execution/cleanup or buffered logs block deletion (API 409, web error toast) and leave the agent unchanged. Viewer/deployer access is forbidden. |
| Rejoin deleted agent (#143) | Stop the disposable deleted agent after reconciling work. Move only its private state.json aside, keep its identity keys, and restart. Try an incorrect code, then pair from Admin > Agents with the current code and unchanged fingerprint; repeat through POST /api/v1/admin/agents/pair. |
The listener rejects incorrect codes. Approved pairing creates a new registration and agent ID and allows polling again. The old ID remains absent; historical deployments remain with agent references cleared. |
| Pairing recovery during database contention | Run `bash scripts/go_test.sh -race -run ’TestPrepareAgentPairingRecovery | TestAgentPairingRecoveryContentionAPIWebE2E’ ./internal/repository ./internal/agentserver`. Repeat the disposable agent rejoin check above. |
| Login, sessions, and CSRF (F0) | Sign out and request / without following redirects; sign in with the E2E admin. Submit POST /projects with the session cookie but omit the CSRF token (edit the request in developer tools). |
Anonymous GET redirects to login (303); login redirects to the app; protected pages load; missing CSRF rejected (403). |
| Happy path, steps page, scoped variables (F3.1) | TestProject, release 1.0.0, environment dev. Deploy it and open Steps and live logs. |
Success; logs contain default-variable=hello. |
| Variable scopes before deployment (#159) | Create a project with zero deployments. Add a global variable named DEFAULT. Create another variable with an environment scope. Edit that variable’s environment scope. Select Override on DEFAULT to create a scoped override. Repeat on desktop and phone. |
Eligible scopes are selectable in all three forms. The API accepts eligible scopes before deployment. For lifecycle-bound projects, the API rejects environments outside the lifecycle. The first release retains scoped values. Later variable edits do not change the release snapshot. |
| Deployment detail without step definitions (#154) | Open a retained deployment on desktop and phone; inspect its step logs, status, actions, and verification results. Reload after completion, then open its release link and project Steps page. GET /deployments/{id}, /deployments/{id}/logs, and /projects/{id}/releases/{relId} through the API. |
Deployment detail has no bottom Steps table, script cards, or step-definition empty message. Step log panels and controls remain usable; the release page and API steps_json retain the scripts. |
| Live deployment log timestamps (#163) | Deploy a retained slow release, such as TestProject 1.0.1, and keep its deployment page open while new logs arrive. Record a new entry’s timestamp, refresh while running, and compare the same entry. Repeat on desktop and phone. Read /api/v1/deployments/{id}/logs/stream?format=structured and compare the entry with /api/v1/deployments/{id}/logs/{logId}. |
Streamed entries immediately show [YYYY-MM-DD HH:mm:ss] in the same gray styling and server timezone as historical entries. Refresh/reconnect preserves timestamps and does not duplicate entries. SSE created_at matches the stored log; display_timestamp matches the page. Highlighting, step/agent labels, and keyboard disclosure controls remain usable. Cancel the slow example if needed and wait for confirmed cleanup. |
| Deployment notes (F3.1b) | Inspect the original and smoke-test-audit deployments; submit another 1.0.0 deployment with a note. |
Only the deployment given a note shows it. |
| Dashboard deployment navigation | Open /. Click a status or date in any deployment entry, then return and use Tab and Enter. Repeat on a phone and resize the page. |
Phone entries are separate rounded cards with all fields visible. Desktop entries use the existing table. The whole card or row opens its deployment; hover and keyboard focus highlight it. Text stays plain, with no added buttons. |
| Cancel (F3.2) | Deploy TestProject release 1.0.1; click Cancel while running. |
Cancelled; the retained snapshot still contains LongStep. |
| Timeout and rerun (F3.2b/c) | Deploy release 1.0.2, then use Re-run on the failed deployment. |
Both fail from the one-second timeout within 25 seconds; rerun note identifies the original deployment. |
| Retry (F3.2d) | Deploy release 1.0.4. |
Fails after retries; logs show attempt 1 and retrying. |
| Empty project validation (F3.3) | Submit an empty name on New Project (edit the request if browser validation blocks it). | Web response 422; no project created. |
| Missing variable (F3.4) | Deploy release 2.0.0 in TestProject. |
Success; logs contain missing-variable-empty. |
| Lifecycle order (F3.5) | Open the printed Lifecycle promotion project link (LC-Project, lifecycle LC). Inspect dev → test → prod, create a fresh release, deploy to dev, then try prod before test. Then deploy test and prod. |
Early prod rejected (422); dev → test → prod succeeds. The suite verifies the retained lifecycle assignment, stage order, and successful 1.0.0 deployment in each stage through the API. Use a fresh release to repeat promotion. |
| Lifecycle approval (F3.5b) | Open the printed pending approval in AppProject; approve it. Deploy 1.0.0 to its Prod environment again to repeat. |
Waits in pending_approval; approval starts execution and succeeds; recorded approver is the signed-in admin. |
| Force deployment (F3.6) | In LC-Project, create a fresh release and select Force when deploying directly to Prod. |
Deployment accepted without prior stages. |
| Environment restrictions and deploy pages (F3.7–9) | Inspect TestProject and LC-Project deploy forms. Remove prod from the disposable LC lifecycle, then submit an LC release to prod with and without force. Restore the prod stage after the check. | Free project lists dev/test/prod; LC form excludes prod while its stage is absent; outside environment rejected (422) even with force. |
| Cross-project release (F3.10) | Submit an LC-Project release ID to DP-cross-proj’s deploy endpoint. |
Rejected (400); no deployment created. |
| Scheduler (F3.11) | Open TestProject’s Schedules. Enable its retained * * * * * schedule, wait for a minute tick, then disable it. |
A successful 1.0.0 deployment appears with Scheduled: in its note. The harness disables this schedule after checking it, before later queue and browser checks. |
| User management and audit (F3.12) | /admin/users: the printed e2e-newdeployer-…@test.local is recreated after the deletion test. Its test password is newdeployer-pass-1234. Sign in separately; as admin promote/demote it, then delete it and recreate a disposable user for another pass. Try deleting your own admin account. Inspect /admin/audit. |
Deployer cannot access admin pages; promotion allows access; role change invalidates old sessions; deletion removes the user; self-delete rejected (422); audit records create/update/delete actions. |
| Modal keyboard scrolling | On a phone, open a user, resource, or runbook step modal. Open the keyboard and scroll to the bottom fields. Repeat in the token project picker and fullscreen script editor. Check notification details, confirmation dialogs, and the project menu. Close the keyboard. | Content scrolls within the visible screen. Fixed controls remain visible. Closing the keyboard restores the modal height. |
| User form validation | Open New user from /admin/users. Submit empty fields. Enter an invalid email and submit again. Repeat with an empty Name in Edit. Correct each input. |
A red warning remains beside the fixed controls. Red messages identify invalid fields. Correction clears the messages. Rejected submissions do not save. |
| User form modal scrolling | Open New user from /admin/users. Set the viewport height to 520 pixels. Scroll to the bottom fields. Submit mismatched passwords. Correct the input and create the account. Repeat with Edit. |
The fields scroll inside the modal. Create or Save and Cancel remain visible. Errors stay in the modal. The modal fits the viewport. |
| Modal error feedback | Submit a duplicate project, environment, lifecycle, or template name. Submit an empty step name. Submit mismatched user passwords. Repeat with Edit. Submit an invalid member role in Edit Project. In another admin session, downgrade the editor before submitting an open form. | Each rejected request shows one error inside its modal. No error toast appears behind the modal. Focus moves to the error. Submitted drafts remain available. Corrected submissions still save. |
| Audit identities (A08) | Open /admin/audit as an admin at 390 and 1280 pixels. Filter by an exact current name or uppercase email. Combine the filter with User ID. Follow an existing user or project target. Inspect a deleted target. Request /api/v1/admin/audit?user=EMAIL with an administrator token. |
Actor names, emails, and IDs remain visible. Target links open existing records. Deleted targets retain IDs without links. Filters preserve values. The API includes actor_name, actor_email, and target_url. Non-admin access remains forbidden. |
| Ambiguous audit targets (A08) | Create a release in a disposable project. Inspect its create_release entry in /admin/audit and /api/v1/admin/audit?action=create_release. |
The entry retains its recorded ID without a target link. Older creation events can contain enclosing project IDs. Release refresh, deployment creation, runbook creation, and runbook execution also suppress ambiguous links. |
| Users on phones (A04) | Open /admin/users as an admin. Set the viewport to 390 pixels. Use a disposable account with a long email and name. Open Reset MFA. Check the target email. Select Cancel. Open Edit. Close it with Cancel, Escape, and the backdrop. Submit mismatched passwords. Correct the input and save. |
Full identities wrap. Roles and actions do not overlap. Each action stays with its account. Edit opens the correct account in a modal. Dismissal returns focus to Edit. Validation keeps the modal open and focuses the error. Save returns to the updated list. Self-reset and self-delete are absent. |
| New user role (A05) | Open /admin/users/new as an admin. Create a disposable user without changing Role. Check the account through /api/v1/admin/users. Repeat with Admin selected. Submit mismatched passwords before correcting them. |
The initial role is Viewer. The API confirms the saved role. Admin requires an explicit selection. Validation preserves that selection. Role explanations remain visible. Existing edit forms retain their roles. |
| User form accessibility (A06) | Open /admin/users/new as an admin at 390 and 1280 pixels. Click Email, then press Tab. Inspect control names in the accessibility tree. Submit mismatched passwords. Correct the passwords and create the account. Repeat validation from its Edit page. Check the saved account through /api/v1/admin/users. |
Every control has an associated label and accessible name. Email receives focus when its label is clicked. Tab moves to Name. The error summary has the alert role and receives focus. Rejected forms retain non-secret values and clear passwords. Corrected submissions save successfully. Rejected edits do not change the account. |
| Permission guidance (A07) | Open a project as a global Deployer with project Admin membership. Inspect its overview, Steps, Releases, project menu, and account menu. Repeat as a global Maintainer with project Viewer membership. Follow Project member settings. Compare /api/v1/users/me and /api/v1/projects/{id}/members. Try a rejected configuration write. |
The effective role follows the lower role. Missing actions have explanations. Global limits point to Users. Project limits point to member settings. Restricted members can read the roster but cannot change roles. API roles agree with the overview. Rejected writes do not persist. |
| Read-only menu explanation (A07 follow-up) | Open the project menu as a Viewer or Deployer. Hover over the settings explanation. Use Tab to move through links. Select Members. Repeat at 390 px and 1280 px. Repeat as an Admin. | The explanation has no hover highlight or button cursor. It is outside the tab order. Clicking it keeps the menu open. Members opens the member settings. An Admin sees no read-only explanation. |
| Token, health, project/environment/step/release API (A1–6) | Mint a token. GET /healthz with and without it. Inspect e2e-api-project, environment dev, long-step, and release v1 through list/detail endpoints. Create disposable counterparts through POST. |
Health 200; create returns IDs; lists and details show matching names. |
| File handoff and retry | Deploy stage-handoff release 1 through the web and API. Inspect its producer/consumer scripts and logs. |
Both succeed; logs show retry and stage-handoff-content-and-size-ok; each deployment starts with fresh staging data. |
| Reserved staging variable validation | In stage-handoff, create or rename a variable to DURPDEPLOY_STAGE_DIR; try selecting it in a new step’s variable list through web and API. |
Variable writes rejected (422); step rejected (web 422, API 400). Existing LIMITED variable is retained for rename checks. |
| Interpreter forms, containers, variables | e2e-interpreters: inspect new/edit step forms, current PowerShell step, releases mixed-v1 and powershell-immutable, and deployment pages. Deploy both releases. |
Choices Bash/Python/PowerShell; current interpreter selected; mixed logs show bash-e2e and python-e2e=container-value; PowerShell logs show powershell-e2e=container-value without prompts or escape sequences. |
| Interpreter validation and release refresh | POST a step with interpreter: "/bin/sh" (400). Edit the Python step to PowerShell, then refresh the used mixed-v1 release through the API (200) or its web Refresh button (303). Inspect the release and original deployment, then re-run the original mixed deployment. |
Invalid interpreter rejected; refreshed release shows PowerShell; original deployment and its rerun still execute Python. API alias powershell normalizes to pwsh. Refresh is available after failed or successful deployments. Active or unconfirmed runs and buffered agent logs return 409 until they finish. |
| Template forms, history, save/insert | Open python-template in Templates and its History; inspect Python and PowerShell versions. In interpreter project use Save Template and insert an existing template. |
Form selections/history preserve interpreters; save/insert preserves pwsh; API template GET/PUT/history agree with the web. |
| Agent container placement (#99) | In a disposable project, add a step with Run step on = Agent, Agent execution mode = Container, image harbor.durp.info/dockerhub/library/alpine:3.20, and variable limit DEPLOY_ENV. Save a template and a release. Change the mutable step to Host, then inspect the template history and the original release through the API. Repeat in a runbook with one container and one host step. |
Host is the default; container mode requires an image. Modes, images, and limits survive create/edit and immutable snapshots. Agent-host steps reject images; server-container steps reject agent container mode. |
| Agent/3 execution and cleanup (#99) | Assign a ready agent/3 agent to an environment and deploy the retained container release. Inspect /admin/agents/{agent-id} and deployment logs. If the agent reports cleanup_unconfirmed, restore its same local engine and let it reconcile before polling again. |
Health shows modes, runtimes, and container interpreters. Bash, Python, and PowerShell use their selected images and limits. Unconfirmed cleanup blocks queued work and rerun; readiness confirms cleanup separately and preserves the original result. Agent/1 and agent/2 cannot claim container steps or confirm cleanup. |
| API cancel/status (A7) | POST a deployment of e2e-api-project release v1, GET its status, POST /deployments/{id}/cancel while running. |
Running → cancelled; cancel response 200. |
| API log streaming (A8) | Deploy e2e-api-project v2; GET /deployments/{id}/logs/stream?format=ndjson. |
Each replayed/live line is a JSON object with a line field. |
| Project/environment queue isolation | Deploy e2e-api-project queue-check to dev, then deploy v2 to dev. While the first runs, deploy TestProject 1.0.0 to dev and e2e-api-project queue-check to test. Cancel both slow deployments after inspection. |
Only the second dev job in e2e-api-project queues. The other project and test job run independently. Cancelling the dev head admits its next queued job. Release snapshots and printed deployment links remain available. |
| API failure paths (A9) | POST /projects without a token. GET a nonexistent project ID. Sign in as the printed e2e-viewer-…@test.local with password e2e-viewer-pass-1234. To repeat the API viewer check, promote that user to Maintainer. Mint its token. Demote the user to Viewer before POST /projects. |
No token returns 401. A missing project returns 404. A Viewer write returns 403. Admin changes invalidate sessions. Sign in again for web checks. |
| Swagger (A10) | Open /api/swagger/, /api/swagger/index.html, and /api/swagger/spec without authentication. Inspect verification/rollback security and environment error responses in the spec. |
UI/spec 200; Swagger 2.0; verification and rollback require bearer auth; environment POST/PUT document 403 and 422. |
| Terraform artifact approval | terraform-approval: inspect printed approved, rejected, and pending deployments. Open the pending review, download the plan, then approve or reject. Deploy approval-demo again to repeat either decision. |
Real Terraform summary: one added, one modified, one removed resource, zero reads, unverified review label. Approval applies the saved plan, verifies the resulting state and no remaining changes, then succeeds; rejection prevents the saved-plan apply and ends rejected. Downloaded artifact SHA-256 matches the gate. |
Terraform uses only the built-in terraform_data resource; it creates no
cloud resources. Each deployment first seeds two disposable resources in its
local state. On its deployment page open View resource changes and
check approval_demo (added), modified_demo (modified), and removed_demo
(removed), their Before/After values, public messages,
masked token, and (known after apply) values. The same data is available at
GET /api/v1/deployments/{id}/artifact-gates/0/review. Viewers must receive
403 for this endpoint and must not see the disclosure. No displayed value
should contain the demo’s TF_VAR_demo_secret project secret.
The release page’s Steps table must show normal script previews. Deployment
details show step logs and verification without a Steps definitions section.
Logs must include Terraform’s
plan output, with green additions, red removals, and yellow modifications,
both live and after a reload. The resource review uses matching Add/Remove/Modify
badges and red/green Before/After borders. Sensitive values remain masked by
Terraform and DurpDeploy’s project-secret scrubber, including old values of removed resources.
Pending artifact approvals expire after 24 hours. Their release stays
available for a fresh plan. Use the same container runtime and image store as
the server when running the harness. No retained schedule should run unattended
after manual checks.
Decision buttons appear automatically once artifact publication and container
cleanup finish; the review panel polls every three seconds. During publication,
expect a preparing message and no Approve/Reject controls. The public gate
metadata reports decision_ready: false until decisions are available.
To check stale-form recovery, open the same pending deployment in two tabs,
approve or reject in one, then submit the older form in the other. The older
submission returns to deployment details with a warning and refreshed gate
state, without recording a second successful decision in the audit log.
The pending Terraform example owns its project’s dev execution slot. New dev
deployments and runbooks for that project queue until you approve, reject, or
cancel it. Other projects in dev and that project in test/prod can run at the
same time. The suite
creates this example after its execution checks. Approval waits in earlier
projects no longer block the new run’s projects; release snapshots,
deployment history, and runbook versions remain available. Each run leaves a
fresh pending example for manual approval checks.
SMTP delivery warnings
Configure the server to use a disposable SMTP relay without STARTTLS.
Clear DURPDEPLOY_SMTP_USER for this unauthenticated relay.
Set the project’s email recipients in Settings → Notifications.
Run a deployment that publishes a notification.
Open /admin/notifications as an administrator.
Expect email: ok: sent without TLS with a warning badge.
Open the event details and confirm the same warning.
Request GET /api/v1/admin/notifications with an administrator API token.
Expect the email result to contain ok: sent without TLS.
If the relay uses verified STARTTLS, expect ok instead.
If TLS certificate verification fails, expect failed: delivery failed without a plaintext retry.
Shared lifecycle variables
On a release with a deployment awaiting approval, click Refresh and accept
the confirmation. The release page must remain open and show Unable to
refresh this release, with the snapshot lock reason, cancellation/completion,
cleanup and log sync guidance, and a View project deployments link.
The API refresh must return 409 while that deployment is active. Cancel a
disposable deployment or let it complete, then retry: the API returns 200
and the web refresh returns to the release page with the error cleared.
The retained demo has a disposable pending-approval case at
/projects/4/releases/9, with /deployments/25 awaiting approval; check its
current status before retrying. Keep its release/history after testing.
make e2e-test retains a shared-lifecycle-<run> lifecycle and two
shared-project-<run> projects. The suite prints their editor, variables,
release and deployment links. On /lifecycles, tap the lifecycle card to open
its full page, then find Shared variables below Promotion order. Existing
lifecycles open the page instead of a settings modal on every screen size.
Open the lifecycle’s Shared variables section
to add an unscoped value or an override for one of its stages. Secret values
show a mask; leaving an existing secret blank keeps its saved value.
Open the first project’s Variables page: REGION shows Project override
with its lifecycle default, while the second project inherits the shared value.
Choose Reset to inherited in the first project to remove only its override.
Reset removes all local rows with that name and scope, including legacy
unscoped duplicates; other scopes and projects keep their values. The public
API supports the same action with DELETE /api/v1/projects/{id}/variables/{varId}?reset=inherit.
After removing a lifecycle stage, edit one of its retained variables: Save
must wait for an explicit All stages or current-stage selection. A blank
secret field still preserves the saved secret after choosing the scope.
Choose Override, enter a project value and save to recreate the example.
Global administrators manage shared values.
Project Maintainers can override their own values.
Viewers and Deployers see no write controls.
Unassigned projects inherit nothing.
The refreshed demo is https://citadel.durp.loc:45465, with data in
tmp/demo.zU6HfZ and its temporary certificate at tls/cert.pem.
Credentials are in that directory’s login.txt. Stop only this demo with
make demo-stop DEMO_DIR=/absolute/path/to/tmp/demo.zU6HfZ.
Previous tmp/demo.ugv3mx and tmp/demo.pX7hhv histories are retained on disk.
The hands-on scope-change example is lifecycle
Live scope manual (example route: /lifecycles/6), project
Live scope manual (example route: /projects/24), and release
before-scope-edit (example route: /projects/24/releases/36).
Deployment 42 (example route: /deployments/42) prints
LIVE=all-stages before the shared edit. The same release then prints
LIVE=unset in prod in deployment
43 (example route: /deployments/43), and LIVE=dev-only in dev in
deployment 44 (example route: /deployments/44).
The TOKEN output is redacted. Repeat by editing LIVE on the lifecycle page and
deploying release 36 to Live scope prod or Live scope dev, without refreshing it.
These examples create no schedules; all retained schedules remain disabled.
Both retained shared-v1 releases contain only project-owned variables. Their
successful deployments use shared-updated, which the suite set after creating
the releases. Change a shared variable from All stages to dev, then deploy the
same release to another stage: the variable must be absent there. Deploy to dev:
its current value must appear. No release refresh is needed. Re-runs, rollbacks
and saved runbook versions also resolve current lifecycle variables when creating
a new execution. Queued and active executions retain their captured values.
Inspect the logs: the shared secret is redacted. To check
environment precedence, add a lifecycle REGION override for the stage; it wins
over an unscoped project override, while a project override for that same stage
wins over the lifecycle value. These examples have no enabled schedules.
Lifecycle assignment grants execution access to shared secrets.
Only global administrators can attach a lifecycle to a project.
Project administrators with global Maintainer permission can retain or remove an existing assignment.
As a Maintainer, create or update a project with a new lifecycle ID.
Confirm a 403 response without a project change.
Step variable viewer (#142)
Run make e2e-test against the selected development instance.
The suite retains a project named variable-viewer-<run> and prints its Steps URL.
Use its project, lifecycle, environment, and saved step for these checks.
- Open the retained project’s Steps page.
- Select View Variables.
- Confirm separate configured and predefined groups.
- Select the retained environment.
- Confirm
REGIONresolves to the environment-specific lifecycle value. - Confirm its unscoped project value is overridden.
- Search for
SECRET. - Confirm the secret flag appears without a value.
- Copy the name and reference into a scratch editor.
- Confirm the clipboard contains a name or reference without the secret value.
Repeat these editor checks in Add Step, Edit, and fullscreen script mode:
- Change the name, script, interpreter, timeout, retries, and variable selection without saving.
- Select part of the script and scroll the editor.
- Open View Variables.
- Confirm references match Bash, PowerShell, or Python, according to the draft interpreter.
- Confirm selected variables are available and other configured variables are excluded.
- Close the viewer with Close, then repeat with Escape.
- Confirm every draft field, script selection, scroll position, and parent dialog remains intact.
- Confirm focus returns to the View Variables button.
- Cancel the step form and confirm the saved step is unchanged.
Check a phone with its software keyboard open:
- Focus Search variable names.
- Scroll through results while the keyboard remains open.
- Confirm the search, environment control, copy buttons, and Close button remain reachable.
- Confirm there is no horizontal overflow.
- Use Tab and Shift+Tab to verify focus stays in the viewer.
- Use a screen reader to verify dialog labels, result counts, and copy announcements.
Confirm package and staging variables remain conditional without execution context.
Select the agent target and confirm approved-artifact injection is unsupported.
Inspect GET /api/v1/projects/{id}/variables/available with and without environment_id.
Repeat with a viewer token scoped to this project.
Confirm another project’s endpoint is rejected.
Confirm an unrelated project’s step ID returns 404.
Automated API and browser checks use TestStepVariablesAPIWebE2E and TestStepVariablesBrowserE2E.
The browser regression includes desktop, phone, reduced-height, fullscreen, copy, draft-preservation, and retry cases.
Reduced-height emulation does not replace the physical keyboard and screen-reader checks above.
Deployment notes in history
- Create a deployment with an incident note, such as
incident-failure. - Wait for the deployment to finish.
- Select Re-run.
- Confirm the request.
- Wait for the re-run to finish.
- Open Deployments or the project’s Deployment history link.
- Confirm the original card shows at most two lines of its incident note.
- Confirm the re-run card shows
Re-run of #with the original deployment ID. - Use Load more when the original record is on another page.
- Confirm appended cards show their saved notes.
- Repeat at phone and desktop widths with a long note containing line breaks and markup.
- Confirm note previews use at most two lines and display markup as plain text.
- Confirm deployments without notes have no empty Note field.
- Open a card with a shortened note.
- Confirm the deployment details show the complete note with its original line breaks.
- Repeat with a Viewer assigned to the project.
Public deployment list endpoints include the same saved note values.
History does not change notes, deployment statuses, or project access.
Keep the incident and re-run records for manual checks.
These checks create no schedules.
Project groups
Run make e2e-test against the running server.
Use the printed grouped-project and Ungrouped-project links.
The suite retains projects, groups, memberships, and a disposable group for repeated deletion checks.
- As a global administrator, open
/project-groups. - Select New Group and create a group with a unique name.
- Open Group actions and select Rename group.
- Select projects with their checkboxes and select Move to group.
- Select a destination and confirm the move.
- Use Select all to select editable projects on the current page.
- Clear one checkbox and confirm Select all shows a mixed state.
- Clear the selection and confirm the action toolbar disappears.
- Change the group, search, or page and confirm the selection resets.
- Use Group actions to delete the disposable group.
- Confirm its projects remain accessible under Ungrouped.
- If a move fails, read the completed count before you reload projects.
Moves run one project at a time. Successful moves remain saved if a later move fails. Each move checks current permissions and records its own audit event.
Repeat the browsing checks:
Group panels use faint tints and soft borders. Project cards use two columns on wider screens and one column on phones. Group counts show only projects on the current page.
- Open
/projects. - Select the group heading with Tab.
- Press Enter to collapse or expand the group.
- Confirm assistive technology reports the expanded state.
- Repeat browsing at a viewport width of 375 pixels.
- Select Filters to expand the mobile filter panel.
- Search for part of a project name or description.
- Select a group, Name A–Z, and Per page of 1.
- Select Filter.
- Follow Next and Previous.
- Confirm the filters remain selected and the mobile Filters button shows Active.
- Select Clear and confirm all filters and pagination reset.
- Open Manage Groups and repeat the filter checks inside the modal.
- At desktop widths, confirm the filter controls stay visible.
Projects and Manage Groups default to Name A–Z. Clear restores this default. Manage Groups keeps the Projects URL and filters behind the modal. The modal keeps its size when you select another group. The project list scrolls below the top Cancel and New Group controls. The Projects page uses the Catppuccin Mocha text and accent colors in the Mocha theme. Use New Group to create a group. Use Cancel or Escape to close the modal. Changes save when you apply them. Cancel does not undo changes. After a change, confirm the Projects list refreshes when you close the modal.
As a Viewer, browse the printed accessible project. Expect no group-management or project-move controls. Expect group counts to exclude inaccessible projects. As a Maintainer, open Manage Groups to move authorized projects between visible groups. Expect project permissions and deployment links to remain unchanged.
Use GET /api/v1/project-groups to inspect authorized groups and counts.
Use PUT /api/v1/projects/{id}/group with {"group_id":null} to clear an assignment.
Use GET /api/v1/projects?group_id=0 to inspect Ungrouped projects.
Use the public Swagger UI for group CRUD and project search, sorting, filtering, and pagination.
Expect invalid names to return 422 and duplicate names to return 409.
Expect inaccessible assignment destinations to return 404.
Inspect /admin/audit for successful group and assignment changes.
Empty deployment filters
- Open Deployments for an account with deployment history.
- Select a project and a status without matching deployments.
- Select Filter.
- Confirm
No deployments match these filtersappears beside a visible Clear link. - On a phone, keep the filter panel closed and select the nearby Clear link.
- Confirm the deployment history returns and the filter fields reset.
- Use Tab and Enter to activate Clear with the keyboard.
- Repeat with an unmatched environment and date range.
- Use an account without accessible deployment history.
- Open Deployments without filters.
- Confirm
No deployments yet. Deploy a release to see results here.appears.
The public GET /api/v1/deployments endpoint returns empty items and zero total for unmatched filters.
Clear removes the filters and preserves deployment records.
These checks create no schedules.
Lifecycle stage ordering
Create a lifecycle and at least three disposable environments.
Call POST /api/v1/lifecycles/{id}/stages with an environment_id and sort_order: 10.
Expect 201 with position 10.
Add another environment with position 5.
Expect 201 with position 5, without renumbering position 10.
Add another environment without sort_order.
Expect position 11.
Zero and negative positions also append after the highest position.
Add a different environment at an occupied position.
Expect 409 with sort_order in the response and no stage change.
Add an environment that already belongs to the lifecycle.
Expect 409 with environment_id in the response and no stage change.
Conflicting stage updates also return 409.
Open the lifecycle page and add an available environment through its form.
Expect an append after the highest position.
If another client adds the selected environment first, submit the stale form.
Expect the existing 400 validation response and no duplicate stage.
Reload the page to see the committed stage.
Call POST /api/v1/lifecycles/{id}/stages/reorder with every stage ID in stage_ids.
Expect consecutive positions from one in the requested sequence.
Keep all schedules disabled during these checks.
Run the isolated backend and browser checks:
bash scripts/go_test.sh -tags=e2e -race -count=1 -run '^TestLifecycleStageOrderAPIWebE2E$' ./internal/handler
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestLifecycleStageOrderBrowserE2E$' ./internal/handler/api
The backend checks run concurrent API and web appends on SQLite, PostgreSQL, and SQL Server.
Stale-form permission navigation (V02)
Use a disposable project with a release, environment, and Maintainer test account. Keep the deployment history empty before this check. Use the same account in two browser tabs.
- In the first tab, open the project Edit dialog.
- Enter an unsaved description.
- In the second tab, open the project and select Deploy.
- Select a release and environment.
- With a separate administrator session, change the account’s project role to Viewer.
- Submit both stale forms.
- Expect error messages, a retained description draft, and no deployment.
- Reload the deployment page.
- Select Go back and record the URL and page.
- If the denial repeats, select Go back again.
- Restore the project role to Maintainer.
- Repeat with the global role changed to Viewer.
- Restore both roles to Maintainer.
- Save the retained draft and confirm the saved description through the API.
The verified sequence adds two deployment-form history entries when release selection reloads the page.
The first Go back revisits /projects/{id}/deploy and repeats the denial.
The second Go back reaches /projects/{id}.
A fresh denial with one deployment-form history entry reaches the project after one Go back.
These checks did not reproduce a persistent navigation loop.
Call POST /api/v1/projects/{id}/deployments with the Viewer token and valid release and environment IDs.
Expect 403.
Call GET /api/v1/projects/{id}/deployments with an administrator token.
Expect total: 0.
Keep the project, release, environment, account, and deployment history available for repeat checks.
Run the isolated browser and API regression:
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestStaleFormBackBrowserE2E$' ./internal/handler/api
Browser Back after a confirmed re-run (V03)
Use a disposable project with one completed deployment.
Record its ID and the project deployment count through GET /api/v1/projects/{id}/deployments.
- Open the completed deployment in the browser.
- Select Re-run and dismiss the confirmation.
- Confirm that the API deployment count has not changed.
- Select Re-run again and accept once.
- Wait for the new deployment to complete.
- Record its ID and confirm that the count increased by one.
- Confirm that its note identifies the original deployment.
- Use browser Back and confirm that the original deployment opens.
- Confirm that the API count has not changed.
- Use browser Forward and confirm that the re-run opens.
- Reload the re-run detail.
- Confirm that the API count has not changed after either action.
- Confirm that the original deployment logs have not changed.
The verified sequence retains exactly the original deployment and one confirmed re-run. Browser history traversal and detail reload do not create another execution. Keep both deployments, their logs, the project, release, and environment available for repeat checks.
Run the isolated browser and API checks:
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 -run '^TestRerunInputs(APIWeb|Browser)E2E$' ./internal/handler/api
Device, certificate, and passkey checks (V04)
Use an isolated HTTPS instance and disposable accounts for these checks. Use a browser profile that contains no personal credentials. Keep certificate exceptions disabled. Trust a test CA only in that isolated profile. Do not change the system trust store.
- Open the HTTPS login page before trusting its CA.
- Expect an untrusted-certificate error.
- Trust the CA in the isolated profile and reload the matching hostname.
- Expect the login page without a certificate warning.
- Call
GET /api/v1/healthzfrom that page and expect200with{"ok":true}. - Open the same server through a hostname absent from the certificate.
- Expect a TLS failure and record its exact error.
- Sign in with a disposable account and open Security.
- Reauthenticate and select Add passkey.
- Complete registration and select Verify passkey.
- Confirm that ten recovery codes appear without recording their values.
- Select Continue and sign in again with the password.
- Confirm that the MFA page appears without a final
sessioncookie. - Select Use a passkey and complete its assertion.
- Confirm that the dashboard opens.
- Confirm that the final session cookie has
Secure,HttpOnly, andSameSite=Lax.
Record each device and security result separately.
The isolated Chromium check passed with a trusted test CA and a CTAP2 virtual authenticator.
The untrusted CA failed with ERR_CERT_AUTHORITY_INVALID.
The wrong-host request failed with ERR_SSL_PROTOCOL_ERROR because the proxy rejected TLS first.
That result does not independently prove browser hostname validation.
These checks do not establish production certificate trust or physical authenticator behavior.
Physical-phone keyboard behavior remains pending because device access is disabled in this environment. Orca is installed, but no controlled browser session was available to measure spoken announcements. Screen-reader announcements remain pending.
When suitable tools are available, repeat these checks on a physical phone and with a screen reader. Check keyboard overlap, focused-field visibility, form submission, validation announcements, toast announcements, and focus after navigation. Record the device, operating system, browser, screen reader, action, expected result, and observed result. Keep each unmeasured result pending.
Database TLS policy
Use a disposable database when changing TLS settings for this check. Keep the matching application encryption key. See connection settings for PostgreSQL and SQL Server options.
- Configure verified TLS with the trusted CA and matching database hostname.
- Start the application.
- Call
GET /api/v1/admin/database-securitywith an administrator bearer token. - Expect
policy: tls_verifiedwithout awarningfield. - Create a disposable project through the API.
- Confirm that the project appears in the project list.
- Stop the disposable application.
- Configure an incorrect CA or hostname.
- Start the application and expect certificate validation to fail.
- Restore the correct settings before continuing.
To check the supported exceptions, repeat startup with unverified TLS or plaintext settings.
Expect a database transport security warning in the startup log.
Expect tls_unverified or plaintext_allowed with a warning in the administrator API response.
The application must still permit the disposable project operation.
The warning must omit credentials, database addresses, and certificate paths.
The API reports configured policy, not the encryption of each live connection.
The public health endpoints must not expose this information.
Anonymous API access returns 401.
Viewer and deployer tokens return 403.
Run the isolated native checks with the local runtime socket:
DOCKER_HOST=unix:///run/user/1000/podman/podman.sock \
DURPDEPLOY_TLS_BACKEND=postgres make database-tls-test
DOCKER_HOST=unix:///run/user/1000/podman/podman.sock \
DURPDEPLOY_TLS_BACKEND=mssql make database-tls-test
These isolated checks remove their disposable databases and certificates. They do not modify retained demo projects or enable schedules.
Split default credential redaction
Use synthetic credentials for these checks. Do not configure the synthetic values as secret variables. Default patterns must detect the credentials independently.
Run the authenticated remote-ingress regression:
bash scripts/go_test.sh -race -count=1 \
-run '^TestSplitCredentialRedactionAPIWebE2E$' ./internal/agentserver
The test sends 0Bea and rer synthetic-proof\n in separate authenticated requests.
It also checks GitHub, AWS, and all three Slack prefixes.
The first request must not persist or broadcast the unfinished credential.
The continuation must produce 0[REDACTED], with the authenticated agent attribution.
Current and legacy claims retain ordering, replay handling, and encrypted pending buffers.
Public API logs, pagination, exports, live streams, replay streams, and web routes must contain no credential value.
Run the browser regression:
bash scripts/go_test.sh -tags=e2e,packagebrowser -race -count=1 \
-timeout=4m -run '^TestDeploymentStepLogsBrowserE2E$' ./internal/handler/api
For a retained example, create a Bash step in the thread-owned development stack.
Print each synthetic credential with a leading digit.
Split the Bearer prefix across two writes, then end each credential with a newline.
Open the deployment page while the step runs.
Expand its log panel.
Expect six 0[REDACTED] markers and no synthetic credential values.
Reload the page and repeat the check at desktop and phone widths.
Read the same deployment through the authenticated API and its SSE endpoint.
Keep the project, release, environment, and deployment for manual review.
The bounded FuzzDefaultPatternStreaming target covers this default-pattern regression.
bash scripts/go_test.sh -run '^$' -fuzz '^FuzzDefaultPatternStreaming$' \
-fuzztime=30s -parallel=1 ./internal/logscrub
Issue #48 retains its independent FuzzScrubberStreaming target and discovery corpus 4bfe6d6a4ac85117.
After this fix merges, run that corpus against the merged revision.
An earlier stacked integration requires separate approval.
The invalid-byte alias oracle associated with 3e1a1c989b26e560 is outside this regression.