rampway 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/build/src/velocious/auth.d.ts +17 -0
- package/build/src/velocious/auth.d.ts.map +1 -0
- package/build/src/velocious/auth.js +30 -0
- package/build/src/velocious/auth.js.map +1 -0
- package/build/src/velocious/control-plane.d.ts +177 -0
- package/build/src/velocious/control-plane.d.ts.map +1 -0
- package/build/src/velocious/control-plane.js +290 -0
- package/build/src/velocious/control-plane.js.map +1 -0
- package/build/src/velocious/controller.d.ts +58 -0
- package/build/src/velocious/controller.d.ts.map +1 -0
- package/build/src/velocious/controller.js +143 -0
- package/build/src/velocious/controller.js.map +1 -0
- package/build/src/velocious/executor.d.ts +55 -0
- package/build/src/velocious/executor.d.ts.map +1 -0
- package/build/src/velocious/executor.js +142 -0
- package/build/src/velocious/executor.js.map +1 -0
- package/build/src/velocious/index.d.ts +31 -0
- package/build/src/velocious/index.d.ts.map +1 -0
- package/build/src/velocious/index.js +144 -0
- package/build/src/velocious/index.js.map +1 -0
- package/build/src/velocious/path-matcher.d.ts +18 -0
- package/build/src/velocious/path-matcher.d.ts.map +1 -0
- package/build/src/velocious/path-matcher.js +48 -0
- package/build/src/velocious/path-matcher.js.map +1 -0
- package/build/src/velocious/registry.d.ts +25 -0
- package/build/src/velocious/registry.d.ts.map +1 -0
- package/build/src/velocious/registry.js +48 -0
- package/build/src/velocious/registry.js.map +1 -0
- package/build/src/velocious/run-store.d.ts +347 -0
- package/build/src/velocious/run-store.d.ts.map +1 -0
- package/build/src/velocious/run-store.js +569 -0
- package/build/src/velocious/run-store.js.map +1 -0
- package/build/src/velocious/sanitize.d.ts +23 -0
- package/build/src/velocious/sanitize.d.ts.map +1 -0
- package/build/src/velocious/sanitize.js +154 -0
- package/build/src/velocious/sanitize.js.map +1 -0
- package/build/src/velocious/worker.d.ts +48 -0
- package/build/src/velocious/worker.d.ts.map +1 -0
- package/build/src/velocious/worker.js +165 -0
- package/build/src/velocious/worker.js.map +1 -0
- package/docs/docker-development-environment.md +103 -0
- package/docs/velocious-deployment-api.md +226 -0
- package/package.json +22 -3
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Velocious callable deployment API
|
|
2
|
+
|
|
3
|
+
`rampway/velocious` is Rampway's authenticated deployment control plane for a
|
|
4
|
+
Velocious application. Rampway owns every deployment-specific decision and
|
|
5
|
+
calls its normal deploy lifecycle; Velocious supplies only route mounting,
|
|
6
|
+
request/connection scopes, database persistence, background execution, and
|
|
7
|
+
framework error events. Each admitted run is executed by a package-owned
|
|
8
|
+
detached Node worker, not by the request process.
|
|
9
|
+
|
|
10
|
+
The API accepts no command, path, repository, branch, environment value, log
|
|
11
|
+
request, or arbitrary Git ref. A caller can select only a configured
|
|
12
|
+
project/stage pair, an immutable lowercase 40-character commit SHA reachable
|
|
13
|
+
from that target's approved release branch, and an idempotency key.
|
|
14
|
+
The idempotency key must be a non-secret client-generated value of at most 255
|
|
15
|
+
characters and must not contain any configured bearer token; matching is
|
|
16
|
+
case-sensitive, and such requests are rejected with `422` before run lookup or
|
|
17
|
+
persistence.
|
|
18
|
+
|
|
19
|
+
## Install and mount
|
|
20
|
+
|
|
21
|
+
Install Velocious in the application that mounts this optional integration.
|
|
22
|
+
Rampway declares it as an optional peer so applications that only use the CLI
|
|
23
|
+
do not acquire a web-framework dependency.
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm install rampway velocious
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
In the application's Velocious routes file:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import Routes from "velocious/build/src/routes/index.js"
|
|
33
|
+
import RampwayDeploymentApi from "rampway/velocious"
|
|
34
|
+
import deploymentSecrets from "../config/secrets/deployments.js"
|
|
35
|
+
|
|
36
|
+
const routes = new Routes()
|
|
37
|
+
|
|
38
|
+
routes.draw((route) => {
|
|
39
|
+
route.mount(RampwayDeploymentApi, {
|
|
40
|
+
at: "/rampway/deployments",
|
|
41
|
+
accessTokens: deploymentSecrets.rampwayAccessTokens,
|
|
42
|
+
workerBootstrapPath: "/srv/my-app/control/rampway-velocious-worker.mjs",
|
|
43
|
+
projects: {
|
|
44
|
+
"my-app": {
|
|
45
|
+
stages: {
|
|
46
|
+
production: {
|
|
47
|
+
configPath: "/srv/my-app/control/rampway.config.mjs",
|
|
48
|
+
releaseBranch: "main"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
export default {routes}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`accessTokens` and every `configPath` are explicit backend configuration.
|
|
60
|
+
`workerBootstrapPath` is also explicit backend configuration. It must be an
|
|
61
|
+
absolute regular module file available in the deployed application release; the
|
|
62
|
+
mount resolves it to its real path before accepting requests. The module is
|
|
63
|
+
loaded only by Rampway's package-owned worker and must default-export a factory
|
|
64
|
+
that reconstructs the host application's Velocious configuration:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import configurationResolver from "velocious/build/src/configuration-resolver.js"
|
|
68
|
+
|
|
69
|
+
export default async function createRampwayDeploymentWorkerContext() {
|
|
70
|
+
return {
|
|
71
|
+
configuration: await configurationResolver({directory: "/srv/my-app"})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The factory receives only the bounded stable mount and run identifiers. It must
|
|
77
|
+
load backend-owned configuration and secrets itself; neither bearer tokens nor
|
|
78
|
+
deployment targets are passed to the worker.
|
|
79
|
+
Provision the secrets module outside source control using the application's
|
|
80
|
+
normal secret mechanism. There is deliberately no environment-variable
|
|
81
|
+
fallback. `configPath` must be absolute, and its selected Rampway stage must
|
|
82
|
+
use `remote-git`; the deployment target must have Git access to the stage's
|
|
83
|
+
configured repository. Every `releaseBranch` is checked against Git heads-ref
|
|
84
|
+
rules during mounting, so invalid names such as a trailing dot or `.lock`
|
|
85
|
+
component fail at startup rather than during deployment.
|
|
86
|
+
|
|
87
|
+
Mount options:
|
|
88
|
+
|
|
89
|
+
| Option | Required | Meaning |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `at` | yes | HTTP mount prefix. |
|
|
92
|
+
| `accessTokens` | yes | One or more non-empty backend-held bearer tokens. |
|
|
93
|
+
| `projects` | yes | Explicit project/stage allowlist with absolute Rampway config paths and approved release branches. |
|
|
94
|
+
| `databaseIdentifier` | no | Velocious database used for runs/audits; default `default`. |
|
|
95
|
+
| `staleRunTimeoutMs` | no | Ownership lease; default `60000`, minimum `4`. |
|
|
96
|
+
| `workerBootstrapPath` | yes | Absolute backend module that reconstructs the Velocious configuration in the detached worker. |
|
|
97
|
+
|
|
98
|
+
## Client calls
|
|
99
|
+
|
|
100
|
+
Create a run from a client whose token is also loaded from explicit backend
|
|
101
|
+
secrets (not an environment fallback or command argument):
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import deploymentClientSecrets from "./config/secrets/deployment-client.js"
|
|
105
|
+
|
|
106
|
+
const baseUrl = "https://app.example/rampway/deployments"
|
|
107
|
+
const headers = {
|
|
108
|
+
Authorization: `Bearer ${deploymentClientSecrets.rampwayAccessToken}`,
|
|
109
|
+
"Content-Type": "application/json"
|
|
110
|
+
}
|
|
111
|
+
const createResponse = await fetch(`${baseUrl}/runs`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers,
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
project: "my-app",
|
|
116
|
+
stage: "production",
|
|
117
|
+
revision: "0123456789abcdef0123456789abcdef01234567",
|
|
118
|
+
idempotencyKey: "release-2026-08-03-1"
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
const created = await createResponse.json()
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Read the durable result:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const runResponse = await fetch(`${baseUrl}/runs/${encodeURIComponent(created.run.id)}`, {headers})
|
|
128
|
+
const {run, current} = await runResponse.json()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Authentication is accepted only from the `Authorization` header. Never place
|
|
132
|
+
the token in a URL or command argument that is logged; clients should obtain it
|
|
133
|
+
from their own secret store and configure the HTTP library's header directly.
|
|
134
|
+
Rampway never includes tokens in response data, run state, audit payloads, Git
|
|
135
|
+
commands, deploy commands, or deployment reports. Adapter values and object
|
|
136
|
+
keys are JSON-normalized and recursively redacted before persistence. Sanitized
|
|
137
|
+
structures are limited to 100 entries per array/object, 1,000 visited values,
|
|
138
|
+
20 levels, 4,000 characters per string, and 64 KiB total serialized output;
|
|
139
|
+
explicit truncation markers and a bounded redacted preview retain diagnostic
|
|
140
|
+
context.
|
|
141
|
+
|
|
142
|
+
Create responses are bounded:
|
|
143
|
+
|
|
144
|
+
- `202`: created; execution continues in a separate detached Node process.
|
|
145
|
+
- `200`: matching idempotent replay; the original run is returned.
|
|
146
|
+
- `401`: missing or invalid bearer token.
|
|
147
|
+
- `404`: project/stage is not allowlisted.
|
|
148
|
+
- `409`: key conflict, an active deployment, or operator reconciliation is required.
|
|
149
|
+
- `422`: invalid request parameters.
|
|
150
|
+
|
|
151
|
+
Run status is one of `pending`, `running`, `succeeded`, `failed`, `interrupted`,
|
|
152
|
+
or `reconciliation_required`. Failed runs include sanitized recovery state.
|
|
153
|
+
An immutable revision that is not reachable from the approved branch is
|
|
154
|
+
recorded as a sanitized failed run rather than discarded before admission.
|
|
155
|
+
Readback also reports the current/previous Rampway release and active revision.
|
|
156
|
+
If live status cannot be read, the response still returns the durable run with
|
|
157
|
+
`live_status: null` and a sanitized `live_status_error` annotation; the outage
|
|
158
|
+
is also sent to Velocious framework error reporting.
|
|
159
|
+
|
|
160
|
+
## Execution and recovery guarantees
|
|
161
|
+
|
|
162
|
+
Rampway serializes durable admission and writes the request audit before
|
|
163
|
+
launching the worker. The parent waits for the operating system's child-spawn
|
|
164
|
+
acknowledgement before returning `202`; the worker then claims the pending run
|
|
165
|
+
and writes the heartbeat, lifecycle audits, terminal result, and database
|
|
166
|
+
connections from its own process. Its stdio is ignored rather than inherited,
|
|
167
|
+
and its invocation contains only the immutable bootstrap path and bounded
|
|
168
|
+
mount/run identifiers. A launch error fences the pending run as failed and is
|
|
169
|
+
not retried in the request process.
|
|
170
|
+
|
|
171
|
+
Rampway serializes durable admission before validating reachability on the
|
|
172
|
+
deployment target, so an idempotent retry storm performs the Git fetch only for
|
|
173
|
+
the created run. It then invokes the same `deploy()` used by the CLI: deploy
|
|
174
|
+
lock, immutable remote-Git release, linked state, build/migration tasks, runtime
|
|
175
|
+
handoff, health checks, `current` link, cleanup, and report persistence. It does
|
|
176
|
+
not implement an alternate deployment engine or arbitrary command bridge. If
|
|
177
|
+
failure occurs after the requested revision becomes active, the executor calls
|
|
178
|
+
Rampway's existing rollback lifecycle with the captured prior current release
|
|
179
|
+
as its explicit target and records whether that previous live state was
|
|
180
|
+
restored.
|
|
181
|
+
|
|
182
|
+
Runs and audits are stored in `rampway_deployment_runs` and
|
|
183
|
+
`rampway_deployment_api_audit_events`, scoped by a stable hash of the mount.
|
|
184
|
+
Target and idempotency advisory locks prevent duplicate concurrent admission,
|
|
185
|
+
including one key racing across targets. Pending/running/terminal transitions
|
|
186
|
+
are fenced by mount, run, owner, status, and (for stale reconciliation) the
|
|
187
|
+
observed heartbeat. A renewed live owner therefore cannot be reclaimed, and an
|
|
188
|
+
expired owner cannot overwrite a reconciled run.
|
|
189
|
+
|
|
190
|
+
Pending work is protected by a separate durable launch-to-claim lease while the
|
|
191
|
+
worker imports its bootstrap and reconstructs Velocious. Status polling and
|
|
192
|
+
other admission processes therefore cannot interrupt a legitimately booting
|
|
193
|
+
worker; the lease is cleared atomically by claim or pre-claim failure. If that
|
|
194
|
+
launch lease expires without a claim, the pending run becomes `interrupted` and
|
|
195
|
+
no longer blocks a new run. A stale running run, or a known successful
|
|
196
|
+
activation whose success record could not be persisted, becomes
|
|
197
|
+
`reconciliation_required` and continues to block duplicates until an operator
|
|
198
|
+
verifies live state. Bootstrap failures are recorded as failed and audited when
|
|
199
|
+
the host configuration can still be reconstructed. Audit persistence failure is
|
|
200
|
+
reported through both Velocious `framework-error` and `all-error` events and
|
|
201
|
+
never strands deployment execution.
|
|
202
|
+
Keyed retries and run-status polling evaluate the lease before returning, so a
|
|
203
|
+
dead owner's stale `pending` or `running` state is reconciled without requiring
|
|
204
|
+
a different-key deployment attempt.
|
|
205
|
+
|
|
206
|
+
## Token rotation and revocation
|
|
207
|
+
|
|
208
|
+
Use overlapping tokens for zero-downtime rotation:
|
|
209
|
+
|
|
210
|
+
1. Add a new random token to the backend secrets list and restart/deploy every
|
|
211
|
+
web worker.
|
|
212
|
+
2. Move every authorized client to the new token.
|
|
213
|
+
3. Remove the old token from backend secrets and restart/deploy every worker
|
|
214
|
+
again.
|
|
215
|
+
4. Confirm the old token receives `401` on every instance.
|
|
216
|
+
|
|
217
|
+
For emergency revocation, remove the compromised token and restart all workers
|
|
218
|
+
immediately. Revocation affects new HTTP requests; an already authenticated,
|
|
219
|
+
durably admitted run continues under its owner lease. Inspect the run and audit
|
|
220
|
+
records, reconcile any ambiguous running result, and rotate any repository or
|
|
221
|
+
host credentials independently if they may also be compromised.
|
|
222
|
+
|
|
223
|
+
Do not print tokens while diagnosing authentication. Record only token version
|
|
224
|
+
or fingerprint metadata in an external secret inventory, never the token
|
|
225
|
+
itself. Rampway framework-error payloads deliberately omit the HTTP request so
|
|
226
|
+
listeners cannot accidentally serialize its Authorization header.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rampway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "JS-native Capistrano-style release-directory deployments with Rollbridge integration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -54,6 +54,14 @@
|
|
|
54
54
|
"types": "./build/src/test-helpers.d.ts",
|
|
55
55
|
"default": "./build/src/test-helpers.js"
|
|
56
56
|
},
|
|
57
|
+
"./velocious": {
|
|
58
|
+
"types": "./build/src/velocious/index.d.ts",
|
|
59
|
+
"default": "./build/src/velocious/index.js"
|
|
60
|
+
},
|
|
61
|
+
"./velocious.js": {
|
|
62
|
+
"types": "./build/src/velocious/index.d.ts",
|
|
63
|
+
"default": "./build/src/velocious/index.js"
|
|
64
|
+
},
|
|
57
65
|
"./package.json": "./package.json"
|
|
58
66
|
},
|
|
59
67
|
"imports": {
|
|
@@ -64,8 +72,10 @@
|
|
|
64
72
|
},
|
|
65
73
|
"scripts": {
|
|
66
74
|
"test": "npm run build && node --test tests/**/*.test.js",
|
|
67
|
-
"lint": "eslint src/ tests/",
|
|
75
|
+
"lint": "eslint src/ tests/ scripts/",
|
|
68
76
|
"typecheck": "tsc --noEmit",
|
|
77
|
+
"verify:docker-dev-environment": "node scripts/verify-docker-dev-environment.js",
|
|
78
|
+
"test:docker-dev-environment": "node --test tests/docker-dev-environment.test.js",
|
|
69
79
|
"clean": "node scripts/clean.js",
|
|
70
80
|
"compile": "tsc -b",
|
|
71
81
|
"postcompile": "node scripts/ensure-cli-executable.js",
|
|
@@ -84,7 +94,16 @@
|
|
|
84
94
|
"devDependencies": {
|
|
85
95
|
"eslint": "^9.39.2",
|
|
86
96
|
"eslint-plugin-jsdoc": "^61.5.0",
|
|
87
|
-
"typescript": "^5.9.3"
|
|
97
|
+
"typescript": "^5.9.3",
|
|
98
|
+
"velocious": "^1.0.574"
|
|
99
|
+
},
|
|
100
|
+
"peerDependencies": {
|
|
101
|
+
"velocious": "^1.0.574"
|
|
102
|
+
},
|
|
103
|
+
"peerDependenciesMeta": {
|
|
104
|
+
"velocious": {
|
|
105
|
+
"optional": true
|
|
106
|
+
}
|
|
88
107
|
},
|
|
89
108
|
"files": [
|
|
90
109
|
"build",
|