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
package/README.md
CHANGED
|
@@ -22,12 +22,30 @@ This initial scaffold includes:
|
|
|
22
22
|
- JSONL deploy reports at `shared/log/rampway-deployments.jsonl`
|
|
23
23
|
- secret redaction helpers
|
|
24
24
|
- basic runtime adapter interface with `none` and Rollbridge validate/deploy/status/logs/recover helpers
|
|
25
|
+
- optional Rampway-owned authenticated Velocious deployment API at `rampway/velocious`
|
|
25
26
|
|
|
26
27
|
SSH transport command execution supports both local-copy and remote-git deployments. `remote-git` prepares each release checkout on the target host, so the target needs `git` and access to the configured repository.
|
|
27
28
|
|
|
28
29
|
Full config reference: [docs/config-reference.md](docs/config-reference.md)
|
|
29
30
|
Migrating from Capistrano: [docs/migration-from-capistrano.md](docs/migration-from-capistrano.md)
|
|
30
31
|
|
|
32
|
+
## Docker development environment
|
|
33
|
+
|
|
34
|
+
Rampway's canonical development environment is one persistent `dev` service built from the root `Dockerfile`: digest-pinned Ubuntu 26.04 LTS, exact signed/checksummed NodeSource Node 24, the universal coding/debugging baseline, and the current OpenCode, Codex, Claude Code, and official Kimi Code CLIs. The image is source-independent and installs no project dependencies or Threadwire.
|
|
35
|
+
|
|
36
|
+
Copy the portable environment template, prepare mount-target directories, start the service, and install locked dependencies inside it:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cp .env.example .env
|
|
40
|
+
scripts/prepare-dev-home.sh
|
|
41
|
+
docker compose up --build --detach dev
|
|
42
|
+
scripts/docker-run.sh npm ci
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The complete dedicated development home is mounted at `/home/dev`; GitHub CLI configuration and the narrow Codex auth file are required read-only mounts. `scripts/docker-run.sh` executes project commands through the already-running service. Use a distinct `COMPOSE_PROJECT_NAME` and `DEV_HOME_PATH` for each concurrent instance.
|
|
46
|
+
|
|
47
|
+
Setup, credential boundaries, isolation, static checks, and Docker-capable coordinator validation are documented in [docs/docker-development-environment.md](docs/docker-development-environment.md).
|
|
48
|
+
|
|
31
49
|
## Commands
|
|
32
50
|
|
|
33
51
|
```bash
|
|
@@ -169,6 +187,19 @@ Task groups run in config order. Required task failures stop the deploy before p
|
|
|
169
187
|
|
|
170
188
|
Standalone operational tasks are configured separately under `operationalTasks` and run only when explicitly selected with `rampway <stage> task <exact-name>`. Rampway acquires the shared deploy lock, strictly resolves the active release, and runs every required entry against that immutable release path. `forwardEnv` names caller-supplied environment variables that must be present; their values are transported separately from ordinary `env` and redacted from output and errors. Operational tasks do not run during deploy, accept no command arguments or `--force`, and are not included in the plural `tasks` deploy-task inventory.
|
|
171
189
|
|
|
190
|
+
## Callable deployments from Velocious
|
|
191
|
+
|
|
192
|
+
Velocious applications can mount the authenticated control plane exported as
|
|
193
|
+
`rampway/velocious`. The HTTP API accepts only allowlisted Rampway config/stage
|
|
194
|
+
targets, full Git commit SHAs reachable from an approved release branch, and
|
|
195
|
+
idempotency keys. Each admitted run is handed to a detached package-owned Node
|
|
196
|
+
worker, which reconstructs the backend Velocious context and executes Rampway's
|
|
197
|
+
existing deploy/rollback/status lifecycle; Velocious remains the framework and
|
|
198
|
+
persistence runtime. See
|
|
199
|
+
[Velocious callable deployment API](docs/velocious-deployment-api.md) for
|
|
200
|
+
mounting, client calls, durable recovery behavior, and token
|
|
201
|
+
rotation/revocation.
|
|
202
|
+
|
|
172
203
|
## Deployment reports
|
|
173
204
|
|
|
174
205
|
Every successful deploy or rollback appends one JSON object per line to `shared/log/rampway-deployments.jsonl`. Deploy events include:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares two token strings without content-dependent timing once their
|
|
3
|
+
* byte lengths match.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} left - Candidate token.
|
|
6
|
+
* @param {string} right - Configured token.
|
|
7
|
+
* @returns {boolean} Whether the values match.
|
|
8
|
+
*/
|
|
9
|
+
export function constantTimeEqual(left: string, right: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Extracts a bearer token from an Authorization header.
|
|
12
|
+
*
|
|
13
|
+
* @param {unknown} authorization - Header value.
|
|
14
|
+
* @returns {string|null} Token or null.
|
|
15
|
+
*/
|
|
16
|
+
export function bearerToken(authorization: unknown): string | null;
|
|
17
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/velocious/auth.js"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,wCAJW,MAAM,SACN,MAAM,GACJ,OAAO,CAQnB;AAED;;;;;GAKG;AACH,2CAHW,OAAO,GACL,MAAM,GAAC,IAAI,CAQvB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
/**
|
|
4
|
+
* Compares two token strings without content-dependent timing once their
|
|
5
|
+
* byte lengths match.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} left - Candidate token.
|
|
8
|
+
* @param {string} right - Configured token.
|
|
9
|
+
* @returns {boolean} Whether the values match.
|
|
10
|
+
*/
|
|
11
|
+
export function constantTimeEqual(left, right) {
|
|
12
|
+
// Compare fixed-length digests so even differently sized candidates reach
|
|
13
|
+
// timingSafeEqual instead of taking a length-mismatch branch.
|
|
14
|
+
const leftDigest = crypto.createHash("sha256").update(String(left)).digest();
|
|
15
|
+
const rightDigest = crypto.createHash("sha256").update(String(right)).digest();
|
|
16
|
+
return crypto.timingSafeEqual(leftDigest, rightDigest);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extracts a bearer token from an Authorization header.
|
|
20
|
+
*
|
|
21
|
+
* @param {unknown} authorization - Header value.
|
|
22
|
+
* @returns {string|null} Token or null.
|
|
23
|
+
*/
|
|
24
|
+
export function bearerToken(authorization) {
|
|
25
|
+
if (typeof authorization !== "string")
|
|
26
|
+
return null;
|
|
27
|
+
const match = authorization.match(/^Bearer\s+([^\s].*)$/i);
|
|
28
|
+
return match ? match[1].trim() : null;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/velocious/auth.js"],"names":[],"mappings":"AAAA,YAAY;AAEZ,OAAO,MAAM,MAAM,aAAa,CAAA;AAEhC;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAI,EAAE,KAAK;IAC3C,0EAA0E;IAC1E,8DAA8D;IAC9D,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAA;IAC5E,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAA;IAC9E,OAAO,MAAM,CAAC,eAAe,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,aAAa;IACvC,IAAI,OAAO,aAAa,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAElD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;IAE1D,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACvC,CAAC"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} DeploymentTarget
|
|
3
|
+
* @property {string} configPath - Backend-held Rampway config path.
|
|
4
|
+
* @property {string} releaseBranch - Approved release branch.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} DeploymentMountOptions
|
|
8
|
+
* @property {string[]} accessTokens - Accepted tokens.
|
|
9
|
+
* @property {string} workerBootstrapPath - Backend-owned worker bootstrap module.
|
|
10
|
+
* @property {string} workerPath - Immutable package worker entry path.
|
|
11
|
+
* @property {string} mountIdentifier - Stable mount scope.
|
|
12
|
+
* @property {Record<string, any>} projects - Normalized allowlist.
|
|
13
|
+
* @property {number} staleRunTimeoutMs - Lease timeout.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Looks up only explicit allowlist properties.
|
|
17
|
+
*
|
|
18
|
+
* @param {Record<string, any>} projects - Normalized project allowlist.
|
|
19
|
+
* @param {string} project - Project identifier.
|
|
20
|
+
* @param {string} stage - Stage identifier.
|
|
21
|
+
* @returns {DeploymentTarget|undefined} Target configuration.
|
|
22
|
+
*/
|
|
23
|
+
export function lookupTarget(projects: Record<string, any>, project: string, stage: string): DeploymentTarget | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Framework-neutral deployment API behavior used by the Velocious controller.
|
|
26
|
+
*
|
|
27
|
+
* @template {import('./run-store.js').DeploymentRunStoreContract} TStore
|
|
28
|
+
*/
|
|
29
|
+
export class DeploymentControlPlane<TStore extends import("./run-store.js").DeploymentRunStoreContract> {
|
|
30
|
+
/**
|
|
31
|
+
* @param {object} args - Dependencies.
|
|
32
|
+
* @param {(args: {onFailure: (error: unknown) => Promise<void>, runId: string}) => Promise<void>} args.detachedExecution - Starts the package-owned worker.
|
|
33
|
+
* @param {(payload: {context: string, error: unknown}) => void} args.emitFrameworkError - Reports internal failures.
|
|
34
|
+
* @param {import('./executor.js').RampwayDeploymentExecutor} args.executor - Rampway lifecycle executor.
|
|
35
|
+
* @param {() => number} [args.now] - Clock.
|
|
36
|
+
* @param {DeploymentMountOptions} args.options - Validated mount options.
|
|
37
|
+
* @param {TStore} args.store - Durable run store.
|
|
38
|
+
*/
|
|
39
|
+
constructor({ detachedExecution, emitFrameworkError, executor, now, options, store }: {
|
|
40
|
+
detachedExecution: (args: {
|
|
41
|
+
onFailure: (error: unknown) => Promise<void>;
|
|
42
|
+
runId: string;
|
|
43
|
+
}) => Promise<void>;
|
|
44
|
+
emitFrameworkError: (payload: {
|
|
45
|
+
context: string;
|
|
46
|
+
error: unknown;
|
|
47
|
+
}) => void;
|
|
48
|
+
executor: import("./executor.js").RampwayDeploymentExecutor;
|
|
49
|
+
now?: (() => number) | undefined;
|
|
50
|
+
options: DeploymentMountOptions;
|
|
51
|
+
store: TStore;
|
|
52
|
+
});
|
|
53
|
+
detachedExecution: (args: {
|
|
54
|
+
onFailure: (error: unknown) => Promise<void>;
|
|
55
|
+
runId: string;
|
|
56
|
+
}) => Promise<void>;
|
|
57
|
+
emitFrameworkError: (payload: {
|
|
58
|
+
context: string;
|
|
59
|
+
error: unknown;
|
|
60
|
+
}) => void;
|
|
61
|
+
executor: import("./executor.js").RampwayDeploymentExecutor;
|
|
62
|
+
now: () => number;
|
|
63
|
+
options: DeploymentMountOptions;
|
|
64
|
+
store: TStore;
|
|
65
|
+
/**
|
|
66
|
+
* @param {unknown} authorization - Authorization header.
|
|
67
|
+
* @returns {boolean} Whether request is authorized.
|
|
68
|
+
*/
|
|
69
|
+
_authorized(authorization: unknown): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} candidate - Value to compare with configured access tokens.
|
|
72
|
+
* @returns {boolean} Whether the value is an accepted access token.
|
|
73
|
+
*/
|
|
74
|
+
_matchesAccessToken(candidate: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} candidate - Idempotency key candidate.
|
|
77
|
+
* @returns {boolean} Whether the key contains a configured access token.
|
|
78
|
+
*/
|
|
79
|
+
_containsAccessToken(candidate: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Creates a deployment run.
|
|
82
|
+
*
|
|
83
|
+
* @param {{authorization: unknown, input: unknown}} args - Request data.
|
|
84
|
+
* @returns {Promise<{body: Record<string, any>, status: number}>} HTTP response.
|
|
85
|
+
*/
|
|
86
|
+
create({ authorization, input }: {
|
|
87
|
+
authorization: unknown;
|
|
88
|
+
input: unknown;
|
|
89
|
+
}): Promise<{
|
|
90
|
+
body: Record<string, any>;
|
|
91
|
+
status: number;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* @param {{authorization: unknown, id: string}} args - Request data.
|
|
95
|
+
* @returns {Promise<{body: Record<string, any>, status: number}>} HTTP response.
|
|
96
|
+
*/
|
|
97
|
+
show({ authorization, id }: {
|
|
98
|
+
authorization: unknown;
|
|
99
|
+
id: string;
|
|
100
|
+
}): Promise<{
|
|
101
|
+
body: Record<string, any>;
|
|
102
|
+
status: number;
|
|
103
|
+
}>;
|
|
104
|
+
/**
|
|
105
|
+
* @param {{existing: import('./run-store.js').DeploymentRunRow, project: string, revision: string, stage: string}} args - Replay data.
|
|
106
|
+
* @returns {{body: Record<string, any>, status: number}} Response.
|
|
107
|
+
*/
|
|
108
|
+
_existingResponse({ existing, project, revision, stage }: {
|
|
109
|
+
existing: import("./run-store.js").DeploymentRunRow;
|
|
110
|
+
project: string;
|
|
111
|
+
revision: string;
|
|
112
|
+
stage: string;
|
|
113
|
+
}): {
|
|
114
|
+
body: Record<string, any>;
|
|
115
|
+
status: number;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* @param {{claimed?: boolean, run: import('./run-store.js').DeploymentRunRow, target: DeploymentTarget}} args - Execution data.
|
|
119
|
+
* @returns {Promise<void>} Resolves after durable outcome.
|
|
120
|
+
*/
|
|
121
|
+
_executeRun({ claimed, run, target }: {
|
|
122
|
+
claimed?: boolean;
|
|
123
|
+
run: import("./run-store.js").DeploymentRunRow;
|
|
124
|
+
target: DeploymentTarget;
|
|
125
|
+
}): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* @param {{event: string, payload: Record<string, unknown>, runId: string|null}} args - Event.
|
|
128
|
+
* @returns {Promise<void>} Resolves after persistence or reporting.
|
|
129
|
+
*/
|
|
130
|
+
_audit({ event, payload, runId }: {
|
|
131
|
+
event: string;
|
|
132
|
+
payload: Record<string, unknown>;
|
|
133
|
+
runId: string | null;
|
|
134
|
+
}): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* @param {import('./run-store.js').DeploymentRunRow} run - Run.
|
|
137
|
+
* @returns {Record<string, any>} Public representation.
|
|
138
|
+
*/
|
|
139
|
+
_serializeRun(run: import("./run-store.js").DeploymentRunRow): Record<string, any>;
|
|
140
|
+
}
|
|
141
|
+
export type DeploymentTarget = {
|
|
142
|
+
/**
|
|
143
|
+
* - Backend-held Rampway config path.
|
|
144
|
+
*/
|
|
145
|
+
configPath: string;
|
|
146
|
+
/**
|
|
147
|
+
* - Approved release branch.
|
|
148
|
+
*/
|
|
149
|
+
releaseBranch: string;
|
|
150
|
+
};
|
|
151
|
+
export type DeploymentMountOptions = {
|
|
152
|
+
/**
|
|
153
|
+
* - Accepted tokens.
|
|
154
|
+
*/
|
|
155
|
+
accessTokens: string[];
|
|
156
|
+
/**
|
|
157
|
+
* - Backend-owned worker bootstrap module.
|
|
158
|
+
*/
|
|
159
|
+
workerBootstrapPath: string;
|
|
160
|
+
/**
|
|
161
|
+
* - Immutable package worker entry path.
|
|
162
|
+
*/
|
|
163
|
+
workerPath: string;
|
|
164
|
+
/**
|
|
165
|
+
* - Stable mount scope.
|
|
166
|
+
*/
|
|
167
|
+
mountIdentifier: string;
|
|
168
|
+
/**
|
|
169
|
+
* - Normalized allowlist.
|
|
170
|
+
*/
|
|
171
|
+
projects: Record<string, any>;
|
|
172
|
+
/**
|
|
173
|
+
* - Lease timeout.
|
|
174
|
+
*/
|
|
175
|
+
staleRunTimeoutMs: number;
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=control-plane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../../../src/velocious/control-plane.js"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { bearerToken, constantTimeEqual } from "./auth.js";
|
|
3
|
+
import { heartbeatIntervalMs, registerActiveDeploymentRun, unregisterActiveDeploymentRun } from "./run-store.js";
|
|
4
|
+
import { sanitizeAdapterValue, sanitizeErrorPayload } from "./sanitize.js";
|
|
5
|
+
const REVISION_PATTERN = /^[0-9a-f]{40}$/;
|
|
6
|
+
const MAX_IDEMPOTENCY_KEY_LENGTH = 255;
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} DeploymentTarget
|
|
9
|
+
* @property {string} configPath - Backend-held Rampway config path.
|
|
10
|
+
* @property {string} releaseBranch - Approved release branch.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} DeploymentMountOptions
|
|
14
|
+
* @property {string[]} accessTokens - Accepted tokens.
|
|
15
|
+
* @property {string} workerBootstrapPath - Backend-owned worker bootstrap module.
|
|
16
|
+
* @property {string} workerPath - Immutable package worker entry path.
|
|
17
|
+
* @property {string} mountIdentifier - Stable mount scope.
|
|
18
|
+
* @property {Record<string, any>} projects - Normalized allowlist.
|
|
19
|
+
* @property {number} staleRunTimeoutMs - Lease timeout.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Looks up only explicit allowlist properties.
|
|
23
|
+
*
|
|
24
|
+
* @param {Record<string, any>} projects - Normalized project allowlist.
|
|
25
|
+
* @param {string} project - Project identifier.
|
|
26
|
+
* @param {string} stage - Stage identifier.
|
|
27
|
+
* @returns {DeploymentTarget|undefined} Target configuration.
|
|
28
|
+
*/
|
|
29
|
+
export function lookupTarget(projects, project, stage) {
|
|
30
|
+
if (!Object.hasOwn(projects, project))
|
|
31
|
+
return undefined;
|
|
32
|
+
const stages = projects[project].stages;
|
|
33
|
+
if (!Object.hasOwn(stages, stage))
|
|
34
|
+
return undefined;
|
|
35
|
+
return stages[stage];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Framework-neutral deployment API behavior used by the Velocious controller.
|
|
39
|
+
*
|
|
40
|
+
* @template {import('./run-store.js').DeploymentRunStoreContract} TStore
|
|
41
|
+
*/
|
|
42
|
+
export class DeploymentControlPlane {
|
|
43
|
+
/**
|
|
44
|
+
* @param {object} args - Dependencies.
|
|
45
|
+
* @param {(args: {onFailure: (error: unknown) => Promise<void>, runId: string}) => Promise<void>} args.detachedExecution - Starts the package-owned worker.
|
|
46
|
+
* @param {(payload: {context: string, error: unknown}) => void} args.emitFrameworkError - Reports internal failures.
|
|
47
|
+
* @param {import('./executor.js').RampwayDeploymentExecutor} args.executor - Rampway lifecycle executor.
|
|
48
|
+
* @param {() => number} [args.now] - Clock.
|
|
49
|
+
* @param {DeploymentMountOptions} args.options - Validated mount options.
|
|
50
|
+
* @param {TStore} args.store - Durable run store.
|
|
51
|
+
*/
|
|
52
|
+
constructor({ detachedExecution, emitFrameworkError, executor, now = Date.now, options, store }) {
|
|
53
|
+
this.detachedExecution = detachedExecution;
|
|
54
|
+
this.emitFrameworkError = emitFrameworkError;
|
|
55
|
+
this.executor = executor;
|
|
56
|
+
this.now = now;
|
|
57
|
+
this.options = options;
|
|
58
|
+
this.store = store;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @param {unknown} authorization - Authorization header.
|
|
62
|
+
* @returns {boolean} Whether request is authorized.
|
|
63
|
+
*/
|
|
64
|
+
_authorized(authorization) {
|
|
65
|
+
const token = bearerToken(authorization);
|
|
66
|
+
return token ? this._matchesAccessToken(token) : false;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* @param {string} candidate - Value to compare with configured access tokens.
|
|
70
|
+
* @returns {boolean} Whether the value is an accepted access token.
|
|
71
|
+
*/
|
|
72
|
+
_matchesAccessToken(candidate) {
|
|
73
|
+
let matches = false;
|
|
74
|
+
for (const accessToken of this.options.accessTokens) {
|
|
75
|
+
// Do every configured comparison instead of exposing which rotation token matched.
|
|
76
|
+
matches = constantTimeEqual(candidate, accessToken) || matches;
|
|
77
|
+
}
|
|
78
|
+
return matches;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* @param {string} candidate - Idempotency key candidate.
|
|
82
|
+
* @returns {boolean} Whether the key contains a configured access token.
|
|
83
|
+
*/
|
|
84
|
+
_containsAccessToken(candidate) {
|
|
85
|
+
let contains = false;
|
|
86
|
+
for (const accessToken of this.options.accessTokens) {
|
|
87
|
+
contains = candidate.includes(accessToken) || contains;
|
|
88
|
+
}
|
|
89
|
+
return contains;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Creates a deployment run.
|
|
93
|
+
*
|
|
94
|
+
* @param {{authorization: unknown, input: unknown}} args - Request data.
|
|
95
|
+
* @returns {Promise<{body: Record<string, any>, status: number}>} HTTP response.
|
|
96
|
+
*/
|
|
97
|
+
async create({ authorization, input }) {
|
|
98
|
+
if (!this._authorized(authorization))
|
|
99
|
+
return { body: { error: "unauthorized" }, status: 401 };
|
|
100
|
+
const params = input && typeof input === "object" ? /** @type {Record<string, unknown>} */ (input) : {};
|
|
101
|
+
const revision = typeof params.revision === "string" ? params.revision : null;
|
|
102
|
+
const idempotencyKey = typeof params.idempotencyKey === "string" ? params.idempotencyKey : null;
|
|
103
|
+
const invalidFields = [];
|
|
104
|
+
if (!revision || !REVISION_PATTERN.test(revision))
|
|
105
|
+
invalidFields.push("revision");
|
|
106
|
+
if (!idempotencyKey || idempotencyKey.length > MAX_IDEMPOTENCY_KEY_LENGTH || this._containsAccessToken(idempotencyKey)) {
|
|
107
|
+
invalidFields.push("idempotencyKey");
|
|
108
|
+
}
|
|
109
|
+
if (invalidFields.length > 0)
|
|
110
|
+
return { body: { error: "invalid_params", fields: invalidFields }, status: 422 };
|
|
111
|
+
const validRevision = /** @type {string} */ (revision);
|
|
112
|
+
const validIdempotencyKey = /** @type {string} */ (idempotencyKey);
|
|
113
|
+
const project = typeof params.project === "string" ? params.project : "";
|
|
114
|
+
const stage = typeof params.stage === "string" ? params.stage : "";
|
|
115
|
+
const target = lookupTarget(this.options.projects, project, stage);
|
|
116
|
+
if (!target)
|
|
117
|
+
return { body: { error: "not_found" }, status: 404 };
|
|
118
|
+
const outcome = await this.store.createRunIfPossible({ idempotencyKey: validIdempotencyKey, project, revision: validRevision, stage });
|
|
119
|
+
if (outcome.outcome === "replay" || outcome.outcome === "conflict") {
|
|
120
|
+
if (!outcome.run)
|
|
121
|
+
throw new Error(`Run store returned ${outcome.outcome} without a run`);
|
|
122
|
+
return this._existingResponse({ existing: outcome.run, project, revision: validRevision, stage });
|
|
123
|
+
}
|
|
124
|
+
if (outcome.outcome === "in_progress") {
|
|
125
|
+
return { body: { error: "deployment_in_progress", runId: outcome.run?.id }, status: 409 };
|
|
126
|
+
}
|
|
127
|
+
if (outcome.outcome === "reconciliation_required") {
|
|
128
|
+
if (!outcome.run)
|
|
129
|
+
throw new Error("Run store returned reconciliation_required without a run");
|
|
130
|
+
return { body: { error: "deployment_reconciliation_required", runId: outcome.run.id }, status: 409 };
|
|
131
|
+
}
|
|
132
|
+
if (!outcome.run)
|
|
133
|
+
throw new Error("Run store returned created without a run");
|
|
134
|
+
const run = outcome.run;
|
|
135
|
+
await this._audit({ event: "run_requested", payload: { project, revision: validRevision, stage }, runId: run.id });
|
|
136
|
+
const failLaunch = async (/** @type {unknown} */ error) => {
|
|
137
|
+
const safeError = sanitizeErrorPayload(error, this.options.accessTokens);
|
|
138
|
+
try {
|
|
139
|
+
await this.store.markPendingFailed({ error: safeError, finishedAtMs: this.now(), id: run.id, ownerToken: run.ownerToken ?? "" });
|
|
140
|
+
await this._audit({ event: "run_failed", payload: { message: safeError.message, phase: "worker_launch", project, revision: validRevision, stage }, runId: run.id });
|
|
141
|
+
}
|
|
142
|
+
catch (recordingError) {
|
|
143
|
+
this.emitFrameworkError({ context: "rampway-deployment-api-record-launch-failure", error: recordingError });
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
await this.detachedExecution({ runId: run.id, onFailure: failLaunch });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
await failLaunch(error);
|
|
151
|
+
const safeError = sanitizeErrorPayload(error, this.options.accessTokens);
|
|
152
|
+
const failed = (await this.store.findRunById(run.id)) ?? { ...run, error: safeError, finishedAtMs: this.now(), status: "failed" };
|
|
153
|
+
return { body: { run: this._serializeRun(failed) }, status: 503 };
|
|
154
|
+
}
|
|
155
|
+
return { body: { run: this._serializeRun(run) }, status: 202 };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* @param {{authorization: unknown, id: string}} args - Request data.
|
|
159
|
+
* @returns {Promise<{body: Record<string, any>, status: number}>} HTTP response.
|
|
160
|
+
*/
|
|
161
|
+
async show({ authorization, id }) {
|
|
162
|
+
if (!this._authorized(authorization))
|
|
163
|
+
return { body: { error: "unauthorized" }, status: 401 };
|
|
164
|
+
const run = await this.store.findRunById(id);
|
|
165
|
+
if (!run)
|
|
166
|
+
return { body: { error: "not_found" }, status: 404 };
|
|
167
|
+
const target = lookupTarget(this.options.projects, run.project, run.stage);
|
|
168
|
+
if (!target)
|
|
169
|
+
return { body: { error: "not_found" }, status: 404 };
|
|
170
|
+
/** @type {Record<string, any>} */
|
|
171
|
+
const body = { run: this._serializeRun(run) };
|
|
172
|
+
if (this.executor.readStatus) {
|
|
173
|
+
try {
|
|
174
|
+
const current = await this.executor.readStatus({ project: run.project, stage: run.stage, target });
|
|
175
|
+
body.current = sanitizeAdapterValue(current, this.options.accessTokens) ?? null;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const liveStatusError = sanitizeErrorPayload(error, this.options.accessTokens);
|
|
179
|
+
body.live_status = null;
|
|
180
|
+
body.live_status_error = liveStatusError;
|
|
181
|
+
this.emitFrameworkError({ context: "rampway-deployment-api-live-status", error: liveStatusError });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { body, status: 200 };
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* @param {{existing: import('./run-store.js').DeploymentRunRow, project: string, revision: string, stage: string}} args - Replay data.
|
|
188
|
+
* @returns {{body: Record<string, any>, status: number}} Response.
|
|
189
|
+
*/
|
|
190
|
+
_existingResponse({ existing, project, revision, stage }) {
|
|
191
|
+
const same = existing.project === project && existing.stage === stage && existing.revision === revision;
|
|
192
|
+
return same
|
|
193
|
+
? { body: { replayed: true, run: this._serializeRun(existing) }, status: 200 }
|
|
194
|
+
: { body: { error: "idempotency_conflict", runId: existing.id }, status: 409 };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* @param {{claimed?: boolean, run: import('./run-store.js').DeploymentRunRow, target: DeploymentTarget}} args - Execution data.
|
|
198
|
+
* @returns {Promise<void>} Resolves after durable outcome.
|
|
199
|
+
*/
|
|
200
|
+
async _executeRun({ claimed = false, run, target }) {
|
|
201
|
+
if (!run.ownerToken)
|
|
202
|
+
throw new Error(`Deployment run ${run.id} has no owner token`);
|
|
203
|
+
const ownerToken = run.ownerToken;
|
|
204
|
+
registerActiveDeploymentRun(run.id);
|
|
205
|
+
/** @type {ReturnType<typeof setInterval>|null} */
|
|
206
|
+
let heartbeat = null;
|
|
207
|
+
try {
|
|
208
|
+
if (!claimed)
|
|
209
|
+
await this.store.markRunning({ id: run.id, startedAtMs: this.now() });
|
|
210
|
+
heartbeat = setInterval(() => {
|
|
211
|
+
this.store.heartbeat({ heartbeatAtMs: this.now(), id: run.id }).catch(error => {
|
|
212
|
+
this.emitFrameworkError({ context: "rampway-deployment-api-heartbeat", error });
|
|
213
|
+
});
|
|
214
|
+
}, heartbeatIntervalMs(this.options.staleRunTimeoutMs));
|
|
215
|
+
heartbeat.unref();
|
|
216
|
+
await this._audit({ event: "run_started", payload: { project: run.project, revision: run.revision, stage: run.stage }, runId: run.id });
|
|
217
|
+
let report;
|
|
218
|
+
try {
|
|
219
|
+
const reachable = await this.executor.validateRevision({ project: run.project, revision: run.revision, stage: run.stage, target });
|
|
220
|
+
if (!reachable)
|
|
221
|
+
throw new Error(`Revision ${run.revision} is not reachable from approved release branch ${target.releaseBranch}`);
|
|
222
|
+
report = await this.executor.deploy({ project: run.project, revision: run.revision, runId: run.id, stage: run.stage, target });
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
const safeError = sanitizeErrorPayload(error, this.options.accessTokens);
|
|
226
|
+
try {
|
|
227
|
+
await this.store.markFailed({ error: safeError, finishedAtMs: this.now(), id: run.id, ownerToken });
|
|
228
|
+
await this._audit({ event: "run_failed", payload: { message: safeError.message, project: run.project, revision: run.revision, stage: run.stage }, runId: run.id });
|
|
229
|
+
}
|
|
230
|
+
catch (storeError) {
|
|
231
|
+
this.emitFrameworkError({ context: "rampway-deployment-api-record-failure", error: storeError });
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const result = sanitizeAdapterValue(report ?? {}, this.options.accessTokens) ?? {};
|
|
237
|
+
await this.store.markSucceeded({ finishedAtMs: this.now(), id: run.id, ownerToken, result });
|
|
238
|
+
await this._audit({ event: "run_succeeded", payload: { project: run.project, revision: run.revision, stage: run.stage }, runId: run.id });
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
this.emitFrameworkError({ context: "rampway-deployment-api-record-success", error });
|
|
242
|
+
const reconciliationError = { message: "Deployment activation succeeded, but its result could not be persisted; operator reconciliation is required" };
|
|
243
|
+
try {
|
|
244
|
+
await this.store.markReconciliationRequired({ error: reconciliationError, finishedAtMs: this.now(), id: run.id, ownerToken });
|
|
245
|
+
await this._audit({ event: "run_reconciliation_required", payload: { ...reconciliationError, project: run.project, revision: run.revision, stage: run.stage }, runId: run.id });
|
|
246
|
+
}
|
|
247
|
+
catch (persistenceError) {
|
|
248
|
+
this.emitFrameworkError({ context: "rampway-deployment-api-record-reconciliation-required", error: persistenceError });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
if (heartbeat)
|
|
254
|
+
clearInterval(heartbeat);
|
|
255
|
+
unregisterActiveDeploymentRun(run.id);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* @param {{event: string, payload: Record<string, unknown>, runId: string|null}} args - Event.
|
|
260
|
+
* @returns {Promise<void>} Resolves after persistence or reporting.
|
|
261
|
+
*/
|
|
262
|
+
async _audit({ event, payload, runId }) {
|
|
263
|
+
try {
|
|
264
|
+
await this.store.addAuditEvent({ event, payload: sanitizeAdapterValue(payload, this.options.accessTokens) ?? {}, runId });
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
this.emitFrameworkError({ context: "rampway-deployment-api-audit", error });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* @param {import('./run-store.js').DeploymentRunRow} run - Run.
|
|
272
|
+
* @returns {Record<string, any>} Public representation.
|
|
273
|
+
*/
|
|
274
|
+
_serializeRun(run) {
|
|
275
|
+
return {
|
|
276
|
+
error: run.error,
|
|
277
|
+
finishedAtMs: run.finishedAtMs,
|
|
278
|
+
id: run.id,
|
|
279
|
+
idempotencyKey: run.idempotencyKey,
|
|
280
|
+
project: run.project,
|
|
281
|
+
requestedAtMs: run.requestedAtMs,
|
|
282
|
+
result: run.result,
|
|
283
|
+
revision: run.revision,
|
|
284
|
+
stage: run.stage,
|
|
285
|
+
startedAtMs: run.startedAtMs,
|
|
286
|
+
status: run.status
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=control-plane.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control-plane.js","sourceRoot":"","sources":["../../../src/velocious/control-plane.js"],"names":[],"mappings":"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"}
|