@volter/world-runtime 2.0.33 → 2.0.35
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/dist/src/runtime.js +37 -7
- package/dist/src/served-world.js +9 -0
- package/package.json +1 -1
- package/src/runtime.ts +22 -5
- package/src/served-world.ts +8 -0
package/dist/src/runtime.js
CHANGED
|
@@ -1585,8 +1585,16 @@ export async function upWorld(configId, options = {}, abort) {
|
|
|
1585
1585
|
});
|
|
1586
1586
|
}
|
|
1587
1587
|
catch (error) {
|
|
1588
|
-
|
|
1589
|
-
|
|
1588
|
+
// the failure that stopped the boot leads: a release that also fails is added to it, never put in its place
|
|
1589
|
+
if (resourcesClaimed) {
|
|
1590
|
+
try {
|
|
1591
|
+
releaseWorldLifecycle(root, name);
|
|
1592
|
+
}
|
|
1593
|
+
catch (release) {
|
|
1594
|
+
if (error instanceof Error)
|
|
1595
|
+
error.message += `; releasing the World's claim failed too: ${release instanceof Error ? release.message : String(release)}`;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1590
1598
|
throw error;
|
|
1591
1599
|
}
|
|
1592
1600
|
// From here on this call owns the claim; the finally below releases it however the boot
|
|
@@ -1785,7 +1793,7 @@ export async function upWorld(configId, options = {}, abort) {
|
|
|
1785
1793
|
if (surviving.length)
|
|
1786
1794
|
rollbackErrors.push('owned processes remain alive or uncertain');
|
|
1787
1795
|
if (error instanceof CommandRetirementError)
|
|
1788
|
-
rollbackErrors.push(error.message);
|
|
1796
|
+
rollbackErrors.push(error.retirement ?? error.message);
|
|
1789
1797
|
if (rollbackErrors.length > 0) {
|
|
1790
1798
|
cleanupUncertain = true;
|
|
1791
1799
|
try {
|
|
@@ -3475,7 +3483,10 @@ function startProxyDaemonOwned(name, root, caCertPath, envFile, boot, caller = p
|
|
|
3475
3483
|
}
|
|
3476
3484
|
}
|
|
3477
3485
|
catch (error) {
|
|
3478
|
-
|
|
3486
|
+
try {
|
|
3487
|
+
rmSync(marker, { force: true });
|
|
3488
|
+
}
|
|
3489
|
+
catch { /* the write's failure is the one to report */ } // partial pre-spawn writes own no child
|
|
3479
3490
|
throw error;
|
|
3480
3491
|
}
|
|
3481
3492
|
let child;
|
|
@@ -3494,7 +3505,10 @@ function startProxyDaemonOwned(name, root, caCertPath, envFile, boot, caller = p
|
|
|
3494
3505
|
}
|
|
3495
3506
|
}
|
|
3496
3507
|
catch (error) {
|
|
3497
|
-
|
|
3508
|
+
try {
|
|
3509
|
+
rmSync(marker, { force: true });
|
|
3510
|
+
}
|
|
3511
|
+
catch { /* the spawn's failure is the one to report */ } // no child was returned by spawn
|
|
3498
3512
|
throw error;
|
|
3499
3513
|
}
|
|
3500
3514
|
child.on('error', () => { }); // a failed spawn can emit after this synchronous operation throws
|
|
@@ -3547,8 +3561,24 @@ function startProxyDaemonOwned(name, root, caCertPath, envFile, boot, caller = p
|
|
|
3547
3561
|
return { url: '', caCertPath, env: {} };
|
|
3548
3562
|
}
|
|
3549
3563
|
catch (error) {
|
|
3550
|
-
|
|
3551
|
-
|
|
3564
|
+
// the failure that stopped the start leads; a retirement that also fails is added to it, never put in its place
|
|
3565
|
+
if (!retirementAttempted) {
|
|
3566
|
+
// a retirement error is still what is thrown (its type keeps the survivor's ownership), with the cause it hid leading its
|
|
3567
|
+
// message; any other cleanup failure is added to the original, which is thrown
|
|
3568
|
+
try {
|
|
3569
|
+
retire();
|
|
3570
|
+
}
|
|
3571
|
+
catch (retirement) {
|
|
3572
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
3573
|
+
if (retirement instanceof CommandRetirementError) {
|
|
3574
|
+
Object.assign(retirement, { retirement: retirement.message, cause: error });
|
|
3575
|
+
retirement.message = `${cause}; ${retirement.message}`;
|
|
3576
|
+
throw retirement;
|
|
3577
|
+
}
|
|
3578
|
+
if (error instanceof Error)
|
|
3579
|
+
error.message += `; retiring the proxy failed too: ${retirement instanceof Error ? retirement.message : String(retirement)}`;
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3552
3582
|
throw error;
|
|
3553
3583
|
}
|
|
3554
3584
|
}
|
package/dist/src/served-world.js
CHANGED
|
@@ -1189,6 +1189,15 @@ export class WorldDoors {
|
|
|
1189
1189
|
}
|
|
1190
1190
|
if (kind === 'links' && !name && request.method === 'GET')
|
|
1191
1191
|
return Response.json({ links: this.links() });
|
|
1192
|
+
// OPENING A SEAL — pointing this World at a vendor (a link, a twin's root) or sealing the credential it uses there — is
|
|
1193
|
+
// the World token's act, or a person's at its page: never a key's, which an app or a script holds (an app that could
|
|
1194
|
+
// open its own seal would be unsealed by itself, not deliberately)
|
|
1195
|
+
const opensSeal = (kind === 'links' && name && (request.method === 'PUT' || request.method === 'DELETE'))
|
|
1196
|
+
|| (kind === 'twins' && name && (verb === 'root' || verb === 'credential') && request.method === 'PUT')
|
|
1197
|
+
// a check runs in a local World's own process, with all it can reach: putting or removing one is the same act
|
|
1198
|
+
|| (kind === 'checks' && name && (request.method === 'PUT' || request.method === 'DELETE'));
|
|
1199
|
+
if (opensSeal && via === 'key')
|
|
1200
|
+
return Response.json({ error: 'opening a seal is the World\'s token\'s act, or a person\'s at its page: a key cannot point this World at a vendor, seal a credential, or change its checks' }, { status: 403 });
|
|
1192
1201
|
if (kind === 'links' && name && request.method === 'PUT') {
|
|
1193
1202
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(name) || this.up().services[name])
|
|
1194
1203
|
return Response.json({ error: 'a link is named [a-z0-9_-] and is not a twin of this World' }, { status: 400 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.35",
|
|
4
4
|
"description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
package/src/runtime.ts
CHANGED
|
@@ -1567,7 +1567,11 @@ export async function upWorld(configId: string, options: UpWorldOptions = {}, ab
|
|
|
1567
1567
|
writeFileSync(bootingFile(root, name), `${JSON.stringify({ pid: process.pid, at: new Date().toISOString() } satisfies BootingClaim)}\n`, { mode: 0o600 });
|
|
1568
1568
|
});
|
|
1569
1569
|
} catch (error) {
|
|
1570
|
-
|
|
1570
|
+
// the failure that stopped the boot leads: a release that also fails is added to it, never put in its place
|
|
1571
|
+
if (resourcesClaimed) {
|
|
1572
|
+
try { releaseWorldLifecycle(root, name); }
|
|
1573
|
+
catch (release) { if (error instanceof Error) error.message += `; releasing the World's claim failed too: ${release instanceof Error ? release.message : String(release)}`; }
|
|
1574
|
+
}
|
|
1571
1575
|
throw error;
|
|
1572
1576
|
}
|
|
1573
1577
|
|
|
@@ -1755,7 +1759,7 @@ export async function upWorld(configId: string, options: UpWorldOptions = {}, ab
|
|
|
1755
1759
|
recordWorldStop(Object.values(services).filter((service) => rollbackKilled.includes(service.pid) && service.log).map((service) => service.log), 'world.service.killed', 'boot rollback', 'SIGKILL');
|
|
1756
1760
|
const surviving = undeadPids(pids);
|
|
1757
1761
|
if (surviving.length) rollbackErrors.push('owned processes remain alive or uncertain');
|
|
1758
|
-
if (error instanceof CommandRetirementError) rollbackErrors.push(error.message);
|
|
1762
|
+
if (error instanceof CommandRetirementError) rollbackErrors.push((error as CommandRetirementError & { retirement?: string }).retirement ?? error.message);
|
|
1759
1763
|
|
|
1760
1764
|
if (rollbackErrors.length > 0) {
|
|
1761
1765
|
cleanupUncertain = true;
|
|
@@ -3341,7 +3345,7 @@ function startProxyDaemonOwned(name: string, root: string, caCertPath: string, e
|
|
|
3341
3345
|
try { writeFileSync(markerFd, JSON.stringify({ token: randomUUID(), ownerPid: process.pid, createdAt })); }
|
|
3342
3346
|
finally { closeSync(markerFd); }
|
|
3343
3347
|
} catch (error) {
|
|
3344
|
-
rmSync(marker, { force: true }); // partial pre-spawn writes own no child
|
|
3348
|
+
try { rmSync(marker, { force: true }); } catch { /* the write's failure is the one to report */ } // partial pre-spawn writes own no child
|
|
3345
3349
|
throw error;
|
|
3346
3350
|
}
|
|
3347
3351
|
let child: ReturnType<typeof spawn>;
|
|
@@ -3356,7 +3360,7 @@ function startProxyDaemonOwned(name: string, root: string, caCertPath: string, e
|
|
|
3356
3360
|
});
|
|
3357
3361
|
} finally { closeSync(proxyOut); }
|
|
3358
3362
|
} catch (error) {
|
|
3359
|
-
rmSync(marker, { force: true }); // no child was returned by spawn
|
|
3363
|
+
try { rmSync(marker, { force: true }); } catch { /* the spawn's failure is the one to report */ } // no child was returned by spawn
|
|
3360
3364
|
throw error;
|
|
3361
3365
|
}
|
|
3362
3366
|
child.on('error', () => {}); // a failed spawn can emit after this synchronous operation throws
|
|
@@ -3399,7 +3403,20 @@ function startProxyDaemonOwned(name: string, root: string, caCertPath: string, e
|
|
|
3399
3403
|
process.stderr.write(`!! WARN: ambient proxy daemon for world "${name}" did not report within ${deadlineMs}ms — world is NOT proxy-sealed; unmodified CLIs will bypass the twins (env-only redirect still active).\n`);
|
|
3400
3404
|
return { url: '', caCertPath, env: {} };
|
|
3401
3405
|
} catch (error) {
|
|
3402
|
-
|
|
3406
|
+
// the failure that stopped the start leads; a retirement that also fails is added to it, never put in its place
|
|
3407
|
+
if (!retirementAttempted) {
|
|
3408
|
+
// a retirement error is still what is thrown (its type keeps the survivor's ownership), with the cause it hid leading its
|
|
3409
|
+
// message; any other cleanup failure is added to the original, which is thrown
|
|
3410
|
+
try { retire(); } catch (retirement) {
|
|
3411
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
3412
|
+
if (retirement instanceof CommandRetirementError) {
|
|
3413
|
+
Object.assign(retirement, { retirement: retirement.message, cause: error });
|
|
3414
|
+
retirement.message = `${cause}; ${retirement.message}`;
|
|
3415
|
+
throw retirement;
|
|
3416
|
+
}
|
|
3417
|
+
if (error instanceof Error) error.message += `; retiring the proxy failed too: ${retirement instanceof Error ? retirement.message : String(retirement)}`;
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3403
3420
|
throw error;
|
|
3404
3421
|
}
|
|
3405
3422
|
}
|
package/src/served-world.ts
CHANGED
|
@@ -1037,6 +1037,14 @@ export class WorldDoors {
|
|
|
1037
1037
|
return Response.json(approveWorldChangeset(name, { root: this.worldRoot, world: this.name, principal, ...(body.note ? { note: body.note } : {}) }));
|
|
1038
1038
|
}
|
|
1039
1039
|
if (kind === 'links' && !name && request.method === 'GET') return Response.json({ links: this.links() });
|
|
1040
|
+
// OPENING A SEAL — pointing this World at a vendor (a link, a twin's root) or sealing the credential it uses there — is
|
|
1041
|
+
// the World token's act, or a person's at its page: never a key's, which an app or a script holds (an app that could
|
|
1042
|
+
// open its own seal would be unsealed by itself, not deliberately)
|
|
1043
|
+
const opensSeal = (kind === 'links' && name && (request.method === 'PUT' || request.method === 'DELETE'))
|
|
1044
|
+
|| (kind === 'twins' && name && (verb === 'root' || verb === 'credential') && request.method === 'PUT')
|
|
1045
|
+
// a check runs in a local World's own process, with all it can reach: putting or removing one is the same act
|
|
1046
|
+
|| (kind === 'checks' && name && (request.method === 'PUT' || request.method === 'DELETE'));
|
|
1047
|
+
if (opensSeal && via === 'key') return Response.json({ error: 'opening a seal is the World\'s token\'s act, or a person\'s at its page: a key cannot point this World at a vendor, seal a credential, or change its checks' }, { status: 403 });
|
|
1040
1048
|
if (kind === 'links' && name && request.method === 'PUT') {
|
|
1041
1049
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(name) || this.up().services[name]) return Response.json({ error: 'a link is named [a-z0-9_-] and is not a twin of this World' }, { status: 400 });
|
|
1042
1050
|
const body = (await request.json()) as { origin?: string };
|