# Velocious callable deployment API

`rampway/velocious` is Rampway's authenticated deployment control plane for a
Velocious application. Rampway owns every deployment-specific decision and
calls its normal deploy lifecycle; Velocious supplies only route mounting,
request/connection scopes, database persistence, background execution, and
framework error events. Each admitted run is executed by a package-owned
detached Node worker, not by the request process.

The API accepts no command, path, repository, branch, environment value, log
request, or arbitrary Git ref. A caller can select only a configured
project/stage pair, an immutable lowercase 40-character commit SHA reachable
from that target's approved release branch, and an idempotency key.
The idempotency key must be a non-secret client-generated value of at most 255
characters and must not contain any configured bearer token; matching is
case-sensitive, and such requests are rejected with `422` before run lookup or
persistence.

## Install and mount

Install Velocious in the application that mounts this optional integration.
Rampway declares it as an optional peer so applications that only use the CLI
do not acquire a web-framework dependency.

```sh
npm install rampway velocious
```

In the application's Velocious routes file:

```js
import Routes from "velocious/build/src/routes/index.js"
import RampwayDeploymentApi from "rampway/velocious"
import deploymentSecrets from "../config/secrets/deployments.js"

const routes = new Routes()

routes.draw((route) => {
  route.mount(RampwayDeploymentApi, {
    at: "/rampway/deployments",
    accessTokens: deploymentSecrets.rampwayAccessTokens,
    workerBootstrapPath: "/srv/my-app/control/rampway-velocious-worker.mjs",
    projects: {
      "my-app": {
        stages: {
          production: {
            configPath: "/srv/my-app/control/rampway.config.mjs",
            releaseBranch: "main"
          }
        }
      }
    }
  })
})

export default {routes}
```

`accessTokens` and every `configPath` are explicit backend configuration.
`workerBootstrapPath` is also explicit backend configuration. It must be an
absolute regular module file available in the deployed application release; the
mount resolves it to its real path before accepting requests. The module is
loaded only by Rampway's package-owned worker and must default-export a factory
that reconstructs the host application's Velocious configuration:

```js
import configurationResolver from "velocious/build/src/configuration-resolver.js"

export default async function createRampwayDeploymentWorkerContext() {
  return {
    configuration: await configurationResolver({directory: "/srv/my-app"})
  }
}
```

The factory receives only the bounded stable mount and run identifiers. It must
load backend-owned configuration and secrets itself; neither bearer tokens nor
deployment targets are passed to the worker.
Provision the secrets module outside source control using the application's
normal secret mechanism. There is deliberately no environment-variable
fallback. `configPath` must be absolute, and its selected Rampway stage must
use `remote-git`; the deployment target must have Git access to the stage's
configured repository. Every `releaseBranch` is checked against Git heads-ref
rules during mounting, so invalid names such as a trailing dot or `.lock`
component fail at startup rather than during deployment.

Mount options:

| Option | Required | Meaning |
| --- | --- | --- |
| `at` | yes | HTTP mount prefix. |
| `accessTokens` | yes | One or more non-empty backend-held bearer tokens. |
| `projects` | yes | Explicit project/stage allowlist with absolute Rampway config paths and approved release branches. |
| `databaseIdentifier` | no | Velocious database used for runs/audits; default `default`. |
| `staleRunTimeoutMs` | no | Ownership lease; default `60000`, minimum `4`. |
| `workerBootstrapPath` | yes | Absolute backend module that reconstructs the Velocious configuration in the detached worker. |

## Client calls

Create a run from a client whose token is also loaded from explicit backend
secrets (not an environment fallback or command argument):

```js
import deploymentClientSecrets from "./config/secrets/deployment-client.js"

const baseUrl = "https://app.example/rampway/deployments"
const headers = {
  Authorization: `Bearer ${deploymentClientSecrets.rampwayAccessToken}`,
  "Content-Type": "application/json"
}
const createResponse = await fetch(`${baseUrl}/runs`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    project: "my-app",
    stage: "production",
    revision: "0123456789abcdef0123456789abcdef01234567",
    expectedActiveRevision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    idempotencyKey: "release-2026-08-03-1"
  })
})
const created = await createResponse.json()
```

`expectedActiveRevision` is optional and, when present, must be a full 40-character hexadecimal SHA. It is durably stored and audited with the run, participates in replay/conflict identity, and is passed unchanged to execution. Execution acquires the deployment lock before strictly resolving the active release's `REVISION`; there is no bootstrap or force-bypass behavior.

Read the durable result:

```js
const runResponse = await fetch(`${baseUrl}/runs/${encodeURIComponent(created.run.id)}`, {headers})
const {run, current} = await runResponse.json()
```

## Deploying from the Rampway CLI

A stage that configures `stage.http` can deploy through this API directly from
the CLI:

```sh
RAMPWAY_DEPLOY_TOKEN=... rampway production deploy --transport=http
RAMPWAY_DEPLOY_TOKEN=... rampway production deploy --transport=auto
```

`--transport=auto` (the default) tries the control plane first and falls back
to the stage's direct SSH/local transport only when the control plane is
unreachable, the project/stage is not allowlisted (`404`), or it returns `5xx`
without having admitted a run. It never falls back on `401`/`403`/`409`/`422`,
on a `5xx` that carries an admitted run, or once a run has been admitted and is
being polled. `--transport=ssh` bypasses the control plane entirely;
`--transport=http` requires `stage.http` and fails if it is missing. Dry runs
always print a direct-transport plan.

The separate `rampway <stage> plan [revision]` command is purely structural.
Even for a stage configured with `http`, it never contacts this control plane
and does not resolve a branch, prove that a supplied SHA exists or is reachable
from the approved branch, or request deployment admission. Those full-SHA and
reachability checks remain part of an actual HTTP deployment.

The client posts the stage's project/application name, an immutable full commit
SHA (resolving `stage.branch` with `git ls-remote` against `stage.repo` when no
revision is pinned), and an idempotency key derived from the project, stage,
revision, and release timestamp. It then polls the run until it reaches a
terminal status. A `succeeded` run is reported as a normal deploy; a
`failed`/`interrupted`/`reconciliation_required` run fails the deploy with the
control plane's sanitized error, and Rampway records whether previous live
state was restored. Polling is bounded by `http.pollIntervalMs` (default
`2000`) and `http.timeoutMs` (default 30 minutes); losing contact with the
control plane while a run is admitted fails the deploy rather than retrying
elsewhere.

If admission finds a live run for the same project/stage, immutable revision,
and `expectedActiveRevision` value, it returns that canonical run with HTTP
`200` and `coalesced: true`; the client polls it and exposes `coalesced: true`
in the final deploy report. No second detached worker is started. A different
revision or expected-active fence remains a `409 deployment_in_progress`
conflict. A same-key replay remains `replayed: true` and is distinct from
coalescing a different key onto a live run.

Authentication is accepted only from the `Authorization` header. Never place
the token in a URL or command argument that is logged; clients should obtain it
from their own secret store and configure the HTTP library's header directly.
Rampway never includes tokens in response data, run state, audit payloads, Git
commands, deploy commands, or deployment reports. Adapter values and object
keys are JSON-normalized and recursively redacted before persistence. Sanitized
structures are limited to 100 entries per array/object, 1,000 visited values,
20 levels, 4,000 characters per string, and 64 KiB total serialized output;
explicit truncation markers and a bounded redacted preview retain diagnostic
context.

Create responses are bounded:

- `202`: created; execution continues in a separate detached Node process.
- `200`: matching idempotent replay or live-run coalescing; the canonical run is returned. A coalesced response includes `coalesced: true`. Both identities include `expectedActiveRevision`, so changing the guard returns a conflict rather than inheriting another caller's result.
- `401`: missing or invalid bearer token.
- `404`: project/stage is not allowlisted.
- `409`: key conflict, an active deployment, or operator reconciliation is required.
- `422`: invalid request parameters.

Run status is one of `pending`, `running`, `succeeded`, `failed`, `interrupted`,
or `reconciliation_required`. Failed runs include sanitized recovery state.
An immutable revision that is not reachable from the approved branch is
recorded as a sanitized failed run rather than discarded before admission.
Readback also reports the current/previous Rampway release and active revision.
If live status cannot be read, the response still returns the durable run with
`live_status: null` and a sanitized `live_status_error` annotation; the outage
is also sent to Velocious framework error reporting.

## Execution and recovery guarantees

Rampway serializes durable admission and writes the request audit before
launching the worker. The parent waits for the operating system's child-spawn
acknowledgement before returning `202`; the worker then claims the pending run
and writes the heartbeat, lifecycle audits, terminal result, and database
connections from its own process. Its stdin and stdout are ignored; stderr is
inherited so credential-selection diagnostics remain visible in the operator's
application logs. Its invocation contains only the immutable bootstrap path and
bounded mount/run identifiers. A launch error fences the pending run as failed
and is not retried in the request process.

Rampway waits for the existing target advisory lock and re-reads durable state
before deciding admission. Concurrent callers using independent database
connections therefore create one canonical run for a matching revision and
expected-active fence; different revisions or fences do not coalesce. Failed
runs are terminal and do not suppress a later legitimate retry. Rampway
serializes durable admission before validating reachability on the deployment
target, so an idempotent retry storm performs the Git fetch only for the
created run. It then invokes the same `deploy()` used by the CLI. Under that
deploy-host lock, Rampway enforces `expectedActiveRevision` as its first
locked operation, before any remote repository access. It then resolves an
unpinned branch or a pinned abbreviated revision to the canonical full commit
SHA and compares that immutable identity with the active release metadata. A
match appends the bounded deploy report with `idempotent: true`, the active
release and canonical revision, and `healthChecks: "unchanged"` (no invented
runtime or health success), then returns it for the active release before
release creation, source preparation, tasks, runtime handoff, health
checks, publication, or cleanup. Directory preparation and pre-lock hooks have
already run at that point. Active-revision admission applies only to
remote-git revisions, which immutably pin the fetched content; mutable
local-copy labels always run the full lifecycle. A mismatch continues through
the established
remote-Git release, linked state, build/migration tasks, runtime handoff, health
checks, `current` link, cleanup, and report persistence. It does not implement
an alternate deployment engine or arbitrary command bridge. If
failure occurs after the requested revision becomes active, the executor calls
Rampway's existing rollback lifecycle with the captured prior current release
as its explicit target and records whether that previous live state was
restored.

Runs and audits are stored in `rampway_deployment_runs` and
`rampway_deployment_api_audit_events`, scoped by a stable hash of the mount.
Target and idempotency advisory locks prevent duplicate concurrent admission,
including one key racing across targets. Target lock acquisition waits so a
same-revision caller can observe and join the canonical durable run instead of
surfacing lock contention. Pending/running/terminal transitions
are fenced by mount, run, owner, status, and (for stale reconciliation) the
observed heartbeat. A renewed live owner therefore cannot be reclaimed, and an
expired owner cannot overwrite a reconciled run.

Pending work is protected by a separate durable launch-to-claim lease while the
worker imports its bootstrap and reconstructs Velocious. Status polling and
other admission processes therefore cannot interrupt a legitimately booting
worker; the lease is cleared atomically by claim or pre-claim failure. If that
launch lease expires without a claim, the pending run becomes `interrupted` and
no longer blocks a new run. A stale running run, or a known successful
activation whose success record could not be persisted, becomes
`reconciliation_required` and continues to block duplicates until an operator
verifies live state. Bootstrap failures are recorded as failed and audited when
the host configuration can still be reconstructed. Audit persistence failure is
reported through both Velocious `framework-error` and `all-error` events and
never strands deployment execution.
Keyed retries and run-status polling evaluate the lease before returning, so a
dead owner's stale `pending` or `running` state is reconciled without requiring
a different-key deployment attempt.

## Token rotation and revocation

Use overlapping tokens for zero-downtime rotation:

1. Add a new random token to the backend secrets list and restart/deploy every
   web worker.
2. Move every authorized client to the new token.
3. Remove the old token from backend secrets and restart/deploy every worker
   again.
4. Confirm the old token receives `401` on every instance.

For emergency revocation, remove the compromised token and restart all workers
immediately. Revocation affects new HTTP requests; an already authenticated,
durably admitted run continues under its owner lease. Inspect the run and audit
records, reconcile any ambiguous running result, and rotate any repository or
host credentials independently if they may also be compromised.

Do not print tokens while diagnosing authentication. Record only token version
or fingerprint metadata in an external secret inventory, never the token
itself. Rampway framework-error payloads deliberately omit the HTTP request so
listeners cannot accidentally serialize its Authorization header.
