@volter/world-runtime 2.0.46 → 2.0.47
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/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/root.d.ts +10 -0
- package/dist/src/root.js +47 -0
- package/dist/src/served-world.js +12 -2
- package/package.json +4 -4
- package/src/index.ts +1 -1
- package/src/root.ts +27 -0
- package/src/served-world.ts +11 -2
package/dist/src/index.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export { fetchFromOrigin, worldOrigin } from './origin.js';
|
|
|
39
39
|
export type { FetchOptions, FetchOutcome } from './origin.js';
|
|
40
40
|
export { branchWorld, checkoutWorld } from './branch.js';
|
|
41
41
|
export type { BranchOptions } from './branch.js';
|
|
42
|
-
export { deployWorld, refreshTwin, adaptersFor, credentialPath, credentialPayloadFrom, rootCredentialInput, bindLegacyCredentials, deployTwin, loadWorldChecks, materializeRoots, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, userKekPath } from './root.js';
|
|
42
|
+
export { deployWorld, refreshTwin, adaptersFor, credentialPath, rewrapTwinCredentials, credentialPayloadFrom, rootCredentialInput, bindLegacyCredentials, deployTwin, loadWorldChecks, materializeRoots, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, userKekPath } from './root.js';
|
|
43
43
|
export type { DeployTwinOutcome, MaterializedRoot } from './root.js';
|
|
44
44
|
export { landReceipts, mountWorld, READ_ONLY_HEADER, worldOriginLabel, readServeRecord, refreshSchedule, servedName, serveWorld, sessionsFile, keysFile, TOKEN_HEADER, WorldDoors } from './served-world.js';
|
|
45
45
|
export type { BranchDoors, BranchRow, DoorHost, MountedWorld, ServeRecord, WorldLayout } from './served-world.js';
|
package/dist/src/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { advertiseWorldManifest, fetchRemoteManifest, MANIFEST_PATH, remoteAttac
|
|
|
29
29
|
export { resetWorld, seedEntryFor, seedWorld } from "./placeholder.js";
|
|
30
30
|
export { fetchFromOrigin, worldOrigin } from "./origin.js";
|
|
31
31
|
export { branchWorld, checkoutWorld } from "./branch.js";
|
|
32
|
-
export { deployWorld, refreshTwin, adaptersFor, credentialPath, credentialPayloadFrom, rootCredentialInput, bindLegacyCredentials, deployTwin, loadWorldChecks, materializeRoots, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, userKekPath } from "./root.js";
|
|
32
|
+
export { deployWorld, refreshTwin, adaptersFor, credentialPath, rewrapTwinCredentials, credentialPayloadFrom, rootCredentialInput, bindLegacyCredentials, deployTwin, loadWorldChecks, materializeRoots, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, userKekPath } from "./root.js";
|
|
33
33
|
export { landReceipts, mountWorld, READ_ONLY_HEADER, worldOriginLabel, readServeRecord, refreshSchedule, servedName, serveWorld, sessionsFile, keysFile, TOKEN_HEADER, WorldDoors } from "./served-world.js";
|
|
34
34
|
export { LocalBranches } from "./local-branches.js";
|
|
35
35
|
export { serveWorldView, VIEW_CONSOLE_BASE } from "./world-view.js";
|
package/dist/src/root.d.ts
CHANGED
|
@@ -5,6 +5,16 @@ import type { WorldConfig } from './schema.js';
|
|
|
5
5
|
* seals is wrapped under. Made on first use, 0600, never printed. */
|
|
6
6
|
export { userKek, userKekPath } from '@volter/world-core';
|
|
7
7
|
export declare function credentialPath(worldRoot: string, vendor: string): string;
|
|
8
|
+
/** Rewrap every credential sealed beside the World (each vendor's and each link's) under the newest version of the key
|
|
9
|
+
* that wrapped it: an operator's act after rotating the key, so no record still needs an old version. `current` is only
|
|
10
|
+
* what the sealer vouched for; a record it cannot (sealed under a host key, or by another vault key, a version it
|
|
11
|
+
* could not read), one changed while it was rewrapped (sealed again meanwhile, never overwritten) or one that does not
|
|
12
|
+
* read is `left`, by name. */
|
|
13
|
+
export declare function rewrapTwinCredentials(worldRoot: string): Promise<{
|
|
14
|
+
rewrapped: string[];
|
|
15
|
+
current: string[];
|
|
16
|
+
left: string[];
|
|
17
|
+
}>;
|
|
8
18
|
/** Seal a vendor's credential beside the World, bound to `bound` (destinationOf the root's or link's origin): it opens
|
|
9
19
|
* for that destination only, so pointing the root or link elsewhere never sends it there. */
|
|
10
20
|
export declare function sealTwinCredential(worldRoot: string, vendor: string, payload: CredentialPayload, at: string | undefined, bound: string): Promise<{
|
package/dist/src/root.js
CHANGED
|
@@ -30,6 +30,53 @@ export { userKek, userKekPath } from '@volter/world-core';
|
|
|
30
30
|
export function credentialPath(worldRoot, vendor) {
|
|
31
31
|
return join(resolve(worldRoot), stateDirName(), 'credentials', `${vendor}.json`);
|
|
32
32
|
}
|
|
33
|
+
/** Rewrap every credential sealed beside the World (each vendor's and each link's) under the newest version of the key
|
|
34
|
+
* that wrapped it: an operator's act after rotating the key, so no record still needs an old version. `current` is only
|
|
35
|
+
* what the sealer vouched for; a record it cannot (sealed under a host key, or by another vault key, a version it
|
|
36
|
+
* could not read), one changed while it was rewrapped (sealed again meanwhile, never overwritten) or one that does not
|
|
37
|
+
* read is `left`, by name. */
|
|
38
|
+
export async function rewrapTwinCredentials(worldRoot) {
|
|
39
|
+
const store = getActiveWorldStore();
|
|
40
|
+
const sealer = currentSealer();
|
|
41
|
+
const dir = dirname(credentialPath(worldRoot, 'x'));
|
|
42
|
+
const names = store.list(dir).filter((f) => /^[a-z0-9][a-z0-9_-]*\.json$/.test(f)).map((f) => f.replace(/\.json$/, ''));
|
|
43
|
+
const out = { rewrapped: [], current: [], left: [] };
|
|
44
|
+
for (const name of names) {
|
|
45
|
+
const path = credentialPath(worldRoot, name);
|
|
46
|
+
const text = store.read(path);
|
|
47
|
+
if (text === null)
|
|
48
|
+
continue;
|
|
49
|
+
let sealed;
|
|
50
|
+
try {
|
|
51
|
+
sealed = JSON.parse(text);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
out.left.push(name);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!sealer.rewrap) {
|
|
58
|
+
out.left.push(name);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const result = await sealer.rewrap(sealed);
|
|
62
|
+
if (result.status === 'current') {
|
|
63
|
+
out.current.push(name);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (result.status === 'foreign') {
|
|
67
|
+
out.left.push(name);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
// sealed again while the vault rewrapped it: the new credential stands, and this record is not reported moved
|
|
71
|
+
if (store.read(path) !== text) {
|
|
72
|
+
out.left.push(name);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
store.write(path, `${JSON.stringify(result.sealed, null, 2)}\n`, { secret: true });
|
|
76
|
+
out.rewrapped.push(name);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
33
80
|
/** Seal a vendor's credential beside the World, bound to `bound` (destinationOf the root's or link's origin): it opens
|
|
34
81
|
* for that destination only, so pointing the root or link elsewhere never sends it there. */
|
|
35
82
|
export async function sealTwinCredential(worldRoot, vendor, payload, at, bound) {
|
package/dist/src/served-world.js
CHANGED
|
@@ -36,7 +36,7 @@ import { approveWorldChangeset, createWorldChangeset, diffWorld, findWorldChange
|
|
|
36
36
|
import { fetchFromOrigin } from "./origin.js";
|
|
37
37
|
import { CONSOLE_BASE, consoleRedirect, serveConsoleApart } from "./console-apart.js";
|
|
38
38
|
import { loadWorldConfig } from "./configs.js";
|
|
39
|
-
import { adaptersFor, credentialPath, packMirror, credentialPayloadFrom, deployTwin, deployWorld, loadWorldChecks, twinSigningSecret, materializeRoots, refreshPosture, refreshTwin, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, bindLegacyCredentials, setTwinRoot, spanMs } from "./root.js";
|
|
39
|
+
import { adaptersFor, credentialPath, packMirror, credentialPayloadFrom, deployTwin, deployWorld, loadWorldChecks, twinSigningSecret, materializeRoots, refreshPosture, refreshTwin, rootForControlRoot, rewrapTwinCredentials, sealTwinCredential, sealedCredentialInfo, bindLegacyCredentials, setTwinRoot, spanMs } from "./root.js";
|
|
40
40
|
import { clockFile, downWorld, listWorlds, statusWorld, upWorld } from "./runtime.js";
|
|
41
41
|
export const TOKEN_HEADER = 'x-volter-token';
|
|
42
42
|
/** The header the injector carries a hosted World's token in (inject.cjs, VOLTER_TWINS_KEY). */
|
|
@@ -1256,9 +1256,19 @@ export class WorldDoors {
|
|
|
1256
1256
|
const opensSeal = (kind === 'links' && name && (request.method === 'PUT' || request.method === 'DELETE'))
|
|
1257
1257
|
|| (kind === 'twins' && name && (verb === 'root' || verb === 'credential') && request.method === 'PUT')
|
|
1258
1258
|
// a check runs in a local World's own process, with all it can reach: putting or removing one is the same act
|
|
1259
|
-
|| (kind === 'checks' && name && (request.method === 'PUT' || request.method === 'DELETE'))
|
|
1259
|
+
|| (kind === 'checks' && name && (request.method === 'PUT' || request.method === 'DELETE'))
|
|
1260
|
+
// rewrapping the World's sealed credentials is its holder's act, after a rotation of the key that wrapped them
|
|
1261
|
+
|| (kind === 'credentials' && name === 'rewrap' && request.method === 'POST');
|
|
1260
1262
|
if (opensSeal && via === 'key')
|
|
1261
1263
|
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 });
|
|
1264
|
+
// THE REWRAP: after an operator rotates the vault key this World's credentials are wrapped under, each record's DEK
|
|
1265
|
+
// is wrapped again under the newest version (transit/rewrap: the DEK never leaves the vault); the answer names
|
|
1266
|
+
// which moved, which were already current, and which this sealer does not wrap (sealed under a host key)
|
|
1267
|
+
if (kind === 'credentials' && name === 'rewrap' && request.method === 'POST') {
|
|
1268
|
+
if (scope !== 'write')
|
|
1269
|
+
return Response.json({ error: 'rewrapping needs the World\'s write access' }, { status: 403 });
|
|
1270
|
+
return Response.json(await this.serialized('credentials', () => rewrapTwinCredentials(this.worldRoot)));
|
|
1271
|
+
}
|
|
1262
1272
|
if (kind === 'links' && name && request.method === 'PUT') {
|
|
1263
1273
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(name) || this.up().services[name])
|
|
1264
1274
|
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.47",
|
|
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",
|
|
@@ -67,14 +67,14 @@
|
|
|
67
67
|
"node": ">=22.3"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@volter/world-core": "2.0.
|
|
70
|
+
"@volter/world-core": "2.0.29",
|
|
71
71
|
"@volter/world-console": "2.0.13"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@electric-sql/pglite": "0.5.8",
|
|
75
75
|
"@electric-sql/pglite-pgvector": "0.0.9",
|
|
76
76
|
"@volter/world-access": "2.0.2",
|
|
77
|
-
"@volter/world-core": "2.0.
|
|
77
|
+
"@volter/world-core": "2.0.29",
|
|
78
78
|
"pg-gateway": "0.3.0-beta.4",
|
|
79
79
|
"smol-toml": "^1.8.0"
|
|
80
80
|
},
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
86
|
"optionalDependencies": {
|
|
87
|
-
"@volter/twin-mongodb": "0.1.
|
|
87
|
+
"@volter/twin-mongodb": "0.1.29"
|
|
88
88
|
}
|
|
89
89
|
}
|
package/src/index.ts
CHANGED
|
@@ -161,7 +161,7 @@ export type { FetchOptions, FetchOutcome } from './origin.ts';
|
|
|
161
161
|
|
|
162
162
|
export { branchWorld, checkoutWorld } from './branch.ts';
|
|
163
163
|
export type { BranchOptions } from './branch.ts';
|
|
164
|
-
export { deployWorld, refreshTwin, adaptersFor, credentialPath, credentialPayloadFrom, rootCredentialInput, bindLegacyCredentials, deployTwin, loadWorldChecks, materializeRoots, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, userKekPath } from './root.ts';
|
|
164
|
+
export { deployWorld, refreshTwin, adaptersFor, credentialPath, rewrapTwinCredentials, credentialPayloadFrom, rootCredentialInput, bindLegacyCredentials, deployTwin, loadWorldChecks, materializeRoots, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, userKekPath } from './root.ts';
|
|
165
165
|
export type { DeployTwinOutcome, MaterializedRoot } from './root.ts';
|
|
166
166
|
export { landReceipts, mountWorld, READ_ONLY_HEADER, worldOriginLabel, readServeRecord, refreshSchedule, servedName, serveWorld, sessionsFile, keysFile, TOKEN_HEADER, WorldDoors } from './served-world.ts';
|
|
167
167
|
export type { BranchDoors, BranchRow, DoorHost, MountedWorld, ServeRecord, WorldLayout } from './served-world.ts';
|
package/src/root.ts
CHANGED
|
@@ -37,6 +37,33 @@ export { userKek, userKekPath } from '@volter/world-core';
|
|
|
37
37
|
export function credentialPath(worldRoot: string, vendor: string): string {
|
|
38
38
|
return join(resolve(worldRoot), stateDirName(), 'credentials', `${vendor}.json`);
|
|
39
39
|
}
|
|
40
|
+
/** Rewrap every credential sealed beside the World (each vendor's and each link's) under the newest version of the key
|
|
41
|
+
* that wrapped it: an operator's act after rotating the key, so no record still needs an old version. `current` is only
|
|
42
|
+
* what the sealer vouched for; a record it cannot (sealed under a host key, or by another vault key, a version it
|
|
43
|
+
* could not read), one changed while it was rewrapped (sealed again meanwhile, never overwritten) or one that does not
|
|
44
|
+
* read is `left`, by name. */
|
|
45
|
+
export async function rewrapTwinCredentials(worldRoot: string): Promise<{ rewrapped: string[]; current: string[]; left: string[] }> {
|
|
46
|
+
const store = getActiveWorldStore(); const sealer = currentSealer();
|
|
47
|
+
const dir = dirname(credentialPath(worldRoot, 'x'));
|
|
48
|
+
const names = store.list(dir).filter((f) => /^[a-z0-9][a-z0-9_-]*\.json$/.test(f)).map((f) => f.replace(/\.json$/, ''));
|
|
49
|
+
const out = { rewrapped: [] as string[], current: [] as string[], left: [] as string[] };
|
|
50
|
+
for (const name of names) {
|
|
51
|
+
const path = credentialPath(worldRoot, name);
|
|
52
|
+
const text = store.read(path); if (text === null) continue;
|
|
53
|
+
let sealed: SealedCredential;
|
|
54
|
+
try { sealed = JSON.parse(text) as SealedCredential; } catch { out.left.push(name); continue; }
|
|
55
|
+
if (!sealer.rewrap) { out.left.push(name); continue; }
|
|
56
|
+
const result = await sealer.rewrap(sealed);
|
|
57
|
+
if (result.status === 'current') { out.current.push(name); continue; }
|
|
58
|
+
if (result.status === 'foreign') { out.left.push(name); continue; }
|
|
59
|
+
// sealed again while the vault rewrapped it: the new credential stands, and this record is not reported moved
|
|
60
|
+
if (store.read(path) !== text) { out.left.push(name); continue; }
|
|
61
|
+
store.write(path, `${JSON.stringify(result.sealed, null, 2)}\n`, { secret: true });
|
|
62
|
+
out.rewrapped.push(name);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
40
67
|
/** Seal a vendor's credential beside the World, bound to `bound` (destinationOf the root's or link's origin): it opens
|
|
41
68
|
* for that destination only, so pointing the root or link elsewhere never sends it there. */
|
|
42
69
|
export async function sealTwinCredential(worldRoot: string, vendor: string, payload: CredentialPayload, at: string | undefined, bound: string): Promise<{ placedAt: string; fingerprint: string }> {
|
package/src/served-world.ts
CHANGED
|
@@ -39,7 +39,7 @@ import { approveWorldChangeset, createWorldChangeset, diffWorld, findWorldChange
|
|
|
39
39
|
import { fetchFromOrigin } from './origin.ts';
|
|
40
40
|
import { CONSOLE_BASE, consoleRedirect, serveConsoleApart, type ConsoleMount } from './console-apart.ts';
|
|
41
41
|
import { loadWorldConfig } from './configs.ts';
|
|
42
|
-
import { adaptersFor, credentialPath, packMirror, type PackMirror, credentialPayloadFrom, deployTwin, deployWorld, loadWorldChecks, twinSigningSecret, materializeRoots, refreshPosture, refreshTwin, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, bindLegacyCredentials, setTwinRoot, spanMs } from './root.ts';
|
|
42
|
+
import { adaptersFor, credentialPath, packMirror, type PackMirror, credentialPayloadFrom, deployTwin, deployWorld, loadWorldChecks, twinSigningSecret, materializeRoots, refreshPosture, refreshTwin, rootForControlRoot, rewrapTwinCredentials, sealTwinCredential, sealedCredentialInfo, bindLegacyCredentials, setTwinRoot, spanMs } from './root.ts';
|
|
43
43
|
import { clockFile, downWorld, listWorlds, statusWorld, upWorld } from './runtime.ts';
|
|
44
44
|
import type { WorldInstance } from './schema.ts';
|
|
45
45
|
|
|
@@ -1094,8 +1094,17 @@ export class WorldDoors {
|
|
|
1094
1094
|
const opensSeal = (kind === 'links' && name && (request.method === 'PUT' || request.method === 'DELETE'))
|
|
1095
1095
|
|| (kind === 'twins' && name && (verb === 'root' || verb === 'credential') && request.method === 'PUT')
|
|
1096
1096
|
// a check runs in a local World's own process, with all it can reach: putting or removing one is the same act
|
|
1097
|
-
|| (kind === 'checks' && name && (request.method === 'PUT' || request.method === 'DELETE'))
|
|
1097
|
+
|| (kind === 'checks' && name && (request.method === 'PUT' || request.method === 'DELETE'))
|
|
1098
|
+
// rewrapping the World's sealed credentials is its holder's act, after a rotation of the key that wrapped them
|
|
1099
|
+
|| (kind === 'credentials' && name === 'rewrap' && request.method === 'POST');
|
|
1098
1100
|
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 });
|
|
1101
|
+
// THE REWRAP: after an operator rotates the vault key this World's credentials are wrapped under, each record's DEK
|
|
1102
|
+
// is wrapped again under the newest version (transit/rewrap: the DEK never leaves the vault); the answer names
|
|
1103
|
+
// which moved, which were already current, and which this sealer does not wrap (sealed under a host key)
|
|
1104
|
+
if (kind === 'credentials' && name === 'rewrap' && request.method === 'POST') {
|
|
1105
|
+
if (scope !== 'write') return Response.json({ error: 'rewrapping needs the World\'s write access' }, { status: 403 });
|
|
1106
|
+
return Response.json(await this.serialized('credentials', () => rewrapTwinCredentials(this.worldRoot)));
|
|
1107
|
+
}
|
|
1099
1108
|
if (kind === 'links' && name && request.method === 'PUT') {
|
|
1100
1109
|
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 });
|
|
1101
1110
|
const body = (await request.json()) as { origin?: string };
|