# DD — Runtime endpoints (1D) [Common design](dd.md) · [Tasks and completion status](adr.md) · [Request transport](request-transport.md). The browser runs repository actions and reads and stops their jobs and deployments through the Go API. The routes keep Themelio's paths and response shapes, with an `/api/v1` prefix. The API sends each route as a typed request over RabbitMQ ([request transport](request-transport.md)); Execution Core and Workload Portfolio answer from the dedicated `metallo-requests` process. The HTTP shapes, examples and status codes are defined in the [Execution Core HTTP contract](../../../../../contracts/execution_core/http_api/). The typed requests are defined in the owner contracts for [Execution Core](../../../contracts/requests/proto/pharos/requests/v1/contexts/execution_core/requests.proto) and [Workload Portfolio](../../../contracts/requests/proto/pharos/requests/v1/contexts/workload_portfolio/requests.proto). | Route under `/api/v1` | Owner request | Use case | | --- | --- | --- | | `GET {type}/{ns}/{repo}/jobs`, `…/deployments` | `list_runs` | `RepositoryRuns.list` | | `GET jobs/namespaces/{ns}`, `deployments/namespaces/{ns}` | `list_runs` | `RepositoryRuns.list` | | `GET {type}/{ns}/{repo}/jobs/{id}`, `…/deployments/{id}` | `get_run` | `RepositoryRuns.get` | | `POST {type}/{ns}/{repo}/action/{branch}` | `launch_action` | `ActionLaunches.launch` | | `DELETE {type}/{ns}/{repo}/jobs/{id}`, `…/deployments/{id}` | `cancel_run` | `RepositoryRuns.stop` | | Repository details with its `actions` (route not added) | `list_actions` | `ActionCatalogue.list` | The repository details route waits for open question 12, which decides who owns the other repository fields. Workload Portfolio already answers its `actions` list. Pipeline routes follow in 8E. ## Addressing and access `{type}/{ns}/{repo}` is Themelio's address: `models`, `spaces` or `repos`, the Project namespace and the repository path. It is an opaque binding. Each context's `RepositoryDirectory` port resolves it to the canonical Project and repository IDs, and the namespace routes resolve `{ns}` to the Project. An absent repository and one the member cannot see are both `not_found`. Source Control supplies this lookup through the service-level repository owner adaptors. A Run keeps its repository after the repository is renamed or deleted, or while the provider cannot answer (#766). When the path no longer resolves, reading or stopping a Run falls back to the Run's own saved repository if Source Control last saw that repository at exactly this path. Source Control answers from its own records, without asking the provider. The member's current read permission still decides, and a member without it gets `not_found`, so a former address reveals nothing. A repository moved to another Project leaves its Runs in their own Project, so neither path finds them. Listing a deleted repository's Runs by its path is not supported; the namespace routes still list them. Queries carry the signed-in member in `QueryMetadata`; commands carry the member in their metadata. Reads check `runs.view` or `workloads.view`; launch and Workload preparation check `runs.launch` and `workloads.manage`; cancellation checks `runs.cancel`. Pipeline operations use their corresponding `pipelines.*` capability. ## Running an action `launch_action` names the new launch by the command's `Idempotency-Key`; the browser sends `If-Match: "0"`. Execution Core first looks up a launch with that ID and returns its saved decision on a retry. Otherwise it resolves the repository, and Workload Portfolio prepares the action's Workload revision on `main` with the environment derived from the action type ([2D](workload-creation.md#repository-actions)). Execution Core then accepts the launch for planning ([3B](launch-acceptance.md)). The launch saves `actionFingerprint`, the SHA-256 of the repository path, branch, action and parameters, as part of its material input. A retry is replayed only when it comes from the same member with the same fingerprint, also when a concurrent delivery saved the decision first. The comparison uses the request alone and never rereads `actions.yaml`, which may have changed. A key used by another member or for another request is an `idempotency_conflict`; nothing is prepared again and the first request stays saved. Workload Portfolio reports every invalid parameter at once. The rejection carries one message per parameter in `Rejection.fields`, and the API returns Themelio's 400 body with `fieldErrors`. Other refusals return 422. A launch that acceptance refuses is saved with its reason, and the committed reply reports that reason. The Run appears under jobs or deployments once planning saves it. ## Reading runs Jobs are `queued-batch` Runs; deployments are `reconciled-service` Runs. Lists are complete and newest first, filtered by Project, repository, family and any repeated `status`. Context migration `0007_run_listing.sql` indexes these fields. `action_name` is the display name Workload Portfolio saved for the action's Workload. `started_at` is the first Attempt's start. `completed_at` and `destroyed_at` take the last Attempt's update for succeeded or failed Runs and for cancelled or terminated Runs. `stop_requested` is true once a member asked to stop. `tag` is not stored yet, deployment `url` waits for public service addresses and `logs` stays empty until 7A, so they are always null or empty. Detail responses also return `configuration`, read exclusively from the authorised Run's saved snapshot. It carries the accepted Workload ID/revision, execution environment, named parameters, requested hardware, total admitted hardware, source repository IDs with saved branches and commit pins, and the wall-time limit. `resources` describes one instance; `admission_resources` is the total demand accepted across concurrent instances. Both describe requested resources, rather than measured utilisation or available Site capacity. The retained detail view exposes these fields through a read-only Configuration control. Lists and realtime projections omit the configuration. Named parameter evidence is copied from the exact Workload revision. Parameters are null when an older revision did not record them, an empty array for a recorded parameter-free action, and a null individual value when deliberately hidden. The context never reconstructs old values from today's `actions.yaml`. Commands, environment values and provider credentials are excluded. Earlier owner replies and launches still waiting for planning have a null configuration; the UI states that it is unavailable. Pending admission demand is never presented as a planned Run's requested hardware. An accepted launch is listed before planning saves its Run (#766), under the ID that Run will get, with no attempts and no `started_at`. While planning waits it is `preparing` (a deployment `reconciling`) and `status_reason` says why, as the code planning saved with its last retry: `resource_not_ready`, `limits_unconfirmed`, `provisioning_unavailable`, `access_check_unavailable`, `workload_unavailable`, `repository_unavailable` or `temporarily_unavailable`; it is null before the first retry. A launch planning refused is `rejected` with the refusal's code, such as `resource_suspended`, and one a member stopped is `cancelled` (`terminated`). A rejected Run carries its Attempt's rejection code the same way; every other item has a null `status_reason`. `status_message` explains the reason in a short sentence for people: a fixed text for each wait reason (`WAIT_MESSAGES` in `application/launch_processing.py`), or the refusal's own message. It never carries an owner's or provider's error, and a code saved before these reasons existed reads as `temporarily_unavailable`. A launch that planning turns into a Run between the two reads is listed once, as the Run. Migration `0013_launch_listing.sql` indexes the launches by Project. The retained web displays these items and their reasons in its restored authenticated layouts. ## Stopping a run `cancel_run` targets the Run in the route and uses the browser's `If-Match` version. A Run outside the repository or of the other kind is `not_found`. The cancellation records the member's request ([7C](run-cancellation.md)). A retry repeats the command identity and version but not its arrival time, so the time is not part of the fingerprint. A stale version returns 409. A launch still waiting for planning is stopped with the same route and the launch's version. The member's current `runs.cancel` permission is checked, and the durable launch fence that Pipeline cancellation uses keeps any Run from starting and releases the launch's account; the answer is `cancelled` (`terminated`). A refused or already stopped launch answers `run_not_active`. If planning saved the Run first, the answer is 409 and the member stops the Run. ## Hosting The [`metallo-requests` Deployment](../../../../../gitops/manifests/pharos/base/metallo-requests/README.md) runs both owners' request consumers and Execution Core's reply relay. It holds the two contexts' database bindings and their requests broker users, which hold no event authority; the users that publish and consume events belong to other processes. Metallo's broker provisioning declares the request queues from [`contracts/requests/owners.json`](../../../contracts/requests/owners.json), and the API's broker role covers these owners. Source Control supplies the repository directory and `actions.yaml`; each context's local Beto projection supplies its access evidence. The request host confirms allowing person decisions with Beto through its separate query-only `METALLO_REQUESTS_VIEWS_BROKER_URL` binding. Other processes maintain the projections. Missing configuration refuses startup, and the base installs the request host with zero replicas. The dev-box overlay enables it and the execution worker for real-owner Run qualification; other targets must check their rendered configuration. While the request host is unavailable, the routes answer 503. ## Remaining work - Qualify the connected owners on the dev box before wider rollout. - Add the repository details route once open question 12 is decided. - Store the action's `tag`; deliver logs (7A) and service addresses. ## Verification Completed on 22 October 2026 on `feat/metallo-requests`, with the repository directory, `actions.yaml` and Project permission owners as test doubles: - **27 new Metallo unit cases** cover run-action replay, another member's key, refused and post-commit launches, list scopes, kinds and states, Runs outside their address, stop retries without the arrival time, parameter errors with per-field messages, action descriptions and their errors, typed reply mapping, the query actor, the host's composition and drain, and the broker manifest. - **5 new Go cases** cover the Themelio response shapes, time normalisation, owner errors, run-action and stop translation, invalid input that never reaches the broker and cross-origin stops. Broker provisioning, the deployment and credential inventories have their own script cases. - `pnpm test:integration` passed with **317 Metallo cases** on real PostgreSQL and RabbitMQ. The request host answered a caller holding the API's provisioned broker role: a launch through real Workload preparation and acceptance, its retry, parameter errors, a namespace listing, a stop, a Run read and the actions list. Run listing filtered saved Runs with the `0007` indexes in place, and a refused launch committed its reason with its reply. The running API, which declares a reply queue for every owner on one connection, served `/api/v1/projects`. - `pnpm verify` passed with **2,094 Metallo tests** and strict Mypy; regenerating the contracts reproduced the outputs. Not run: the dev-box identity lane (`make metallo-identity-test TARGET=dev-NAME SSH=your-dev-box`), because its remote run was not authorised in this session. No browser or UAT case covers these routes until the UI uses them, and no Minikube/k3s installation ran; `pnpm verify` renders the overlays. ## Retained web integration The compatibility routes now forward canonical sessions and command headers to these API endpoints. Their components use UUID Run IDs, observed versions and server-authorised Run publications. Their restored layouts expose these journeys; this wiring does not change request-host or worker replica counts. The base pauses them; the dev-box overlay enables them for qualification. The [compatibility migration guide](../../../../../compat/themelio/services/web/api-spec/project-repositories.md#runs-through-the-new-api) records rollout and legacy-workload limits. Backend qualification above predates this client change; it is not evidence of an end-to-end browser Run.