@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.
Files changed (121) hide show
  1. package/dist/known-external-services.json +5 -4
  2. package/dist/src/actions-runner-cli.d.ts +2 -0
  3. package/dist/src/actions-runner-cli.js +25 -0
  4. package/dist/src/actions-runner.d.ts +18 -0
  5. package/dist/src/actions-runner.js +281 -0
  6. package/dist/src/ancestry.js +1 -1
  7. package/dist/src/app-url.js +1 -1
  8. package/dist/src/backings.d.ts +29 -0
  9. package/dist/src/backings.js +45 -0
  10. package/dist/src/branch.js +3 -2
  11. package/dist/src/browser-proxy-cli.js +1 -1
  12. package/dist/src/catalog-index.d.ts +38 -0
  13. package/dist/src/catalog-index.js +37 -0
  14. package/dist/src/catalog.d.ts +16 -3
  15. package/dist/src/catalog.js +69 -26
  16. package/dist/src/changeset.d.ts +1 -1
  17. package/dist/src/changeset.js +9 -11
  18. package/dist/src/cli-args.d.ts +4 -0
  19. package/dist/src/cli-args.js +18 -0
  20. package/dist/src/cli-commands.d.ts +1 -0
  21. package/dist/src/cli-commands.js +1216 -0
  22. package/dist/src/cli.js +8 -1300
  23. package/dist/src/configs.js +1 -1
  24. package/dist/src/console-apart.d.ts +2 -2
  25. package/dist/src/console-apart.js +5 -5
  26. package/dist/src/consumers.js +1 -1
  27. package/dist/src/covers.d.ts +1 -1
  28. package/dist/src/covers.js +94 -30
  29. package/dist/src/env-output.d.ts +1 -1
  30. package/dist/src/env-output.js +2 -2
  31. package/dist/src/fixture-env.js +18 -26
  32. package/dist/src/host-cli.js +4 -2
  33. package/dist/src/host-fault-fixture.js +6 -0
  34. package/dist/src/host-ready.d.ts +8 -2
  35. package/dist/src/host-ready.js +16 -4
  36. package/dist/src/host-worker.js +6 -1
  37. package/dist/src/host.d.ts +2 -0
  38. package/dist/src/host.js +9 -1
  39. package/dist/src/index.d.ts +2 -2
  40. package/dist/src/index.js +2 -2
  41. package/dist/src/init.d.ts +33 -23
  42. package/dist/src/init.js +228 -96
  43. package/dist/src/lifecycle-record.js +2 -2
  44. package/dist/src/local-branches.js +1 -1
  45. package/dist/src/origin.d.ts +1 -1
  46. package/dist/src/origin.js +12 -10
  47. package/dist/src/pack-facts.d.ts +30 -6
  48. package/dist/src/pack-facts.js +13 -3
  49. package/dist/src/pglite-backing.js +2 -1
  50. package/dist/src/placeholder.d.ts +1 -1
  51. package/dist/src/placeholder.js +12 -4
  52. package/dist/src/project-inspect.d.ts +1 -1
  53. package/dist/src/project-inspect.js +2 -2
  54. package/dist/src/redirect-proxy.d.ts +5 -0
  55. package/dist/src/redirect-proxy.js +8 -1
  56. package/dist/src/redis-backing.js +4 -2
  57. package/dist/src/reflect.js +1 -1
  58. package/dist/src/root.d.ts +11 -33
  59. package/dist/src/root.js +28 -59
  60. package/dist/src/runtime-test-support.js +1 -1
  61. package/dist/src/runtime.d.ts +7 -1
  62. package/dist/src/runtime.js +317 -53
  63. package/dist/src/schema.d.ts +27 -5
  64. package/dist/src/schema.js +13 -12
  65. package/dist/src/served-world.d.ts +11 -35
  66. package/dist/src/served-world.js +54 -141
  67. package/dist/src/tail.js +1 -1
  68. package/dist/src/up-cli.d.ts +3 -0
  69. package/dist/src/up-cli.js +55 -0
  70. package/dist/src/websocket-relay.d.ts +1 -1
  71. package/dist/src/world-origins.d.ts +1 -1
  72. package/dist/src/world-origins.js +1 -1
  73. package/dist/src/world-view.d.ts +1 -1
  74. package/dist/src/world-view.js +2 -2
  75. package/known-external-services.json +5 -4
  76. package/package.json +11 -11
  77. package/src/actions-runner-cli.ts +24 -0
  78. package/src/actions-runner.ts +244 -0
  79. package/src/ancestry.ts +1 -1
  80. package/src/app-url.ts +1 -1
  81. package/src/backings.ts +55 -0
  82. package/src/branch.ts +3 -2
  83. package/src/browser-proxy-cli.ts +1 -1
  84. package/src/catalog-index.ts +44 -0
  85. package/src/catalog.ts +67 -24
  86. package/src/changeset.ts +10 -13
  87. package/src/cli-args.ts +17 -0
  88. package/src/cli-commands.ts +1070 -0
  89. package/src/cli.ts +9 -1139
  90. package/src/configs.ts +1 -1
  91. package/src/console-apart.ts +5 -5
  92. package/src/consumers.ts +1 -1
  93. package/src/covers.ts +83 -30
  94. package/src/env-output.ts +2 -2
  95. package/src/fixture-env.ts +18 -26
  96. package/src/host-cli.ts +4 -2
  97. package/src/host-ready.ts +13 -4
  98. package/src/host-worker.ts +6 -2
  99. package/src/host.ts +11 -2
  100. package/src/index.ts +2 -2
  101. package/src/init.ts +222 -101
  102. package/src/lifecycle-record.ts +2 -2
  103. package/src/local-branches.ts +1 -1
  104. package/src/origin.ts +12 -10
  105. package/src/pack-facts.ts +22 -6
  106. package/src/pglite-backing.ts +2 -1
  107. package/src/placeholder.ts +13 -4
  108. package/src/project-inspect.ts +2 -2
  109. package/src/redirect-proxy.ts +9 -1
  110. package/src/redis-backing.ts +4 -2
  111. package/src/reflect.ts +1 -1
  112. package/src/root.ts +33 -65
  113. package/src/runtime-test-support.ts +1 -1
  114. package/src/runtime.ts +261 -55
  115. package/src/schema.ts +26 -13
  116. package/src/served-world.ts +50 -130
  117. package/src/tail.ts +1 -1
  118. package/src/up-cli.ts +55 -0
  119. package/src/websocket-relay.ts +1 -1
  120. package/src/world-origins.ts +1 -1
  121. 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, withAncestryLock, writeBranchMeta } from '@volter/world-core';
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
- withAncestryLock(() => {
59
- for (const service of services) {
60
- const controlRoot = join(instance.dirs.data, service);
61
- const meta = readBranchMeta(stateServiceOf(controlRoot, service), controlRoot);
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
- withAncestryLock(() => {
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
- withAncestryLock(() => {
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 (adding-a-twin.md §3) (adding-a-twin.md §3 (the descriptor is the one home)), the
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
- const stem = stemRaw.toLowerCase();
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
  }
@@ -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
  }
@@ -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
- /** Pull from the placeholder: every twin's window open around the seed entry. */
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
- const services = Object.keys(instance.services).sort();
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
  }
@@ -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 `packages/twin/<pack>/package.json` devDependencies);
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 (adding-a-twin.md §3), COMPLETE for this table: every pack declares its own sdk names
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
@@ -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
@@ -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 (packages/twin/redis/src/redis-storage.ts). It needs nothing
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, readdirSync, readFileSync } from 'node:fs';
12
+ import { existsSync, readFileSync } from 'node:fs';
12
13
  import { homedir } from 'node:os';
13
- import { basename, dirname, join, relative, resolve } from 'node:path';
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 PushReport, type RemoteExecute, type RootConfig, type SealedCredential, type TwinAction, validateRemoteOrigin, writeRoot,
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, bound to `bound` (destinationOf the root's or link's origin): it opens
68
- * for that destination only, so pointing the root or link elsewhere never sends it there. */
69
- export async function sealTwinCredential(worldRoot: string, vendor: string, payload: CredentialPayload, at: string | undefined, bound: string): Promise<{ placedAt: string; fingerprint: string }> {
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 sealed = await currentSealer().seal(payload, at ?? new Date().toISOString(), vendor, bound);
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 module itself, installed beside the World or in this checkout: its every export (a
357
- * mirror's shell and client), where the registry holds only the descriptor. */
358
- function packEntryPath(vendor: string, from: string): string | null {
359
- const installed = findInstalledPackage(from, twinPackageName(vendor));
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
- /** A pack's mirror as the pack builds it (`<vendor>MirrorHtml`, `build<Vendor>MirrorClient`,
374
- * `<vendor>MirrorStyles`), found the way a World finds the pack; null when the pack has none. The
375
- * client builds at serve under Bun and is served prebuilt under Node, by the pack's own builder. */
376
- export type PackMirror = { html: () => string; client: () => Promise<string>; styles?: () => Promise<string>; /** the account and a credential its screens sign in with (`<vendor>MirrorSignIn`): the account found in the twin's
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); only an unregistered twin is resolved from the pack's anchored exports
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
- // a protocol 2 pack NAMES its adapters (docs/contributing/architecture.md#protocol-2-the-pack-is-a-plugin); only a protocol 1
410
- // pack is resolved from its anchored exports
411
- if (protocolStanding(pack?.protocol).major >= 2) throw new Error(`the ${vendor} pack declares protocol 2 but names no adapters — \`stateSystem: { perform, refresh?, ingest? }\` on its descriptor`);
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. */