rampway 0.5.21 → 0.5.23
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 +28 -6
- package/build/src/commands/runtime-boot.d.ts.map +1 -1
- package/build/src/commands/runtime-boot.js +34 -5
- package/build/src/commands/runtime-boot.js.map +1 -1
- package/build/src/core/deploy-runner.d.ts.map +1 -1
- package/build/src/core/deploy-runner.js +28 -5
- package/build/src/core/deploy-runner.js.map +1 -1
- package/build/src/core/runtime-boot-state.d.ts +8 -2
- package/build/src/core/runtime-boot-state.d.ts.map +1 -1
- package/build/src/core/runtime-boot-state.js +25 -5
- package/build/src/core/runtime-boot-state.js.map +1 -1
- package/build/src/core/runtime-errors.d.ts +23 -0
- package/build/src/core/runtime-errors.d.ts.map +1 -0
- package/build/src/core/runtime-errors.js +27 -0
- package/build/src/core/runtime-errors.js.map +1 -0
- package/build/src/core/runtime-owner-handoff.d.ts +2 -2
- package/build/src/core/runtime-owner-handoff.d.ts.map +1 -1
- package/build/src/core/runtime-owner-handoff.js +1 -1
- package/build/src/runtimes/rollbridge-runtime.d.ts +46 -4
- package/build/src/runtimes/rollbridge-runtime.d.ts.map +1 -1
- package/build/src/runtimes/rollbridge-runtime.js +314 -22
- package/build/src/runtimes/rollbridge-runtime.js.map +1 -1
- package/build/src/transports/local-transport.d.ts +19 -0
- package/build/src/transports/local-transport.d.ts.map +1 -1
- package/build/src/transports/local-transport.js +32 -0
- package/build/src/transports/local-transport.js.map +1 -1
- package/build/src/transports/ssh-transport.d.ts +38 -0
- package/build/src/transports/ssh-transport.d.ts.map +1 -1
- package/build/src/transports/ssh-transport.js +68 -0
- package/build/src/transports/ssh-transport.js.map +1 -1
- package/build/src/types.d.ts +28 -2
- package/build/src/types.d.ts.map +1 -1
- package/docs/config-reference.md +48 -7
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @param {import('#types').Transport} transport
|
|
3
3
|
* @param {import('#types').StageConfig} stage
|
|
4
|
-
* @param {import('#types').
|
|
4
|
+
* @param {import('#types').ReadRuntimeBootState} boot
|
|
5
5
|
* @param {import('#types').LockInfo} lock
|
|
6
6
|
* @returns {Promise<import('#types').RuntimeOwnerHandoff>}
|
|
7
7
|
*/
|
|
8
|
-
export function writeRuntimeOwnerHandoff(transport: import("#types").Transport, stage: import("#types").StageConfig, boot: import("#types").
|
|
8
|
+
export function writeRuntimeOwnerHandoff(transport: import("#types").Transport, stage: import("#types").StageConfig, boot: import("#types").ReadRuntimeBootState, lock: import("#types").LockInfo): Promise<import("#types").RuntimeOwnerHandoff>;
|
|
9
9
|
/**
|
|
10
10
|
* @param {import('#types').Transport} transport
|
|
11
11
|
* @param {import('#types').StageConfig} stage
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-owner-handoff.d.ts","sourceRoot":"","sources":["../../../src/core/runtime-owner-handoff.js"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,oDANW,OAAO,QAAQ,EAAE,SAAS,SAC1B,OAAO,QAAQ,EAAE,WAAW,QAC5B,OAAO,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"runtime-owner-handoff.d.ts","sourceRoot":"","sources":["../../../src/core/runtime-owner-handoff.js"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,oDANW,OAAO,QAAQ,EAAE,SAAS,SAC1B,OAAO,QAAQ,EAAE,WAAW,QAC5B,OAAO,QAAQ,EAAE,oBAAoB,QACrC,OAAO,QAAQ,EAAE,QAAQ,GACvB,OAAO,CAAC,OAAO,QAAQ,EAAE,mBAAmB,CAAC,CAUzD;AAED;;;;GAIG;AACH,mDAJW,OAAO,QAAQ,EAAE,SAAS,SAC1B,OAAO,QAAQ,EAAE,WAAW,GAC1B,OAAO,CAAC,OAAO,QAAQ,EAAE,mBAAmB,GAAC,IAAI,CAAC,CAc9D;AAED;;;;;GAKG;AACH,qDALW,OAAO,QAAQ,EAAE,SAAS,SAC1B,OAAO,QAAQ,EAAE,WAAW,WAC5B,OAAO,QAAQ,EAAE,mBAAmB,GAClC,OAAO,CAAC,IAAI,CAAC,CAKzB"}
|
|
@@ -7,7 +7,7 @@ const DIGEST = /^sha256:[0-9a-f]{64}$/u;
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {import('#types').Transport} transport
|
|
9
9
|
* @param {import('#types').StageConfig} stage
|
|
10
|
-
* @param {import('#types').
|
|
10
|
+
* @param {import('#types').ReadRuntimeBootState} boot
|
|
11
11
|
* @param {import('#types').LockInfo} lock
|
|
12
12
|
* @returns {Promise<import('#types').RuntimeOwnerHandoff>}
|
|
13
13
|
*/
|
|
@@ -123,6 +123,20 @@ export class RollbridgeRuntime {
|
|
|
123
123
|
externalOwnerVerificationRequired?: boolean;
|
|
124
124
|
daemonRuntime?: any;
|
|
125
125
|
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Binds the exact immutable runtime reported by the accepted Rollbridge owner to
|
|
128
|
+
* Rampway's stable snapshot and stores an authenticated manifest copy beside the
|
|
129
|
+
* immutable Rollbridge config material.
|
|
130
|
+
*
|
|
131
|
+
* @param {import('#types').Transport} transport
|
|
132
|
+
* @param {{snapshot: import('#types').RuntimeSnapshot, runtimePath: string, runtimeResult: any}} opts
|
|
133
|
+
* @returns {Promise<import('#types').RuntimeSnapshot>}
|
|
134
|
+
*/
|
|
135
|
+
acceptSnapshot(transport: import("#types").Transport, { snapshot, runtimePath, runtimeResult }: {
|
|
136
|
+
snapshot: import("#types").RuntimeSnapshot;
|
|
137
|
+
runtimePath: string;
|
|
138
|
+
runtimeResult: any;
|
|
139
|
+
}): Promise<import("#types").RuntimeSnapshot>;
|
|
126
140
|
/**
|
|
127
141
|
* Releases an exact stale foreground owner for a serial supervisor through
|
|
128
142
|
* Rollbridge's authenticated owner-retirement contract, then waits for the
|
|
@@ -171,7 +185,7 @@ export class RollbridgeRuntime {
|
|
|
171
185
|
acceptedAt: string;
|
|
172
186
|
}): import("#types").RuntimeBootIdentity | undefined;
|
|
173
187
|
/**
|
|
174
|
-
* @param {{releasePath: string, releaseId: string, revision: string, configPath: string, bootAttestation?: string}} identity
|
|
188
|
+
* @param {{releasePath: string, releaseId: string, revision: string, configPath: string, bootAttestation?: string, snapshot?: import('#types').RuntimeSnapshot}} identity
|
|
175
189
|
* @returns {string}
|
|
176
190
|
*/
|
|
177
191
|
foregroundBootCommand(identity: {
|
|
@@ -180,17 +194,18 @@ export class RollbridgeRuntime {
|
|
|
180
194
|
revision: string;
|
|
181
195
|
configPath: string;
|
|
182
196
|
bootAttestation?: string;
|
|
197
|
+
snapshot?: import("#types").RuntimeSnapshot;
|
|
183
198
|
}): string;
|
|
184
199
|
/**
|
|
185
200
|
* @param {import('#types').Transport} transport
|
|
186
|
-
* @param {import('#types').
|
|
201
|
+
* @param {import('#types').ReadRuntimeBootState} state
|
|
187
202
|
* @returns {Promise<void>}
|
|
188
203
|
*/
|
|
189
|
-
verifyForegroundBootMaterial(transport: import("#types").Transport, state: import("#types").
|
|
204
|
+
verifyForegroundBootMaterial(transport: import("#types").Transport, state: import("#types").ReadRuntimeBootState): Promise<void>;
|
|
190
205
|
/**
|
|
191
206
|
* Starts Rollbridge's foreground daemon contract without supervising it.
|
|
192
207
|
*
|
|
193
|
-
* @param {{releasePath: string, releaseId: string, revision: string, configPath: string, configIdentity: string, bootAttestation?: string, onStarted: () => Promise<void>}} identity
|
|
208
|
+
* @param {{releasePath: string, releaseId: string, revision: string, configPath: string, configIdentity: string, bootAttestation?: string, snapshot: import('#types').RuntimeSnapshot, onStarted: () => Promise<void>}} identity
|
|
194
209
|
* @returns {Promise<{code: number}>}
|
|
195
210
|
*/
|
|
196
211
|
foregroundBoot(identity: {
|
|
@@ -200,6 +215,7 @@ export class RollbridgeRuntime {
|
|
|
200
215
|
configPath: string;
|
|
201
216
|
configIdentity: string;
|
|
202
217
|
bootAttestation?: string;
|
|
218
|
+
snapshot: import("#types").RuntimeSnapshot;
|
|
203
219
|
onStarted: () => Promise<void>;
|
|
204
220
|
}): Promise<{
|
|
205
221
|
code: number;
|
|
@@ -234,6 +250,32 @@ export class RollbridgeRuntime {
|
|
|
234
250
|
runtimePath: string;
|
|
235
251
|
contract?: boolean;
|
|
236
252
|
}): Promise<void>;
|
|
253
|
+
/**
|
|
254
|
+
* Hashes one accepted Rollbridge contract with its exact daemon runtime.
|
|
255
|
+
*
|
|
256
|
+
* @param {import('#types').RuntimeSnapshot} snapshot
|
|
257
|
+
* @param {{format: number, digest: string, path: string, version: string}} daemonRuntime
|
|
258
|
+
* @returns {string}
|
|
259
|
+
*/
|
|
260
|
+
foregroundIdentityDigest(snapshot: import("#types").RuntimeSnapshot, daemonRuntime: {
|
|
261
|
+
format: number;
|
|
262
|
+
digest: string;
|
|
263
|
+
path: string;
|
|
264
|
+
version: string;
|
|
265
|
+
}): string;
|
|
266
|
+
/**
|
|
267
|
+
* Independently reproduces Rollbridge daemon-runtime format 1's content
|
|
268
|
+
* digest without executing any bytes from the accepted runtime.
|
|
269
|
+
*
|
|
270
|
+
* @param {import('#types').Transport} transport
|
|
271
|
+
* @param {{format: number, digest: string, path: string, version: string}} daemonRuntime
|
|
272
|
+
*/
|
|
273
|
+
verifyReportedDaemonRuntime(transport: import("#types").Transport, daemonRuntime: {
|
|
274
|
+
format: number;
|
|
275
|
+
digest: string;
|
|
276
|
+
path: string;
|
|
277
|
+
version: string;
|
|
278
|
+
}): Promise<void>;
|
|
237
279
|
/**
|
|
238
280
|
* @param {import('#types').Transport} transport
|
|
239
281
|
* @param {string} releasePath
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rollbridge-runtime.d.ts","sourceRoot":"","sources":["../../../src/runtimes/rollbridge-runtime.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rollbridge-runtime.d.ts","sourceRoot":"","sources":["../../../src/runtimes/rollbridge-runtime.js"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,10 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { shellQuote } from "../transports/ssh-transport.js";
|
|
4
4
|
import { isSecretKey } from "../core/redaction.js";
|
|
5
|
+
import { RuntimeIncumbentPreservedError } from "../core/runtime-errors.js";
|
|
5
6
|
import { spawnOwnedProcess, withOwnedProcessTree } from "../core/owned-process-tree.js";
|
|
7
|
+
const ROLLBRIDGE_ATOMIC_OWNER_REPLACEMENT_MINIMUM = "0.1.29";
|
|
8
|
+
const ROLLBRIDGE_VERIFIED_DAEMON_RUNTIME_MINIMUM = "0.1.38";
|
|
6
9
|
export class RollbridgeRuntime {
|
|
7
10
|
/** @type {"rollbridge"} */
|
|
8
11
|
static type = "rollbridge";
|
|
@@ -162,8 +165,9 @@ export class RollbridgeRuntime {
|
|
|
162
165
|
const configContent = sourceConfigPath ? await transport.readFile(sourceConfigPath) : undefined;
|
|
163
166
|
const configName = sourceConfigPath ? path.basename(sourceConfigPath) : undefined;
|
|
164
167
|
const packageVersion = await this.packageVersion(transport, releasePath, this.config);
|
|
165
|
-
if (this.config.externalOwner && !
|
|
166
|
-
throw new Error(
|
|
168
|
+
if (this.config.externalOwner && !supportsVerifiedDaemonRuntime(packageVersion)) {
|
|
169
|
+
throw new Error(`Rollbridge external foreground ownership requires Rollbridge ${ROLLBRIDGE_VERIFIED_DAEMON_RUNTIME_MINIMUM} or newer with an exact content-addressed daemon runtime in the candidate release.`);
|
|
170
|
+
}
|
|
167
171
|
const packageIdentity = packageVersion ? `version:${packageVersion}` : `release:${releasePath}`;
|
|
168
172
|
const digest = this.contractDigest({ config: this.config, configContent, configName, packageIdentity });
|
|
169
173
|
const contractDigest = `sha256:${digest}`;
|
|
@@ -194,6 +198,7 @@ export class RollbridgeRuntime {
|
|
|
194
198
|
schemaVersion: 1,
|
|
195
199
|
type: "rollbridge",
|
|
196
200
|
contractDigest,
|
|
201
|
+
command: this.config.command,
|
|
197
202
|
materialDigest: materialDigest ? `sha256:${materialDigest}` : undefined,
|
|
198
203
|
packageDir: this.config.packageDir,
|
|
199
204
|
configPath,
|
|
@@ -217,6 +222,9 @@ export class RollbridgeRuntime {
|
|
|
217
222
|
* @returns {Promise<{status: string, externalOwnerVerificationRequired?: boolean, daemonRuntime?: any}>}
|
|
218
223
|
*/
|
|
219
224
|
async deploy(transport, { releasePath, releaseId, revision, currentPath, targetSnapshot, currentSnapshot, deferCommit = false }) {
|
|
225
|
+
if (this.config.externalOwner && targetSnapshot && !supportsVerifiedDaemonRuntime(targetSnapshot.packageVersion)) {
|
|
226
|
+
throw new Error(`Rollbridge external foreground ownership requires Rollbridge ${ROLLBRIDGE_VERIFIED_DAEMON_RUNTIME_MINIMUM} or newer with an exact content-addressed daemon runtime before deployment.`);
|
|
227
|
+
}
|
|
220
228
|
if (this.config.externalOwner && targetSnapshot && currentPath && currentSnapshot &&
|
|
221
229
|
currentSnapshot.contractDigest !== targetSnapshot.contractDigest && !supportsExternalOwner(currentSnapshot.packageVersion)) {
|
|
222
230
|
throw unsupportedExternalOwnerUpgrade(currentSnapshot.packageVersion);
|
|
@@ -229,10 +237,12 @@ export class RollbridgeRuntime {
|
|
|
229
237
|
throw new Error(`Rollbridge validation failed: ${ /** @type {Error} */(error).message}`);
|
|
230
238
|
}
|
|
231
239
|
let releaseVersion;
|
|
240
|
+
const atomicOwnerReplacement = supportsAtomicOwnerReplacement(targetSnapshot?.packageVersion);
|
|
241
|
+
const atomicIncompatibleReplacement = atomicOwnerReplacement && Boolean(currentPath && currentSnapshot?.contractDigest !== targetSnapshot?.contractDigest);
|
|
232
242
|
const externalOwnerVerificationRequired = Boolean(this.config.externalOwner);
|
|
233
243
|
if (targetSnapshot) {
|
|
234
244
|
releaseVersion = targetSnapshot.packageVersion;
|
|
235
|
-
if (currentPath && currentSnapshot?.contractDigest !== targetSnapshot.contractDigest) {
|
|
245
|
+
if (!atomicOwnerReplacement && currentPath && currentSnapshot?.contractDigest !== targetSnapshot.contractDigest) {
|
|
236
246
|
await this.shutdownChangedVersion(transport, { currentPath, snapshot: currentSnapshot ?? targetSnapshot });
|
|
237
247
|
}
|
|
238
248
|
}
|
|
@@ -244,7 +254,10 @@ export class RollbridgeRuntime {
|
|
|
244
254
|
await transport.run(deployCommand, this.runOptions(releasePath, targetConfig));
|
|
245
255
|
}
|
|
246
256
|
catch (error) {
|
|
247
|
-
if (
|
|
257
|
+
if (atomicIncompatibleReplacement) {
|
|
258
|
+
throw new RuntimeIncumbentPreservedError(`Rollbridge deploy failed: ${ /** @type {Error} */(error).message}`, { cause: error });
|
|
259
|
+
}
|
|
260
|
+
if (atomicOwnerReplacement || !currentPath || !currentSnapshot || !isLegacyRuntimeMismatch(/** @type {Error} */ (error))) {
|
|
248
261
|
throw new Error(`Rollbridge deploy failed: ${ /** @type {Error} */(error).message}`);
|
|
249
262
|
}
|
|
250
263
|
try {
|
|
@@ -267,6 +280,62 @@ export class RollbridgeRuntime {
|
|
|
267
280
|
const status = externalOwnerVerificationRequired ? parseStatus(acceptedStatus.output) : {};
|
|
268
281
|
return { status: "deployed", ...(externalOwnerVerificationRequired ? { externalOwnerVerificationRequired: true, daemonRuntime: status.daemonRuntime } : {}) };
|
|
269
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Binds the exact immutable runtime reported by the accepted Rollbridge owner to
|
|
285
|
+
* Rampway's stable snapshot and stores an authenticated manifest copy beside the
|
|
286
|
+
* immutable Rollbridge config material.
|
|
287
|
+
*
|
|
288
|
+
* @param {import('#types').Transport} transport
|
|
289
|
+
* @param {{snapshot: import('#types').RuntimeSnapshot, runtimePath: string, runtimeResult: any}} opts
|
|
290
|
+
* @returns {Promise<import('#types').RuntimeSnapshot>}
|
|
291
|
+
*/
|
|
292
|
+
async acceptSnapshot(transport, { snapshot, runtimePath, runtimeResult }) {
|
|
293
|
+
if (!this.config.externalOwner)
|
|
294
|
+
return snapshot;
|
|
295
|
+
const daemonRuntime = parseDaemonRuntime(runtimeResult?.daemonRuntime);
|
|
296
|
+
if (daemonRuntime.version !== snapshot.packageVersion)
|
|
297
|
+
throw new Error("Rollbridge daemon runtime version does not match the accepted package identity.");
|
|
298
|
+
if (snapshot.schemaVersion === 2) {
|
|
299
|
+
const acceptedRuntime = parseDaemonRuntime(snapshot.daemonRuntime);
|
|
300
|
+
if (canonicalJson(daemonRuntime) !== canonicalJson(acceptedRuntime)) {
|
|
301
|
+
throw new Error("Rollbridge activated a daemon runtime that does not match the retained accepted runtime identity.");
|
|
302
|
+
}
|
|
303
|
+
await this.verifySnapshot(transport, { snapshot, runtimePath });
|
|
304
|
+
return snapshot;
|
|
305
|
+
}
|
|
306
|
+
const manifestRoot = path.join(runtimePath, "rollbridge-daemon", daemonRuntime.digest);
|
|
307
|
+
const manifestPath = path.join(manifestRoot, "runtime.json");
|
|
308
|
+
const manifestContent = `${JSON.stringify(daemonRuntime, null, 2)}\n`;
|
|
309
|
+
await this.verifyReportedDaemonRuntime(transport, daemonRuntime);
|
|
310
|
+
if (await transport.exists(manifestPath)) {
|
|
311
|
+
if (await transport.readFile(manifestPath) !== manifestContent)
|
|
312
|
+
throw new Error(`Stored Rollbridge daemon runtime manifest does not match its accepted identity: ${manifestPath}`);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
if (await transport.exists(manifestRoot))
|
|
316
|
+
throw new Error(`Stored Rollbridge daemon runtime manifest is incomplete: ${manifestRoot}`);
|
|
317
|
+
const pendingRoot = path.join(runtimePath, `.rollbridge-daemon-${daemonRuntime.digest}-${process.pid}-${Date.now()}`);
|
|
318
|
+
await transport.mkdirp(pendingRoot);
|
|
319
|
+
try {
|
|
320
|
+
await transport.writeFile(path.join(pendingRoot, "runtime.json"), manifestContent);
|
|
321
|
+
await transport.mkdirp(path.dirname(manifestRoot));
|
|
322
|
+
await transport.rename(pendingRoot, manifestRoot);
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
if (await transport.exists(pendingRoot))
|
|
326
|
+
await transport.rm(pendingRoot);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const accepted = {
|
|
330
|
+
...snapshot,
|
|
331
|
+
schemaVersion: 2,
|
|
332
|
+
daemonRuntime,
|
|
333
|
+
daemonRuntimeManifestPath: manifestPath,
|
|
334
|
+
foregroundIdentityDigest: this.foregroundIdentityDigest(snapshot, daemonRuntime)
|
|
335
|
+
};
|
|
336
|
+
await this.verifySnapshot(transport, { snapshot: accepted, runtimePath });
|
|
337
|
+
return accepted;
|
|
338
|
+
}
|
|
270
339
|
/**
|
|
271
340
|
* Releases an exact stale foreground owner for a serial supervisor through
|
|
272
341
|
* Rollbridge's authenticated owner-retirement contract, then waits for the
|
|
@@ -341,21 +410,25 @@ export class RollbridgeRuntime {
|
|
|
341
410
|
if (!snapshot.configPath || !snapshot.materialDigest || !supportsForegroundBoot(snapshot.packageVersion))
|
|
342
411
|
return undefined;
|
|
343
412
|
this.validateSnapshot(snapshot);
|
|
344
|
-
|
|
413
|
+
const snapshotIdentity = snapshot.schemaVersion === 2 ? snapshot.foregroundIdentityDigest : snapshot.contractDigest;
|
|
414
|
+
return { runtimeType: "rollbridge", releaseId, revision, releasePath: path.resolve(releasePath), configPath: path.resolve(/** @type {string} */ (snapshot.configPath)), configIdentity: /** @type {string} */ (snapshot.materialDigest), snapshotIdentity: /** @type {string} */ (snapshotIdentity), acceptedAt };
|
|
345
415
|
}
|
|
346
416
|
/**
|
|
347
|
-
* @param {{releasePath: string, releaseId: string, revision: string, configPath: string, bootAttestation?: string}} identity
|
|
417
|
+
* @param {{releasePath: string, releaseId: string, revision: string, configPath: string, bootAttestation?: string, snapshot?: import('#types').RuntimeSnapshot}} identity
|
|
348
418
|
* @returns {string}
|
|
349
419
|
*/
|
|
350
420
|
foregroundBootCommand(identity) {
|
|
351
421
|
const args = ["daemon", "--config", identity.configPath, "--release-path", identity.releasePath, "--release-id", identity.releaseId, "--revision", identity.revision];
|
|
352
422
|
if (this.config.externalOwner && identity.bootAttestation)
|
|
353
423
|
args.push("--boot-attestation", identity.bootAttestation);
|
|
354
|
-
|
|
424
|
+
const command = identity.snapshot?.schemaVersion === 2 && identity.snapshot.daemonRuntime
|
|
425
|
+
? shellQuote(path.join(identity.snapshot.daemonRuntime.path, "bin", "rollbridge"))
|
|
426
|
+
: this.config.command;
|
|
427
|
+
return `${command} ${args.map(value => shellQuote(value)).join(" ")}`;
|
|
355
428
|
}
|
|
356
429
|
/**
|
|
357
430
|
* @param {import('#types').Transport} transport
|
|
358
|
-
* @param {import('#types').
|
|
431
|
+
* @param {import('#types').ReadRuntimeBootState} state
|
|
359
432
|
* @returns {Promise<void>}
|
|
360
433
|
*/
|
|
361
434
|
async verifyForegroundBootMaterial(transport, state) {
|
|
@@ -367,13 +440,21 @@ export class RollbridgeRuntime {
|
|
|
367
440
|
/**
|
|
368
441
|
* Starts Rollbridge's foreground daemon contract without supervising it.
|
|
369
442
|
*
|
|
370
|
-
* @param {{releasePath: string, releaseId: string, revision: string, configPath: string, configIdentity: string, bootAttestation?: string, onStarted: () => Promise<void>}} identity
|
|
443
|
+
* @param {{releasePath: string, releaseId: string, revision: string, configPath: string, configIdentity: string, bootAttestation?: string, snapshot: import('#types').RuntimeSnapshot, onStarted: () => Promise<void>}} identity
|
|
371
444
|
* @returns {Promise<{code: number}>}
|
|
372
445
|
*/
|
|
373
446
|
async foregroundBoot(identity) {
|
|
374
447
|
return await withOwnedProcessTree(async () => {
|
|
375
448
|
const command = this.foregroundBootCommand(identity);
|
|
376
|
-
const
|
|
449
|
+
const runtimeManifest = identity.snapshot?.schemaVersion === 2 && identity.snapshot.daemonRuntime
|
|
450
|
+
? path.join(identity.snapshot.daemonRuntime.path, "runtime.json")
|
|
451
|
+
: undefined;
|
|
452
|
+
const child = spawnOwnedProcess(`exec ${command}`, {
|
|
453
|
+
cwd: identity.releasePath,
|
|
454
|
+
env: { ...process.env, ...this.config.env, ...(runtimeManifest ? { ROLLBRIDGE_DAEMON_RUNTIME_MANIFEST: runtimeManifest } : {}) },
|
|
455
|
+
shell: true,
|
|
456
|
+
stdio: "inherit"
|
|
457
|
+
});
|
|
377
458
|
const signals = /** @type {const} */ (["SIGINT", "SIGTERM", "SIGHUP"]);
|
|
378
459
|
const handlers = new Map(signals.map(signal => [signal, () => child.kill(signal)]));
|
|
379
460
|
for (const [signal, handler] of handlers)
|
|
@@ -430,6 +511,26 @@ export class RollbridgeRuntime {
|
|
|
430
511
|
if (!isReleaseRelativePath(snapshot.packageDir)) {
|
|
431
512
|
throw new Error("Stored Rollbridge runtime state has an unsafe package directory.");
|
|
432
513
|
}
|
|
514
|
+
if (snapshot.command !== undefined && (typeof snapshot.command !== "string" || !snapshot.command.trim() || snapshot.command.includes("\0"))) {
|
|
515
|
+
throw new Error("Stored Rollbridge runtime state has an invalid accepted command.");
|
|
516
|
+
}
|
|
517
|
+
if (snapshot.schemaVersion === 2) {
|
|
518
|
+
const daemonRuntime = parseDaemonRuntime(snapshot.daemonRuntime);
|
|
519
|
+
if (daemonRuntime.version !== snapshot.packageVersion)
|
|
520
|
+
throw new Error("Stored Rollbridge daemon runtime version does not match its accepted package identity.");
|
|
521
|
+
if (typeof snapshot.daemonRuntimeManifestPath !== "string" || !path.isAbsolute(snapshot.daemonRuntimeManifestPath) || path.normalize(snapshot.daemonRuntimeManifestPath) !== snapshot.daemonRuntimeManifestPath) {
|
|
522
|
+
throw new Error("Stored Rollbridge runtime state has an invalid daemon runtime manifest path.");
|
|
523
|
+
}
|
|
524
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(snapshot.foregroundIdentityDigest ?? "")) {
|
|
525
|
+
throw new Error("Stored Rollbridge runtime state has an invalid foreground runtime identity digest.");
|
|
526
|
+
}
|
|
527
|
+
if (snapshot.foregroundIdentityDigest !== this.foregroundIdentityDigest(snapshot, /** @type {any} */ (snapshot.daemonRuntime))) {
|
|
528
|
+
throw new Error("Stored Rollbridge foreground runtime identity no longer matches its known-good digest.");
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
else if (snapshot.schemaVersion !== undefined && snapshot.schemaVersion !== 1) {
|
|
532
|
+
throw new Error("Stored Rollbridge runtime state has an unsupported schema version.");
|
|
533
|
+
}
|
|
433
534
|
if (snapshot.rollbackCompatibility !== undefined && (typeof snapshot.rollbackCompatibility !== "string" || snapshot.rollbackCompatibility.trim().length === 0 || snapshot.rollbackCompatibility.includes("\0"))) {
|
|
434
535
|
throw new Error("Stored Rollbridge runtime state has an invalid rollback compatibility identifier.");
|
|
435
536
|
}
|
|
@@ -447,8 +548,9 @@ export class RollbridgeRuntime {
|
|
|
447
548
|
* @returns {void}
|
|
448
549
|
*/
|
|
449
550
|
validateRollbackTarget(snapshot) {
|
|
450
|
-
if (this.config.externalOwner && !
|
|
451
|
-
throw new Error(
|
|
551
|
+
if (this.config.externalOwner && !supportsVerifiedDaemonRuntime(snapshot.packageVersion)) {
|
|
552
|
+
throw new Error(`Rollbridge external foreground ownership requires Rollbridge ${ROLLBRIDGE_VERIFIED_DAEMON_RUNTIME_MINIMUM} or newer with an exact content-addressed daemon runtime in the retained rollback release.`);
|
|
553
|
+
}
|
|
452
554
|
if (this.config.externalOwner && (typeof snapshot.socketPath !== "string" || snapshot.socketPath.trim().length === 0 || snapshot.socketPath.includes("\0")))
|
|
453
555
|
throw new Error("Rollbridge external foreground ownership requires a valid control socket path in the retained rollback release; redeploy the target release once with the current Rampway so its runtime snapshot records runtime.socketPath before rolling back to it.");
|
|
454
556
|
if (snapshot.rollbackCompatibility !== this.config.rollbackCompatibility) {
|
|
@@ -505,6 +607,24 @@ export class RollbridgeRuntime {
|
|
|
505
607
|
if (this.materialDigest(path.basename(configPath), configContent) !== materialDigest) {
|
|
506
608
|
throw new Error("Stored Rollbridge config material no longer matches its content digest.");
|
|
507
609
|
}
|
|
610
|
+
if (snapshot.schemaVersion === 2) {
|
|
611
|
+
const manifestPath = /** @type {string} */ (snapshot.daemonRuntimeManifestPath);
|
|
612
|
+
const daemonRuntime = parseDaemonRuntime(snapshot.daemonRuntime);
|
|
613
|
+
const trustedDaemonRoot = path.join(runtimePath, "rollbridge-daemon");
|
|
614
|
+
if (path.resolve(manifestPath) !== path.resolve(trustedDaemonRoot, daemonRuntime.digest, "runtime.json")) {
|
|
615
|
+
throw new Error("Stored Rollbridge daemon runtime manifest path is outside its known-good runtime directory.");
|
|
616
|
+
}
|
|
617
|
+
const trustedDaemonRealPath = await transport.realpath(trustedDaemonRoot);
|
|
618
|
+
const trustedIdentityRealPath = await transport.realpath(path.dirname(manifestPath));
|
|
619
|
+
const manifestRealPath = await transport.realpath(manifestPath);
|
|
620
|
+
if (path.relative(trustedDaemonRealPath, trustedIdentityRealPath) !== daemonRuntime.digest || path.dirname(manifestRealPath) !== trustedIdentityRealPath) {
|
|
621
|
+
throw new Error("Stored Rollbridge daemon runtime manifest resolves outside its known-good runtime directory.");
|
|
622
|
+
}
|
|
623
|
+
if (await transport.readFile(manifestPath) !== `${JSON.stringify(daemonRuntime, null, 2)}\n`) {
|
|
624
|
+
throw new Error("Stored Rollbridge daemon runtime manifest no longer matches its accepted identity.");
|
|
625
|
+
}
|
|
626
|
+
await this.verifyReportedDaemonRuntime(transport, daemonRuntime);
|
|
627
|
+
}
|
|
508
628
|
if (!contract)
|
|
509
629
|
return;
|
|
510
630
|
const config = this.configForSnapshot(snapshot);
|
|
@@ -517,6 +637,54 @@ export class RollbridgeRuntime {
|
|
|
517
637
|
if (actualDigest !== digest)
|
|
518
638
|
throw new Error("Stored Rollbridge runtime contract no longer matches its known-good digest.");
|
|
519
639
|
}
|
|
640
|
+
/**
|
|
641
|
+
* Hashes one accepted Rollbridge contract with its exact daemon runtime.
|
|
642
|
+
*
|
|
643
|
+
* @param {import('#types').RuntimeSnapshot} snapshot
|
|
644
|
+
* @param {{format: number, digest: string, path: string, version: string}} daemonRuntime
|
|
645
|
+
* @returns {string}
|
|
646
|
+
*/
|
|
647
|
+
foregroundIdentityDigest(snapshot, daemonRuntime) {
|
|
648
|
+
return `sha256:${createHash("sha256").update(canonicalJson({ contractDigest: snapshot.contractDigest, daemonRuntime })).digest("hex")}`;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Independently reproduces Rollbridge daemon-runtime format 1's content
|
|
652
|
+
* digest without executing any bytes from the accepted runtime.
|
|
653
|
+
*
|
|
654
|
+
* @param {import('#types').Transport} transport
|
|
655
|
+
* @param {{format: number, digest: string, path: string, version: string}} daemonRuntime
|
|
656
|
+
*/
|
|
657
|
+
async verifyReportedDaemonRuntime(transport, daemonRuntime) {
|
|
658
|
+
const runtimeRealPath = await transport.realpath(daemonRuntime.path);
|
|
659
|
+
if (runtimeRealPath !== daemonRuntime.path)
|
|
660
|
+
throw new Error("Accepted Rollbridge daemon runtime resolves through a mutable or redirected path.");
|
|
661
|
+
const manifestPath = path.join(daemonRuntime.path, "runtime.json");
|
|
662
|
+
const binaryPath = path.join(daemonRuntime.path, "bin", "rollbridge");
|
|
663
|
+
if (!await transport.exists(manifestPath) || await transport.isDirectory(manifestPath) || !await transport.exists(binaryPath) || await transport.isDirectory(binaryPath)) {
|
|
664
|
+
throw new Error("Accepted Rollbridge daemon runtime material is incomplete.");
|
|
665
|
+
}
|
|
666
|
+
let manifest;
|
|
667
|
+
try {
|
|
668
|
+
manifest = JSON.parse(await transport.readFile(manifestPath));
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
throw new Error(`Accepted Rollbridge daemon runtime manifest is malformed: ${ /** @type {Error} */(error).message}`);
|
|
672
|
+
}
|
|
673
|
+
if (canonicalJson(manifest) !== canonicalJson(daemonRuntime))
|
|
674
|
+
throw new Error("Accepted Rollbridge daemon runtime manifest does not match the reported identity.");
|
|
675
|
+
let packageMetadata;
|
|
676
|
+
try {
|
|
677
|
+
packageMetadata = JSON.parse(await transport.readFile(path.join(daemonRuntime.path, "package.json")));
|
|
678
|
+
}
|
|
679
|
+
catch (error) {
|
|
680
|
+
throw new Error(`Accepted Rollbridge daemon runtime package metadata is malformed: ${ /** @type {Error} */(error).message}`);
|
|
681
|
+
}
|
|
682
|
+
if (packageMetadata?.version !== daemonRuntime.version)
|
|
683
|
+
throw new Error("Accepted Rollbridge daemon runtime package version does not match the reported identity.");
|
|
684
|
+
const actualDigest = await rollbridgeDaemonRuntimeDigest(transport, daemonRuntime.path);
|
|
685
|
+
if (actualDigest !== daemonRuntime.digest)
|
|
686
|
+
throw new Error("Accepted Rollbridge daemon runtime bytes no longer match their content digest.");
|
|
687
|
+
}
|
|
520
688
|
/**
|
|
521
689
|
* @param {import('#types').Transport} transport
|
|
522
690
|
* @param {string} releasePath
|
|
@@ -732,7 +900,7 @@ export class RollbridgeRuntime {
|
|
|
732
900
|
}
|
|
733
901
|
return {
|
|
734
902
|
type: "rollbridge",
|
|
735
|
-
command: this.config.command,
|
|
903
|
+
command: snapshot.command ?? this.config.command,
|
|
736
904
|
packageDir: snapshot.packageDir,
|
|
737
905
|
config: snapshot.configPath,
|
|
738
906
|
daemonLogPath: snapshot.daemonLogPath,
|
|
@@ -817,22 +985,65 @@ function isLegacyRuntimeMismatch(error) {
|
|
|
817
985
|
* @returns {boolean}
|
|
818
986
|
*/
|
|
819
987
|
function supportsForegroundBoot(version) {
|
|
820
|
-
|
|
821
|
-
if (!match)
|
|
822
|
-
return false;
|
|
823
|
-
const [major, minor, patch] = match.slice(1).map(Number);
|
|
824
|
-
return major > 0 || minor > 1 || (minor === 1 && patch >= 17);
|
|
988
|
+
return rollbridgeVersionAtLeast(version, "0.1.17");
|
|
825
989
|
}
|
|
826
990
|
/**
|
|
827
991
|
* @param {string|undefined} version
|
|
828
992
|
* @returns {boolean}
|
|
829
993
|
*/
|
|
830
994
|
function supportsExternalOwner(version) {
|
|
831
|
-
|
|
832
|
-
|
|
995
|
+
return rollbridgeVersionAtLeast(version, "0.1.21");
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Exact foreground recovery requires the Rollbridge contract that reports and
|
|
999
|
+
* restores the guardian's committed content-addressed runtime authority.
|
|
1000
|
+
* Earlier external owners can legitimately report a release package path.
|
|
1001
|
+
*
|
|
1002
|
+
* @param {string|undefined} version
|
|
1003
|
+
* @returns {boolean}
|
|
1004
|
+
*/
|
|
1005
|
+
function supportsVerifiedDaemonRuntime(version) {
|
|
1006
|
+
return rollbridgeVersionAtLeast(version, ROLLBRIDGE_VERIFIED_DAEMON_RUNTIME_MINIMUM);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Rollbridge 0.1.29 introduced authenticated, atomic replacement for an
|
|
1010
|
+
* incompatible config, socket, package, or runtime owner. Capability comes
|
|
1011
|
+
* only from the prepared candidate snapshot; the incumbent daemon cannot
|
|
1012
|
+
* safely assert what replacement transaction the candidate implements.
|
|
1013
|
+
*
|
|
1014
|
+
* @param {string|undefined} version
|
|
1015
|
+
* @returns {boolean}
|
|
1016
|
+
*/
|
|
1017
|
+
function supportsAtomicOwnerReplacement(version) {
|
|
1018
|
+
return rollbridgeVersionAtLeast(version, ROLLBRIDGE_ATOMIC_OWNER_REPLACEMENT_MINIMUM);
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Compares strict release versions using the adapter's established
|
|
1022
|
+
* three-component Rollbridge version format. Prereleases and malformed values
|
|
1023
|
+
* fail closed instead of being granted a capability.
|
|
1024
|
+
*
|
|
1025
|
+
* @param {string|undefined} version
|
|
1026
|
+
* @param {string} minimum
|
|
1027
|
+
* @returns {boolean}
|
|
1028
|
+
*/
|
|
1029
|
+
function rollbridgeVersionAtLeast(version, minimum) {
|
|
1030
|
+
const candidate = parseRollbridgeVersion(version);
|
|
1031
|
+
const boundary = parseRollbridgeVersion(minimum);
|
|
1032
|
+
if (!candidate || !boundary)
|
|
833
1033
|
return false;
|
|
834
|
-
|
|
835
|
-
|
|
1034
|
+
for (let index = 0; index < candidate.length; index += 1) {
|
|
1035
|
+
if (candidate[index] !== boundary[index])
|
|
1036
|
+
return candidate[index] > boundary[index];
|
|
1037
|
+
}
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* @param {string|undefined} version
|
|
1042
|
+
* @returns {[number, number, number]|undefined}
|
|
1043
|
+
*/
|
|
1044
|
+
function parseRollbridgeVersion(version) {
|
|
1045
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/u.exec(version ?? "");
|
|
1046
|
+
return match ? /** @type {[number, number, number]} */ (match.slice(1).map(Number)) : undefined;
|
|
836
1047
|
}
|
|
837
1048
|
/**
|
|
838
1049
|
* @param {string|undefined} output
|
|
@@ -934,6 +1145,87 @@ function canonicalJson(value) {
|
|
|
934
1145
|
}
|
|
935
1146
|
return JSON.stringify(value);
|
|
936
1147
|
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Reproduces Rollbridge daemon-runtime format 1: sorted root-relative file
|
|
1150
|
+
* paths and raw bytes, each separated by NUL, with runtime.json excluded.
|
|
1151
|
+
* Symbolic links and every non-file/non-directory entry fail closed exactly as
|
|
1152
|
+
* they do while Rollbridge materializes its immutable runtime.
|
|
1153
|
+
*
|
|
1154
|
+
* @param {import('#types').Transport} transport
|
|
1155
|
+
* @param {string} rootPath
|
|
1156
|
+
* @returns {Promise<string>}
|
|
1157
|
+
*/
|
|
1158
|
+
async function rollbridgeDaemonRuntimeDigest(transport, rootPath) {
|
|
1159
|
+
if (typeof transport.stat !== "function" || typeof transport.readFileBuffer !== "function" || typeof transport.listDirectory !== "function") {
|
|
1160
|
+
throw new Error("The selected transport cannot independently verify Rollbridge daemon runtime bytes.");
|
|
1161
|
+
}
|
|
1162
|
+
const stat = transport.stat;
|
|
1163
|
+
const readFileBuffer = transport.readFileBuffer;
|
|
1164
|
+
const listDirectory = transport.listDirectory;
|
|
1165
|
+
/** @type {string[]} */
|
|
1166
|
+
const files = [];
|
|
1167
|
+
/** @param {string} relativePath */
|
|
1168
|
+
const walk = async (relativePath) => {
|
|
1169
|
+
const directoryPath = path.join(rootPath, relativePath);
|
|
1170
|
+
const directory = await stat(directoryPath);
|
|
1171
|
+
if (!directory || directory.isSymbolicLink?.() || !directory.isDirectory?.()) {
|
|
1172
|
+
throw new Error(`Unsupported entry in accepted Rollbridge daemon runtime: ${directoryPath}`);
|
|
1173
|
+
}
|
|
1174
|
+
const entries = await listDirectory(directoryPath);
|
|
1175
|
+
const uniqueEntries = new Set(entries);
|
|
1176
|
+
if (uniqueEntries.size !== entries.length)
|
|
1177
|
+
throw new Error(`Duplicate entry in accepted Rollbridge daemon runtime: ${directoryPath}`);
|
|
1178
|
+
for (const entry of entries.sort((left, right) => left.localeCompare(right))) {
|
|
1179
|
+
if (!entry || entry === "." || entry === ".." || entry.includes("\0") || path.basename(entry) !== entry) {
|
|
1180
|
+
throw new Error(`Invalid entry in accepted Rollbridge daemon runtime: ${entry}`);
|
|
1181
|
+
}
|
|
1182
|
+
const childRelativePath = path.join(relativePath, entry);
|
|
1183
|
+
const childPath = path.join(rootPath, childRelativePath);
|
|
1184
|
+
const child = await stat(childPath);
|
|
1185
|
+
if (!child || child.isSymbolicLink?.())
|
|
1186
|
+
throw new Error(`Unsupported entry in accepted Rollbridge daemon runtime: ${childPath}`);
|
|
1187
|
+
if (child.isDirectory?.())
|
|
1188
|
+
await walk(childRelativePath);
|
|
1189
|
+
else if (child.isFile?.())
|
|
1190
|
+
files.push(childRelativePath);
|
|
1191
|
+
else
|
|
1192
|
+
throw new Error(`Unsupported entry in accepted Rollbridge daemon runtime: ${childPath}`);
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
await walk("");
|
|
1196
|
+
const hash = createHash("sha256");
|
|
1197
|
+
for (const relativePath of files) {
|
|
1198
|
+
if (relativePath === "runtime.json")
|
|
1199
|
+
continue;
|
|
1200
|
+
const content = await readFileBuffer(path.join(rootPath, relativePath));
|
|
1201
|
+
if (!Buffer.isBuffer(content))
|
|
1202
|
+
throw new Error(`Transport returned non-byte content for accepted Rollbridge daemon runtime: ${relativePath}`);
|
|
1203
|
+
hash.update(relativePath);
|
|
1204
|
+
hash.update("\0");
|
|
1205
|
+
hash.update(content);
|
|
1206
|
+
hash.update("\0");
|
|
1207
|
+
}
|
|
1208
|
+
return hash.digest("hex");
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* @param {unknown} value
|
|
1212
|
+
* @returns {{format: number, digest: string, path: string, version: string}}
|
|
1213
|
+
*/
|
|
1214
|
+
function parseDaemonRuntime(value) {
|
|
1215
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1216
|
+
throw new Error("Accepted Rollbridge owner did not report a valid immutable daemon runtime identity.");
|
|
1217
|
+
const runtime = /** @type {Record<string, unknown>} */ (value);
|
|
1218
|
+
const keys = Object.keys(runtime).sort();
|
|
1219
|
+
if (JSON.stringify(keys) !== JSON.stringify(["digest", "format", "path", "version"]))
|
|
1220
|
+
throw new Error("Accepted Rollbridge daemon runtime identity contains unsupported material.");
|
|
1221
|
+
if (runtime.format !== 1 || typeof runtime.digest !== "string" || !/^[0-9a-f]{64}$/u.test(runtime.digest) || typeof runtime.version !== "string" || !supportsForegroundBoot(runtime.version)) {
|
|
1222
|
+
throw new Error("Accepted Rollbridge owner reported a malformed immutable daemon runtime identity.");
|
|
1223
|
+
}
|
|
1224
|
+
if (typeof runtime.path !== "string" || !path.isAbsolute(runtime.path) || path.normalize(runtime.path) !== runtime.path || path.basename(runtime.path) !== runtime.digest) {
|
|
1225
|
+
throw new Error("Accepted Rollbridge daemon runtime path is not its normalized content-addressed identity.");
|
|
1226
|
+
}
|
|
1227
|
+
return { format: runtime.format, digest: runtime.digest, path: runtime.path, version: runtime.version };
|
|
1228
|
+
}
|
|
937
1229
|
/**
|
|
938
1230
|
* @param {unknown} configuredPath
|
|
939
1231
|
* @returns {configuredPath is string}
|