@volter/world-runtime 2.0.55 → 3.0.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/dist/known-external-services.json +5 -4
- package/dist/src/actions-runner-cli.d.ts +2 -0
- package/dist/src/actions-runner-cli.js +25 -0
- package/dist/src/actions-runner.d.ts +18 -0
- package/dist/src/actions-runner.js +281 -0
- package/dist/src/ancestry.js +1 -1
- package/dist/src/app-url.js +1 -1
- package/dist/src/backings.d.ts +29 -0
- package/dist/src/backings.js +45 -0
- package/dist/src/branch.js +3 -2
- package/dist/src/browser-proxy-cli.js +1 -1
- package/dist/src/catalog-index.d.ts +38 -0
- package/dist/src/catalog-index.js +37 -0
- package/dist/src/catalog.d.ts +16 -3
- package/dist/src/catalog.js +69 -26
- package/dist/src/changeset.d.ts +1 -1
- package/dist/src/changeset.js +9 -11
- package/dist/src/cli-args.d.ts +4 -0
- package/dist/src/cli-args.js +18 -0
- package/dist/src/cli-commands.d.ts +1 -0
- package/dist/src/cli-commands.js +1216 -0
- package/dist/src/cli.js +8 -1300
- package/dist/src/configs.js +1 -1
- package/dist/src/console-apart.d.ts +2 -2
- package/dist/src/console-apart.js +5 -5
- package/dist/src/consumers.js +1 -1
- package/dist/src/covers.d.ts +1 -1
- package/dist/src/covers.js +94 -30
- package/dist/src/env-output.d.ts +1 -1
- package/dist/src/env-output.js +2 -2
- package/dist/src/fixture-env.js +18 -26
- package/dist/src/host-cli.js +4 -2
- package/dist/src/host-fault-fixture.js +6 -0
- package/dist/src/host-ready.d.ts +8 -2
- package/dist/src/host-ready.js +16 -4
- package/dist/src/host-worker.js +6 -1
- package/dist/src/host.d.ts +2 -0
- package/dist/src/host.js +9 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +2 -2
- package/dist/src/init.d.ts +33 -23
- package/dist/src/init.js +228 -96
- package/dist/src/lifecycle-record.js +2 -2
- package/dist/src/local-branches.js +1 -1
- package/dist/src/origin.d.ts +1 -1
- package/dist/src/origin.js +12 -10
- package/dist/src/pack-facts.d.ts +30 -6
- package/dist/src/pack-facts.js +13 -3
- package/dist/src/pglite-backing.js +2 -1
- package/dist/src/placeholder.d.ts +1 -1
- package/dist/src/placeholder.js +12 -4
- package/dist/src/project-inspect.d.ts +1 -1
- package/dist/src/project-inspect.js +2 -2
- package/dist/src/redirect-proxy.d.ts +5 -0
- package/dist/src/redirect-proxy.js +8 -1
- package/dist/src/redis-backing.js +4 -2
- package/dist/src/reflect.js +1 -1
- package/dist/src/root.d.ts +11 -33
- package/dist/src/root.js +28 -59
- package/dist/src/runtime-test-support.js +1 -1
- package/dist/src/runtime.d.ts +7 -1
- package/dist/src/runtime.js +317 -53
- package/dist/src/schema.d.ts +27 -5
- package/dist/src/schema.js +13 -12
- package/dist/src/served-world.d.ts +11 -35
- package/dist/src/served-world.js +54 -141
- package/dist/src/tail.js +1 -1
- package/dist/src/up-cli.d.ts +3 -0
- package/dist/src/up-cli.js +55 -0
- package/dist/src/websocket-relay.d.ts +1 -1
- package/dist/src/world-origins.d.ts +1 -1
- package/dist/src/world-origins.js +1 -1
- package/dist/src/world-view.d.ts +1 -1
- package/dist/src/world-view.js +2 -2
- package/known-external-services.json +5 -4
- package/package.json +11 -11
- package/src/actions-runner-cli.ts +24 -0
- package/src/actions-runner.ts +244 -0
- package/src/ancestry.ts +1 -1
- package/src/app-url.ts +1 -1
- package/src/backings.ts +55 -0
- package/src/branch.ts +3 -2
- package/src/browser-proxy-cli.ts +1 -1
- package/src/catalog-index.ts +44 -0
- package/src/catalog.ts +67 -24
- package/src/changeset.ts +10 -13
- package/src/cli-args.ts +17 -0
- package/src/cli-commands.ts +1070 -0
- package/src/cli.ts +9 -1139
- package/src/configs.ts +1 -1
- package/src/console-apart.ts +5 -5
- package/src/consumers.ts +1 -1
- package/src/covers.ts +83 -30
- package/src/env-output.ts +2 -2
- package/src/fixture-env.ts +18 -26
- package/src/host-cli.ts +4 -2
- package/src/host-ready.ts +13 -4
- package/src/host-worker.ts +6 -2
- package/src/host.ts +11 -2
- package/src/index.ts +2 -2
- package/src/init.ts +222 -101
- package/src/lifecycle-record.ts +2 -2
- package/src/local-branches.ts +1 -1
- package/src/origin.ts +12 -10
- package/src/pack-facts.ts +22 -6
- package/src/pglite-backing.ts +2 -1
- package/src/placeholder.ts +13 -4
- package/src/project-inspect.ts +2 -2
- package/src/redirect-proxy.ts +9 -1
- package/src/redis-backing.ts +4 -2
- package/src/reflect.ts +1 -1
- package/src/root.ts +33 -65
- package/src/runtime-test-support.ts +1 -1
- package/src/runtime.ts +261 -55
- package/src/schema.ts +26 -13
- package/src/served-world.ts +50 -130
- package/src/tail.ts +1 -1
- package/src/up-cli.ts +55 -0
- package/src/websocket-relay.ts +1 -1
- package/src/world-origins.ts +1 -1
- package/src/world-view.ts +3 -3
package/src/origin.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { captureHistory, historyDigest, originHead, publishOriginHistory, type HistoryLayout, type HistoryReference, readHistoryView, historyLength, worldPaths } from '@volter/world-core';
|
|
1
|
+
import { captureHistory, historyDigest, originHead, publishOriginHistory, type HistoryLayout, type HistoryReference, readHistoryView, historyLength, worldPaths } from '@volter/world-core/runtime';
|
|
2
2
|
// `volter world clone` and `fetch` — a world's ORIGIN is another world, served (docs/concepts/the-model.md
|
|
3
3
|
// #a-branch-is-a-position-not-a-copy: a clone is a branch whose parent is at a URL): its whole log per twin is this world's PARENT. `clone` records the origin
|
|
4
4
|
// and brings every twin's log in from position zero; `fetch` appends what the origin has past the
|
|
5
5
|
// recorded position. Nothing here touches a vendor: an origin is always a world, the token is the
|
|
6
6
|
// served world's, and what arrives are entries — the parent log grows, the branch log is untouched.
|
|
7
7
|
import { join, resolve } from 'node:path';
|
|
8
|
-
import { appendOriginEntry, appendParentEntry, branchEntries, getActiveWorldStore, landedIds, originEntries, parentEntries, readBranchMeta, type Entry, wholeLog,
|
|
8
|
+
import { appendOriginEntry, appendParentEntry, branchEntries, getActiveWorldStore, landedIds, originEntries, parentEntries, readBranchMeta, type Entry, wholeLog, withHistoryLock, writeBranchMeta } from '@volter/world-core/runtime';
|
|
9
9
|
import { saveWorldInstance, statusWorld } from './runtime.ts';
|
|
10
10
|
import type { WorldInstance } from './schema.ts';
|
|
11
11
|
import { fetchReady, isPathRemote, localWorld, TOKEN_HEADER } from './served-world.ts';
|
|
@@ -55,13 +55,14 @@ export async function fetchFromOrigin(name: string, options: FetchOptions): Prom
|
|
|
55
55
|
const appended: Record<string, number> = {};
|
|
56
56
|
const behind: Record<string, number> = {};
|
|
57
57
|
const snapshots: FetchOutcome['snapshots'] = {};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
for (const service of services) {
|
|
59
|
+
const controlRoot = join(instance.dirs.data, service);
|
|
60
|
+
const state = stateServiceOf(controlRoot, service);
|
|
61
|
+
withHistoryLock(worldPaths(state, controlRoot).dir, () => {
|
|
62
|
+
const meta = readBranchMeta(state, controlRoot);
|
|
62
63
|
if (meta?.parent && !meta.origin) throw new Error('Cannot attach a first origin to an existing local fork; clone the origin before branching');
|
|
63
|
-
}
|
|
64
|
-
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
65
66
|
for (const service of services) {
|
|
66
67
|
const controlRoot = join(instance.dirs.data, service);
|
|
67
68
|
const state = stateServiceOf(controlRoot, service);
|
|
@@ -69,7 +70,8 @@ export async function fetchFromOrigin(name: string, options: FetchOptions): Prom
|
|
|
69
70
|
// A PARENT ON THIS MACHINE is read live through the pointer, like a local branch (contract
|
|
70
71
|
// "Just like Neon", 1): nothing is copied; a clone points at the parent's head, a fetch only
|
|
71
72
|
// says how far the parent has moved past this branch's position
|
|
72
|
-
|
|
73
|
+
// under the branch's history lock, as rebase is: a fetch cannot restore a pointer a concurrent rebase advanced
|
|
74
|
+
withHistoryLock(worldPaths(state, controlRoot).dir, () => {
|
|
73
75
|
const target = localWorld(origin.url);
|
|
74
76
|
const at = join(target.instance.dirs.data, service);
|
|
75
77
|
const parentState = stateServiceOf(at, service);
|
|
@@ -109,7 +111,7 @@ export async function fetchFromOrigin(name: string, options: FetchOptions): Prom
|
|
|
109
111
|
page = await pageOf(origin, service, entries.length, options.key, first.view);
|
|
110
112
|
}
|
|
111
113
|
if (historyDigest({ layout: first.layout, entries }) !== first.digest) throw new Error('Origin history digest mismatch');
|
|
112
|
-
|
|
114
|
+
withHistoryLock(worldPaths(state, controlRoot).dir, () => {
|
|
113
115
|
const meta = readBranchMeta(state, controlRoot);
|
|
114
116
|
if (meta?.parent && !meta.origin) throw new Error('Cannot attach a first origin to an existing local fork; clone the origin before branching');
|
|
115
117
|
const cached = publishOriginHistory(state, controlRoot, first.view, first.layout, entries);
|
package/src/pack-facts.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// (packages/world-core/generated/pack-facts.json, compiled by scripts/pack-facts.ts
|
|
3
3
|
// from each pack's `pack: TwinPack` descriptor).
|
|
4
4
|
//
|
|
5
|
-
// During the descriptor-first migration (
|
|
5
|
+
// During the descriptor-first migration (architecture.md, "The descriptor": the descriptor is the one home), the
|
|
6
6
|
// adoption tables here are the UNION of the legacy central maps and the pack declarations.
|
|
7
7
|
// The overlay MUTATES the central tables at module init — every use-site keeps reading the
|
|
8
8
|
// object it always read — and a fact declared in BOTH places throws: dual declaration is the
|
|
@@ -10,23 +10,29 @@
|
|
|
10
10
|
|
|
11
11
|
import factsDocument from '@volter/world-core/generated/pack-facts.json' with { type: 'json' };
|
|
12
12
|
|
|
13
|
-
export type PackAdoption = { sdks?: string[]; pypi?: string[]; scopes?: string[]; envStems?: string[]; worldIds?: string[]; tools?: Array<{ package: string; usage: 'build' | 'deployment' }> };
|
|
13
|
+
export type PackAdoption = { sdks?: string[]; pypi?: string[]; scopes?: string[]; envStems?: string[]; worldIds?: string[]; tools?: Array<{ package: string; usage: 'build' | 'deployment' }>; configFiles?: Array<{ file: string; usage: 'application' | 'build' | 'deployment'; when?: string }> };
|
|
14
14
|
export type PackFacts = {
|
|
15
|
+
serverBacking?: { command: string };
|
|
15
16
|
/** Existing descriptor transport, compiled into the same artifact as endpoint/adoption facts. */
|
|
16
17
|
transport?: string;
|
|
17
|
-
/** Optional native frontend of the same state owner, selected explicitly on the pack CLI. */
|
|
18
|
-
nativeTransport?: { protocol: string; flag: string; upstreamEnv: string };
|
|
19
18
|
adoption?: PackAdoption;
|
|
20
19
|
hosts?: Array<{ host?: string; suffix?: string; hostPattern?: string; pathPattern?: string; key?: string; exclude?: true }>;
|
|
21
20
|
hostsNone?: string;
|
|
22
21
|
endpointEnv?: { name: string; templates?: Record<string, string>; note: string };
|
|
23
22
|
endpointEnvNone?: string;
|
|
23
|
+
/** The door that issues the application a credential the twin accepts (TwinPack.credentialDoor). */
|
|
24
|
+
credentialDoor?: { path: string; body: Record<string, unknown>; fill: Record<string, string> };
|
|
25
|
+
credentialIssuer?: string;
|
|
24
26
|
/** The pack's colocatable `create<Name>TwinServer` factory export, derived (or descriptor-
|
|
25
27
|
* declared under ambiguity) by scripts/pack-facts.ts. Consumed by `init` to emit
|
|
26
28
|
* `colocate:` service entries so a world's twins boot inside ONE host process (R2a). */
|
|
27
29
|
serveExport?: string;
|
|
30
|
+
/** The managed-infrastructure kind this pack is the World's backing for (architecture.md, "The descriptor"). */
|
|
31
|
+
managedService?: { kind: 'redis' | 'mongodb'; scheme: string; note: string };
|
|
28
32
|
/** The World's managed database the runtime binds the twin to at boot (TwinPack.managedDatabase). */
|
|
29
33
|
managedDatabase?: { kind: 'postgres'; arg: string; note: string };
|
|
34
|
+
/** Listeners the twin opens beside its main port, allocated and recorded by the runtime (TwinPack.sidePorts). */
|
|
35
|
+
sidePorts?: Array<{ name: string; option: string; flag: string; path?: string; note: string }>;
|
|
30
36
|
/** R16: the protocol major the package targets and its standing against the kernel that compiled the artifact. */
|
|
31
37
|
protocol?: { declared?: string; major: number; standing: 'current' | 'deprecated' | 'refused' | 'assumed' };
|
|
32
38
|
};
|
|
@@ -36,7 +42,16 @@ export type PackFacts = {
|
|
|
36
42
|
// core's artifact, imported as a JSON module: the published runtime resolves it beside an installed
|
|
37
43
|
// core, a bundle carries it, and no host needs a filesystem to read it
|
|
38
44
|
export function packFacts(): Record<string, PackFacts> {
|
|
39
|
-
return (factsDocument as unknown as { packs: Record<string, PackFacts> }).packs;
|
|
45
|
+
return merged ??= { ...(factsDocument as unknown as { packs: Record<string, PackFacts> }).packs, ...overlaid };
|
|
46
|
+
}
|
|
47
|
+
/** The facts of packs the kernel was not built with: a released pack carries its own (`generated/pack-facts.json`), and
|
|
48
|
+
* the catalog overlays them here, vendor by vendor, over the built-in ones (architecture, "The catalog: where twins
|
|
49
|
+
* come from"). The central tables overlaid at module init keep the built-in adoption facts. */
|
|
50
|
+
const overlaid: Record<string, PackFacts> = {};
|
|
51
|
+
let merged: Record<string, PackFacts> | undefined;
|
|
52
|
+
export function overlayPackFacts(packs: Record<string, PackFacts>): void {
|
|
53
|
+
Object.assign(overlaid, packs);
|
|
54
|
+
merged = undefined;
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
const dual = (vendor: string, kind: string, key: string, holder: string): Error =>
|
|
@@ -99,7 +114,8 @@ export function overlayCoversMaps(maps: {
|
|
|
99
114
|
maps.scopeVendors[scope] = vendor;
|
|
100
115
|
}
|
|
101
116
|
for (const stemRaw of facts.adoption?.envStems ?? []) {
|
|
102
|
-
|
|
117
|
+
// normalized as covers.ts reads a name's stem (`UPSTASH_REDIS_REST_URL` → `upstashredis`)
|
|
118
|
+
const stem = stemRaw.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
103
119
|
if (stem in maps.envStemVendors) throw dual(vendor, 'env stem', stem, 'ENV_STEM_VENDORS');
|
|
104
120
|
maps.envStemVendors[stem] = vendor;
|
|
105
121
|
}
|
package/src/pglite-backing.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { dirname, join } from 'node:path';
|
|
|
18
18
|
import { spawn, spawnSync } from 'node:child_process';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
20
|
import { findInstalledPackage, packCli } from './catalog.ts';
|
|
21
|
+
import { vendorPackageDir } from '@volter/world-core/runtime';
|
|
21
22
|
|
|
22
23
|
export interface InfraService { kind: string; hostPort: number }
|
|
23
24
|
|
|
@@ -80,7 +81,7 @@ function pidPath(dataDir: string, kind: string): string {
|
|
|
80
81
|
* this runtime was installed with (its dependency, or the checkout's workspace link). */
|
|
81
82
|
export function mongodbTwinCli(worldConfig?: string, froms: string[] = [...(worldConfig ? [dirname(worldConfig)] : []), here()]): string {
|
|
82
83
|
for (const from of froms) {
|
|
83
|
-
const dir = findInstalledPackage(from, MONGODB_TWIN);
|
|
84
|
+
const dir = vendorPackageDir(from, 'mongodb') ?? findInstalledPackage(from, MONGODB_TWIN);
|
|
84
85
|
const cli = dir === undefined ? undefined : packCli(dir);
|
|
85
86
|
if (cli !== undefined) return cli;
|
|
86
87
|
}
|
package/src/placeholder.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { materializeRoots } from './root.ts';
|
|
|
9
9
|
import { loadWorldConfig } from './configs.ts';
|
|
10
10
|
import { existsSync, readdirSync } from 'node:fs';
|
|
11
11
|
import { dirname, join, resolve } from 'node:path';
|
|
12
|
-
import { beginPlaceholderPull, endPlaceholderPull, listEvents, PLACEHOLDER_REMOTE, worldStateRoot } from '@volter/world-core';
|
|
12
|
+
import { beginPlaceholderPull, endPlaceholderPull, listEvents, PLACEHOLDER_REMOTE, worldStateRoot } from '@volter/world-core/runtime';
|
|
13
13
|
import { downWorld, runWithWorldEnv, saveWorldInstance, statusWorld, upWorld } from './runtime.ts';
|
|
14
14
|
import type { WorldInstance } from './schema.ts';
|
|
15
15
|
|
|
@@ -37,12 +37,21 @@ function placeholderCount(controlRoot: string): number {
|
|
|
37
37
|
return n;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/**
|
|
40
|
+
/** The twins of a World's config that have a root (a vendor's real account behind them). */
|
|
41
|
+
function rootedTwins(instance: WorldInstance, root: string): string[] {
|
|
42
|
+
const { config } = loadWorldConfig(existsSync(instance.configPath) ? instance.configPath : instance.config, root);
|
|
43
|
+
return config.services.filter((s) => (s as { root?: unknown }).root).map((s) => s.id);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Pull from the placeholder: every twin's window open around the seed entry (a twin with a root excepted). */
|
|
41
47
|
export async function seedWorld(name: string, options: SeedWorldOptions = {}): Promise<SeedWorldOutcome> {
|
|
42
48
|
const root = resolve(options.root ?? process.cwd());
|
|
43
49
|
const instance = statusWorld(name, root);
|
|
44
50
|
const entry = seedEntryFor(instance, options.entry);
|
|
45
|
-
|
|
51
|
+
// a vendor-backed twin is not seeded (architecture, "Refresh"): its state is its root's, and the default data's runner
|
|
52
|
+
// skips the twins named in VOLTER_SEED_SKIP
|
|
53
|
+
const rooted = new Set(rootedTwins(instance, root));
|
|
54
|
+
const services = Object.keys(instance.services).filter((s) => !rooted.has(s)).sort();
|
|
46
55
|
const at = new Date().toISOString();
|
|
47
56
|
for (const service of services) beginPlaceholderPull(service, join(instance.dirs.data, service), at);
|
|
48
57
|
let exitCode = 1;
|
|
@@ -55,7 +64,7 @@ export async function seedWorld(name: string, options: SeedWorldOptions = {}): P
|
|
|
55
64
|
if (major < 22 || (major === 22 && minor < 6)) throw new Error(`${entry} is TypeScript: seeding it under Node needs Node 22.6 or newer (type stripping), or Bun; this is Node ${process.versions.node}`);
|
|
56
65
|
}
|
|
57
66
|
const runner = typeof Bun !== 'undefined' ? ['bun', entry] : [process.execPath, '--experimental-strip-types', '--no-warnings', entry];
|
|
58
|
-
exitCode = await runWithWorldEnv(name, runner, root, { cwd: options.cwd ?? dirname(entry) });
|
|
67
|
+
exitCode = await runWithWorldEnv(name, runner, root, { cwd: options.cwd ?? dirname(entry), env: { VOLTER_SEED_SKIP: [...rooted].sort().join(',') } });
|
|
59
68
|
} finally {
|
|
60
69
|
for (const service of services) endPlaceholderPull(service, join(instance.dirs.data, service));
|
|
61
70
|
}
|
package/src/project-inspect.ts
CHANGED
|
@@ -21,13 +21,13 @@ export type ProjectInspection = {
|
|
|
21
21
|
/** npm package → vendor/twin mapping — the shared vendor↔SDK knowledge used by both
|
|
22
22
|
* `inspect-project` (adoption discovery) and `covers` (room-setup coverage proof).
|
|
23
23
|
* The canonical entry for each pack is the real client package that pack's own tests
|
|
24
|
-
* prove the twin against (see each
|
|
24
|
+
* prove the twin against (see each pack's package.json devDependencies);
|
|
25
25
|
* the extra aliases are the vendor's other official/first-party clients of the same
|
|
26
26
|
* API surface. */
|
|
27
27
|
export const SDK_TWINS: Record<string, { vendor: string; twin: string }> = {
|
|
28
28
|
// (empty — every entry has moved onto its pack descriptor. See the note below.)
|
|
29
29
|
};
|
|
30
|
-
// descriptor-first migration (
|
|
30
|
+
// descriptor-first migration (architecture.md, "The descriptor"), COMPLETE for this table: every pack declares its own sdk names
|
|
31
31
|
// (`adoption.sdks` on its descriptor, compiled into the committed pack-facts artifact), and the
|
|
32
32
|
// literal above is now empty — the map is populated entirely by the overlay below. It is kept as
|
|
33
33
|
// the overlay's target, not as a home: the exported object identity is what every use site already
|
package/src/redirect-proxy.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { serveHttp, type HttpServer } from '@volter/world-core';
|
|
1
|
+
import { serveHttp, type HttpServer } from '@volter/world-core/runtime';
|
|
2
2
|
import { worldNetworkPolicyFromEnv, allowsWorldNetworkEgress } from '@volter/world-core/network-policy';
|
|
3
3
|
import { websocketRelay } from './websocket-relay.ts';
|
|
4
4
|
// The ambient TLS redirect proxy — the substance of "any CLI, zero config" (WORLD_ACTIVATE.md §"How
|
|
@@ -185,6 +185,14 @@ function caPaths(tlsDir: string): CaPaths {
|
|
|
185
185
|
return { dir: tlsDir, caKey: join(tlsDir, 'ca-key.pem'), caCert: join(tlsDir, 'ca-cert.pem') };
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/** Whether this host opens raw sockets to other hosts, which the ambient TLS redirect proxy exists to intercept. A
|
|
189
|
+
* browser tab's Node (tabnode, which declares `process.versions.webcontainer`) opens none: its TLS is loopback pairing
|
|
190
|
+
* with no handshake, and a remote HTTPS call leaves through the page's HTTP transport. There no proxy can run and none
|
|
191
|
+
* is needed: the World's network policy is enforced by the injector in every Node program (world-core inject.cjs). */
|
|
192
|
+
export function hostHasRawEgress(versions: Record<string, string | undefined> = process.versions): boolean {
|
|
193
|
+
return versions.webcontainer === undefined;
|
|
194
|
+
}
|
|
195
|
+
|
|
188
196
|
export function opensslAvailable(): boolean {
|
|
189
197
|
// TEST-ONLY seam (TWIN-64): forces "openssl unavailable" deterministically. A PATH-shim (fake
|
|
190
198
|
// `openssl` on PATH) doesn't reliably work here because `spawnSync('openssl', …)` below is called
|
package/src/redis-backing.ts
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Why the twin and not a container: a World's state branches, checkpoints and rolls back as one tree, and a Redis
|
|
8
8
|
// in a container is state outside it (a branch of the World would share the container's queue and cache). The
|
|
9
|
-
// twin keeps each key a subject of the World (
|
|
9
|
+
// twin keeps each key a subject of the World (the Redis pack's storage module). It needs nothing
|
|
10
10
|
// installed: no container runtime, no redis-server binary.
|
|
11
11
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { connect } from 'node:net';
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
14
14
|
import { spawn } from 'node:child_process';
|
|
15
15
|
import { findInstalledPackage, packCli } from './catalog.ts';
|
|
16
|
+
import { vendorPackageDir } from '@volter/world-core/runtime';
|
|
16
17
|
import type { HostLaunch, InfraService } from './pglite-backing.ts';
|
|
17
18
|
|
|
18
19
|
const PACKAGE = '@volter/twin-redis';
|
|
@@ -21,7 +22,7 @@ const READY_TIMEOUT_MS = 60_000;
|
|
|
21
22
|
/** The redis twin's cli: installed beside this runtime (its node_modules walk), else beside the World's config. */
|
|
22
23
|
export function redisTwinCli(from: string[]): string | undefined {
|
|
23
24
|
for (const dir of from) {
|
|
24
|
-
const found = findInstalledPackage(dir, PACKAGE);
|
|
25
|
+
const found = vendorPackageDir(dir, 'redis') ?? findInstalledPackage(dir, PACKAGE);
|
|
25
26
|
const cli = found === undefined ? undefined : packCli(found);
|
|
26
27
|
if (cli !== undefined && existsSync(cli)) return cli;
|
|
27
28
|
}
|
|
@@ -68,6 +69,7 @@ export async function redisTwinUp(services: InfraService[], dataDir: string, cli
|
|
|
68
69
|
const child = spawn(process.execPath, [...runtime, cli, 'serve', '--port', String(service.hostPort), '--root', root], {
|
|
69
70
|
env: launch.env ?? process.env,
|
|
70
71
|
detached: true,
|
|
72
|
+
windowsHide: true,
|
|
71
73
|
stdio: ['ignore', logFd, logFd],
|
|
72
74
|
});
|
|
73
75
|
child.unref();
|
package/src/reflect.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { serveHttp, type HttpServer } from '@volter/world-core';
|
|
1
|
+
import { serveHttp, type HttpServer } from '@volter/world-core/runtime';
|
|
2
2
|
import { websocketRelay } from './websocket-relay.ts';
|
|
3
3
|
import { sessionTrustEnv } from './ca-trust.ts';
|
|
4
4
|
// The reflect attachment (docs/guides/route-a-cli-through-the-world.md): attach an UNMODIFIED consumer —
|
package/src/root.ts
CHANGED
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
// never in a pack, never an env var). `volter world deploy` performs landed entries against each
|
|
8
8
|
// root through the pack's adapters over the kernel executor, with the world's checks in front;
|
|
9
9
|
// `deploy: auto` does the same the moment an entry is appended (the host installs the hook).
|
|
10
|
+
import { randomBytes } from 'node:crypto';
|
|
10
11
|
import { packEntry } from './catalog.ts';
|
|
11
|
-
import { existsSync,
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
12
13
|
import { homedir } from 'node:os';
|
|
13
|
-
import {
|
|
14
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
14
15
|
import {
|
|
15
|
-
NO_SECRETS_CHECK, authStrategyFor, currentSealer, destinationOf, sealsThroughVault, openSealedAt, getActiveWorldStore, getPack, buildRemoteExecute, clearRoot, observeAppends, readRoot, registerStateSystem, stateDirName, stateSystemFor, type Check, type CredentialPayload, type PerformAction, type
|
|
16
|
-
} from '@volter/world-core';
|
|
16
|
+
NO_SECRETS_CHECK, authStrategyFor, currentSealer, destinationOf, sealsThroughVault, openSealedAt, getActiveWorldStore, getPack, buildRemoteExecute, clearRoot, observeAppends, readRoot, registerStateSystem, stateDirName, stateSystemFor, type Check, type CredentialPayload, type PerformAction, type RemoteExecute, type RootConfig, type SealedCredential, type TwinAction, validateRemoteOrigin, writeRoot,
|
|
17
|
+
} from '@volter/world-core/runtime';
|
|
17
18
|
import { loadWorldConfig, worldChangesetRules, writeWorldConfig } from './configs.ts';
|
|
18
|
-
import { changesetReadiness, collectObservations, type Observation, foldObservations, loadChecks, openRootCredential, performEntries, rootCustody, protocolStanding, userKek, type DeployReport, type ObserveReport } from '@volter/world-core';
|
|
19
|
+
import { changesetReadiness, collectObservations, type Observation, foldObservations, loadChecks, openRootCredential, performEntries, rootCustody, protocolStanding, userKek, vendorPackageDir, type DeployReport, type ObserveReport } from '@volter/world-core/runtime';
|
|
19
20
|
import { findWorldChangeset, listWorldChangesets } from './changeset.ts';
|
|
20
21
|
import { importModule } from './import-module.ts';
|
|
21
22
|
import { statusWorld } from './runtime.ts';
|
|
@@ -23,14 +24,13 @@ import { envNameVendor } from './covers.ts';
|
|
|
23
24
|
import { parseEnvFile } from './init.ts';
|
|
24
25
|
import { fakeEnvValue } from './fixture-env.ts';
|
|
25
26
|
import { projectManifestDirs } from './project-inspect.ts';
|
|
26
|
-
import { findInstalledPackage, twinPackageName } from './catalog.ts';
|
|
27
27
|
import type { WorldConfig } from './schema.ts';
|
|
28
28
|
|
|
29
29
|
// ── the user's sealing key ──────────────────────────────────────────────────────────────────
|
|
30
30
|
|
|
31
31
|
/** `$XDG_CONFIG_HOME/volter/kek`, else `~/.config/volter/kek`: the key every credential this user
|
|
32
32
|
* seals is wrapped under. Made on first use, 0600, never printed. */
|
|
33
|
-
export { userKek, userKekPath } from '@volter/world-core';
|
|
33
|
+
export { userKek, userKekPath } from '@volter/world-core/runtime';
|
|
34
34
|
|
|
35
35
|
// ── the sealed credential, beside the world ─────────────────────────────────────────────────
|
|
36
36
|
|
|
@@ -64,13 +64,17 @@ export async function rewrapTwinCredentials(worldRoot: string): Promise<{ rewrap
|
|
|
64
64
|
return out;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/** Seal a vendor's credential beside the World,
|
|
68
|
-
*
|
|
69
|
-
|
|
67
|
+
/** Seal a vendor's credential beside the World, under the current sealer (the host's vault, else the key: company
|
|
68
|
+
* decision 0034), bound to `bound` (destinationOf the root's or link's origin): it opens for that destination only, so
|
|
69
|
+
* pointing the root or link elsewhere never sends it there. Its ingest door's signing secret is sealed with it
|
|
70
|
+
* (architecture, "Ingest"): the one the vendor issued, given in the payload (`signingSecret`), else one minted here and
|
|
71
|
+
* answered once, to enter at the vendor where the operator chooses the webhook's secret. */
|
|
72
|
+
export async function sealTwinCredential(worldRoot: string, vendor: string, payload: CredentialPayload, at: string | undefined, bound: string): Promise<{ placedAt: string; fingerprint: string; mintedSigningSecret?: string }> {
|
|
70
73
|
if (!/^origin=https?:\/\/[^/]+$/.test(bound)) throw new Error(`a credential is sealed for a destination (destinationOf its root's or link's origin), not ${JSON.stringify(bound)}`);
|
|
71
|
-
const
|
|
74
|
+
const minted = typeof payload.signingSecret === 'string' && payload.signingSecret.length >= 8 ? undefined : randomBytes(24).toString('hex');
|
|
75
|
+
const sealed = await currentSealer().seal(minted ? { ...payload, signingSecret: minted } : payload, at ?? new Date().toISOString(), vendor, bound);
|
|
72
76
|
getActiveWorldStore().write(credentialPath(worldRoot, vendor), `${JSON.stringify(sealed, null, 2)}\n`, { secret: true });
|
|
73
|
-
return { placedAt: sealed.placedAt, fingerprint: sealed.fingerprint };
|
|
77
|
+
return { placedAt: sealed.placedAt, fingerprint: sealed.fingerprint, ...(minted ? { mintedSigningSecret: minted } : {}) };
|
|
74
78
|
}
|
|
75
79
|
/** The inventory of credentials sealed before bindings, kept sealed beside them (`.legacy-inventory.json`, its own
|
|
76
80
|
* slot): `pending` maps each such credential to the destination it is bound to, or null when it had none and so is
|
|
@@ -353,67 +357,31 @@ async function packModule(vendor: string, from: string): Promise<Record<string,
|
|
|
353
357
|
const entry = packEntryPath(vendor, from);
|
|
354
358
|
return entry ? await importModule(entry) : null;
|
|
355
359
|
}
|
|
356
|
-
/** The pack's
|
|
357
|
-
*
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
if (installed) return packEntry(installed);
|
|
361
|
-
const checkout = resolve(import.meta.dir, '..', '..', 'twin', vendor, 'src', 'index.ts');
|
|
362
|
-
return existsSync(checkout) ? checkout : null;
|
|
363
|
-
}
|
|
364
|
-
/** The pack's mirror module (`src/<vendor>-mirror-ui.ts`: its shell, client and stylesheet), read as the
|
|
365
|
-
* hosted mirror build reads it (apps/cloud/scripts/mirrors.ts); a pack's index need not re-export them. */
|
|
366
|
-
export async function packMirrorExports(vendor: string, from: string): Promise<Record<string, unknown> | null> {
|
|
367
|
-
const entry = packEntryPath(vendor, from);
|
|
368
|
-
if (!entry) return null;
|
|
369
|
-
const dir = dirname(entry);
|
|
370
|
-
const file = existsSync(dir) ? readdirSync(dir).find((f) => /-mirror-ui\.(ts|js)$/.test(f) && !f.includes('.test.')) : undefined;
|
|
371
|
-
return importModule(file ? join(dir, file) : entry);
|
|
360
|
+
/** The pack's directory: installed beside the World, in its pack repository, or in the pack repository beside this
|
|
361
|
+
* platform checkout. */
|
|
362
|
+
function packDir(vendor: string, from: string): string | undefined {
|
|
363
|
+
return vendorPackageDir(from, vendor) ?? vendorPackageDir(import.meta.dir, vendor);
|
|
372
364
|
}
|
|
373
|
-
/**
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
* state at `root`, the credential minted for it through the twin's own doors (`twin`), never a stored one read back */
|
|
378
|
-
signIn?: (as: string, ctx: MirrorSignInContext) => Promise<{ account: string; token: string } | null> | { account: string; token: string } | null };
|
|
379
|
-
/** What a pack's `<vendor>MirrorSignIn` is given: the twin's state root (to find the account) and the twin's own doors
|
|
380
|
-
* (to mint a credential for it, as the World's seed does through `/_twin/…`). */
|
|
381
|
-
export type MirrorSignInContext = { root: string; twin: (path: string, init?: RequestInit) => Promise<Response> };
|
|
382
|
-
export async function packMirror(vendor: string, from: string): Promise<PackMirror | null> {
|
|
383
|
-
const installed = findInstalledPackage(from, twinPackageName(vendor));
|
|
384
|
-
const dir = installed ?? resolve(import.meta.dir, '..', '..', 'twin', vendor);
|
|
385
|
-
// the pack's mirror module, as the hosted build reads it (its index need not re-export every builder)
|
|
386
|
-
const src = join(dir, 'src');
|
|
387
|
-
const file = existsSync(src) ? readdirSync(src).find((f) => f.endsWith('-mirror-ui.ts')) : undefined;
|
|
388
|
-
const entry = file ? join(src, file) : installed ? packEntry(installed) : join(src, 'index.ts');
|
|
389
|
-
if (!existsSync(entry)) return null;
|
|
390
|
-
const mod = await importModule(entry);
|
|
391
|
-
const pick = (re: RegExp): ((...a: unknown[]) => unknown) | undefined => { const k = Object.keys(mod).find((key) => re.test(key) && typeof mod[key] === 'function'); return k ? mod[k] as (...a: unknown[]) => unknown : undefined; };
|
|
392
|
-
const html = pick(/^\w+MirrorHtml$/); const client = pick(/^build\w+MirrorClient$/); const styles = pick(/^\w+MirrorStyles$/); const signIn = pick(/^\w+MirrorSignIn$/);
|
|
393
|
-
if (!html || !client) return null;
|
|
394
|
-
return { html: html as () => string, client: client as () => Promise<string>, ...(styles ? { styles: styles as () => Promise<string> } : {}), ...(signIn ? { signIn: signIn as PackMirror['signIn'] } : {}) };
|
|
365
|
+
/** The pack's module itself: its every export, where the registry holds only the descriptor. */
|
|
366
|
+
function packEntryPath(vendor: string, from: string): string | null {
|
|
367
|
+
const dir = packDir(vendor, from);
|
|
368
|
+
return dir === undefined ? null : packEntry(dir);
|
|
395
369
|
}
|
|
396
|
-
|
|
397
|
-
/** The real state system's adapters for a vendor: registered with the pack (protocol 2), else the
|
|
398
|
-
* pack's anchored exports (protocol 1: `perform<Name>Action`, `sync<Name>FromRemote`). */
|
|
370
|
+
/** The real state system's adapters for a vendor: the ones its pack names on its descriptor. */
|
|
399
371
|
export type IngestAdapter = (request: Request, ctx: { root?: string; secret?: string }) => Response | Promise<Response>;
|
|
400
|
-
export async function adaptersFor(vendor: string, from: string): Promise<{ perform?: PerformAction; refresh?: (execute: RemoteExecute, opts: { root?: string; origin?: string; credential?: string }) => Promise<unknown>; ingest?: IngestAdapter }> {
|
|
372
|
+
export async function adaptersFor(vendor: string, from: string): Promise<{ perform?: PerformAction; refresh?: (execute: RemoteExecute, opts: { root?: string; origin?: string; credential?: string; scope?: string }) => Promise<unknown>; ingest?: IngestAdapter }> {
|
|
401
373
|
const registered = stateSystemFor(vendor);
|
|
402
374
|
// a registration is the whole state system for that twin (a stub in a test, a pack's own
|
|
403
|
-
// descriptor);
|
|
375
|
+
// descriptor); an unregistered twin's are read from its pack's descriptor
|
|
404
376
|
if (registered) return registered;
|
|
405
377
|
const mod = await packModule(vendor, from);
|
|
406
378
|
if (!mod) return {};
|
|
407
|
-
const pack = mod.pack as { protocol?: string; stateSystem?: { perform?: PerformAction; refresh?: (execute: RemoteExecute, opts: { root?: string; origin?: string; credential?: string }) => Promise<unknown> } } | undefined;
|
|
379
|
+
const pack = mod.pack as { protocol?: string; vendorBacked?: { none: string }; stateSystem?: { perform?: PerformAction; refresh?: (execute: RemoteExecute, opts: { root?: string; origin?: string; credential?: string; scope?: string }) => Promise<unknown> } } | undefined;
|
|
380
|
+
if (pack?.vendorBacked?.none) throw new Error(`the ${vendor} pack has no vendor-backed half: ${pack.vendorBacked.none}`);
|
|
408
381
|
if (pack?.stateSystem?.perform) { registerStateSystem(vendor, pack.stateSystem); return pack.stateSystem; }
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
const name = (re: RegExp) => Object.keys(mod).find((k) => re.test(k) && typeof mod[k] === 'function');
|
|
413
|
-
const performName = name(/^perform\w+Action$/); const refreshName = name(/^refresh\w+$/) ?? name(/^sync\w+FromRemote$/); const ingestName = name(/^ingest\w+Event$/);
|
|
414
|
-
const out = { ...(performName ? { perform: mod[performName] as PerformAction } : {}), ...(refreshName ? { refresh: mod[refreshName] as (execute: RemoteExecute, opts: { root?: string; origin?: string; credential?: string }) => Promise<unknown> } : {}), ...(ingestName ? { ingest: mod[ingestName] as IngestAdapter } : {}) };
|
|
415
|
-
if (out.perform) registerStateSystem(vendor, out);
|
|
416
|
-
return out;
|
|
382
|
+
// the kernel derives a pack's adapters from its manifest and surface (docs/contributing/architecture.md, "The
|
|
383
|
+
// real-system adapters"): its index registers packOf(manifest, surface)
|
|
384
|
+
throw new Error(`the ${vendor} pack derives no adapters: its index registers \`packOf(manifest)\` without its surface (\`create-pack <vendor> --index\` writes it with it), and its resources' \`refresh\` scopes say what a refresh reads`);
|
|
417
385
|
}
|
|
418
386
|
|
|
419
387
|
// ── the world's checks ──────────────────────────────────────────────────────────────────────
|
|
@@ -474,7 +442,7 @@ export async function refreshTwin(opts: { worldRoot: string; vendor: string; con
|
|
|
474
442
|
const store = getActiveWorldStore();
|
|
475
443
|
let value: unknown; let observations: Observation[];
|
|
476
444
|
try {
|
|
477
|
-
({ value, observations } = await collectObservations(() => adapters.refresh!(buildRemoteExecute(origin, credential, authStrategyFor(opts.vendor), getPack(opts.vendor)?.hosts, { custody: rootCustody(opts.root, opts.vendor) }), { root: opts.controlRoot, origin: adapterOrigin, ...(credentialFingerprint ? { credential: credentialFingerprint } : {}) })));
|
|
445
|
+
({ value, observations } = await collectObservations(() => adapters.refresh!(buildRemoteExecute(origin, credential, authStrategyFor(opts.vendor), getPack(opts.vendor)?.hosts, { custody: rootCustody(opts.root, opts.vendor), vendor: opts.vendor }), { root: opts.controlRoot, origin: adapterOrigin, ...(opts.root.scope ? { scope: opts.root.scope } : {}), ...(credentialFingerprint ? { credential: credentialFingerprint } : {}) })));
|
|
478
446
|
} catch (error) {
|
|
479
447
|
// the vendor was asked and did not answer: the marker keeps the last fold's numbers and records the
|
|
480
448
|
// failure — its reason and how many in a row — so the status door and the twin's status say so
|
|
@@ -10,7 +10,7 @@ import net from 'node:net';
|
|
|
10
10
|
import { spawnSync } from 'node:child_process';
|
|
11
11
|
import { activateScript, doctorWorld, downWorld, ensureWorldProxy, runWithWorldEnv, runWorld, shareWorld, shareWorldServices, statusWorld, unshareWorld, upWorld, urlsWorld, worldShellEnv } from './runtime.ts';
|
|
12
12
|
import { opensslAvailable } from './redirect-proxy.ts';
|
|
13
|
-
import { stateDirName } from '@volter/world-core';
|
|
13
|
+
import { stateDirName } from '@volter/world-core/runtime';
|
|
14
14
|
|
|
15
15
|
/** Test-only seam (see runtime.ts `proxyDaemonDeadlineMs`): force the daemon-poll deadline to `ms`
|
|
16
16
|
* for the duration of `fn`, then restore whatever was there before — deterministic, no sleeps. */
|