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.
Files changed (44) hide show
  1. package/README.md +31 -0
  2. package/build/src/velocious/auth.d.ts +17 -0
  3. package/build/src/velocious/auth.d.ts.map +1 -0
  4. package/build/src/velocious/auth.js +30 -0
  5. package/build/src/velocious/auth.js.map +1 -0
  6. package/build/src/velocious/control-plane.d.ts +177 -0
  7. package/build/src/velocious/control-plane.d.ts.map +1 -0
  8. package/build/src/velocious/control-plane.js +290 -0
  9. package/build/src/velocious/control-plane.js.map +1 -0
  10. package/build/src/velocious/controller.d.ts +58 -0
  11. package/build/src/velocious/controller.d.ts.map +1 -0
  12. package/build/src/velocious/controller.js +143 -0
  13. package/build/src/velocious/controller.js.map +1 -0
  14. package/build/src/velocious/executor.d.ts +55 -0
  15. package/build/src/velocious/executor.d.ts.map +1 -0
  16. package/build/src/velocious/executor.js +142 -0
  17. package/build/src/velocious/executor.js.map +1 -0
  18. package/build/src/velocious/index.d.ts +31 -0
  19. package/build/src/velocious/index.d.ts.map +1 -0
  20. package/build/src/velocious/index.js +144 -0
  21. package/build/src/velocious/index.js.map +1 -0
  22. package/build/src/velocious/path-matcher.d.ts +18 -0
  23. package/build/src/velocious/path-matcher.d.ts.map +1 -0
  24. package/build/src/velocious/path-matcher.js +48 -0
  25. package/build/src/velocious/path-matcher.js.map +1 -0
  26. package/build/src/velocious/registry.d.ts +25 -0
  27. package/build/src/velocious/registry.d.ts.map +1 -0
  28. package/build/src/velocious/registry.js +48 -0
  29. package/build/src/velocious/registry.js.map +1 -0
  30. package/build/src/velocious/run-store.d.ts +347 -0
  31. package/build/src/velocious/run-store.d.ts.map +1 -0
  32. package/build/src/velocious/run-store.js +569 -0
  33. package/build/src/velocious/run-store.js.map +1 -0
  34. package/build/src/velocious/sanitize.d.ts +23 -0
  35. package/build/src/velocious/sanitize.d.ts.map +1 -0
  36. package/build/src/velocious/sanitize.js +154 -0
  37. package/build/src/velocious/sanitize.js.map +1 -0
  38. package/build/src/velocious/worker.d.ts +48 -0
  39. package/build/src/velocious/worker.d.ts.map +1 -0
  40. package/build/src/velocious/worker.js +165 -0
  41. package/build/src/velocious/worker.js.map +1 -0
  42. package/docs/docker-development-environment.md +103 -0
  43. package/docs/velocious-deployment-api.md +226 -0
  44. 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.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",