@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
@@ -8,16 +8,18 @@ export type PackAdoption = {
8
8
  package: string;
9
9
  usage: 'build' | 'deployment';
10
10
  }>;
11
+ configFiles?: Array<{
12
+ file: string;
13
+ usage: 'application' | 'build' | 'deployment';
14
+ when?: string;
15
+ }>;
11
16
  };
12
17
  export type PackFacts = {
18
+ serverBacking?: {
19
+ command: string;
20
+ };
13
21
  /** Existing descriptor transport, compiled into the same artifact as endpoint/adoption facts. */
14
22
  transport?: string;
15
- /** Optional native frontend of the same state owner, selected explicitly on the pack CLI. */
16
- nativeTransport?: {
17
- protocol: string;
18
- flag: string;
19
- upstreamEnv: string;
20
- };
21
23
  adoption?: PackAdoption;
22
24
  hosts?: Array<{
23
25
  host?: string;
@@ -34,16 +36,37 @@ export type PackFacts = {
34
36
  note: string;
35
37
  };
36
38
  endpointEnvNone?: string;
39
+ /** The door that issues the application a credential the twin accepts (TwinPack.credentialDoor). */
40
+ credentialDoor?: {
41
+ path: string;
42
+ body: Record<string, unknown>;
43
+ fill: Record<string, string>;
44
+ };
45
+ credentialIssuer?: string;
37
46
  /** The pack's colocatable `create<Name>TwinServer` factory export, derived (or descriptor-
38
47
  * declared under ambiguity) by scripts/pack-facts.ts. Consumed by `init` to emit
39
48
  * `colocate:` service entries so a world's twins boot inside ONE host process (R2a). */
40
49
  serveExport?: string;
50
+ /** The managed-infrastructure kind this pack is the World's backing for (architecture.md, "The descriptor"). */
51
+ managedService?: {
52
+ kind: 'redis' | 'mongodb';
53
+ scheme: string;
54
+ note: string;
55
+ };
41
56
  /** The World's managed database the runtime binds the twin to at boot (TwinPack.managedDatabase). */
42
57
  managedDatabase?: {
43
58
  kind: 'postgres';
44
59
  arg: string;
45
60
  note: string;
46
61
  };
62
+ /** Listeners the twin opens beside its main port, allocated and recorded by the runtime (TwinPack.sidePorts). */
63
+ sidePorts?: Array<{
64
+ name: string;
65
+ option: string;
66
+ flag: string;
67
+ path?: string;
68
+ note: string;
69
+ }>;
47
70
  /** R16: the protocol major the package targets and its standing against the kernel that compiled the artifact. */
48
71
  protocol?: {
49
72
  declared?: string;
@@ -52,6 +75,7 @@ export type PackFacts = {
52
75
  };
53
76
  };
54
77
  export declare function packFacts(): Record<string, PackFacts>;
78
+ export declare function overlayPackFacts(packs: Record<string, PackFacts>): void;
55
79
  /** Overlay descriptor-declared npm SDK names onto the central SDK_TWINS map (project-inspect.ts). */
56
80
  /** The PyPI half of the SDK map, built the same way from each descriptor's `adoption.pypi`
57
81
  * (PEP 503 names). One name belongs to one pack; a second claim refuses at load. */
@@ -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
@@ -13,7 +13,16 @@ import factsDocument from '@volter/world-core/generated/pack-facts.json' with {
13
13
  // core's artifact, imported as a JSON module: the published runtime resolves it beside an installed
14
14
  // core, a bundle carries it, and no host needs a filesystem to read it
15
15
  export function packFacts() {
16
- return factsDocument.packs;
16
+ return merged ??= { ...factsDocument.packs, ...overlaid };
17
+ }
18
+ /** The facts of packs the kernel was not built with: a released pack carries its own (`generated/pack-facts.json`), and
19
+ * the catalog overlays them here, vendor by vendor, over the built-in ones (architecture, "The catalog: where twins
20
+ * come from"). The central tables overlaid at module init keep the built-in adoption facts. */
21
+ const overlaid = {};
22
+ let merged;
23
+ export function overlayPackFacts(packs) {
24
+ Object.assign(overlaid, packs);
25
+ merged = undefined;
17
26
  }
18
27
  const dual = (vendor, kind, key, holder) => new Error(`pack-facts: ${kind} "${key}" is declared BOTH on the ${vendor} pack descriptor AND in the central ${holder} table — ` +
19
28
  `the migration rule is one home per fact. Delete the central entry (the descriptor wins).`);
@@ -65,7 +74,8 @@ export function overlayCoversMaps(maps) {
65
74
  maps.scopeVendors[scope] = vendor;
66
75
  }
67
76
  for (const stemRaw of facts.adoption?.envStems ?? []) {
68
- const stem = stemRaw.toLowerCase();
77
+ // normalized as covers.ts reads a name's stem (`UPSTASH_REDIS_REST_URL` → `upstashredis`)
78
+ const stem = stemRaw.toLowerCase().replace(/[^a-z0-9]/g, '');
69
79
  if (stem in maps.envStemVendors)
70
80
  throw dual(vendor, 'env stem', stem, 'ENV_STEM_VENDORS');
71
81
  maps.envStemVendors[stem] = vendor;
@@ -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.js";
21
+ import { vendorPackageDir } from '@volter/world-core/runtime';
21
22
  /** Parse the services out of world.infrastructure.yml. This is NOT a YAML
22
23
  * parser: init's renderComposeFile emits a deterministic shape, and this
23
24
  * reads exactly that shape back (service key at 2-space indent = the kind;
@@ -83,7 +84,7 @@ function pidPath(dataDir, kind) {
83
84
  * this runtime was installed with (its dependency, or the checkout's workspace link). */
84
85
  export function mongodbTwinCli(worldConfig, froms = [...(worldConfig ? [dirname(worldConfig)] : []), here()]) {
85
86
  for (const from of froms) {
86
- const dir = findInstalledPackage(from, MONGODB_TWIN);
87
+ const dir = vendorPackageDir(from, 'mongodb') ?? findInstalledPackage(from, MONGODB_TWIN);
87
88
  const cli = dir === undefined ? undefined : packCli(dir);
88
89
  if (cli !== undefined)
89
90
  return cli;
@@ -12,7 +12,7 @@ export type SeedWorldOutcome = {
12
12
  };
13
13
  /** The seed entry `init` emits beside the world config, unless the caller names one. */
14
14
  export declare function seedEntryFor(instance: WorldInstance, entry?: string): string;
15
- /** Pull from the placeholder: every twin's window open around the seed entry. */
15
+ /** Pull from the placeholder: every twin's window open around the seed entry (a twin with a root excepted). */
16
16
  export declare function seedWorld(name: string, options?: SeedWorldOptions): Promise<SeedWorldOutcome>;
17
17
  /** Back to the defaults: down with purge, up from the same config and env file, seed. */
18
18
  export declare function resetWorld(name: string, options?: SeedWorldOptions): Promise<SeedWorldOutcome & {
@@ -9,7 +9,7 @@ import { materializeRoots } from "./root.js";
9
9
  import { loadWorldConfig } from "./configs.js";
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.js";
14
14
  /** The seed entry `init` emits beside the world config, unless the caller names one. */
15
15
  export function seedEntryFor(instance, entry) {
@@ -36,12 +36,20 @@ function placeholderCount(controlRoot) {
36
36
  }
37
37
  return n;
38
38
  }
39
- /** Pull from the placeholder: every twin's window open around the seed entry. */
39
+ /** The twins of a World's config that have a root (a vendor's real account behind them). */
40
+ function rootedTwins(instance, root) {
41
+ const { config } = loadWorldConfig(existsSync(instance.configPath) ? instance.configPath : instance.config, root);
42
+ return config.services.filter((s) => s.root).map((s) => s.id);
43
+ }
44
+ /** Pull from the placeholder: every twin's window open around the seed entry (a twin with a root excepted). */
40
45
  export async function seedWorld(name, options = {}) {
41
46
  const root = resolve(options.root ?? process.cwd());
42
47
  const instance = statusWorld(name, root);
43
48
  const entry = seedEntryFor(instance, options.entry);
44
- const services = Object.keys(instance.services).sort();
49
+ // a vendor-backed twin is not seeded (architecture, "Refresh"): its state is its root's, and the default data's runner
50
+ // skips the twins named in VOLTER_SEED_SKIP
51
+ const rooted = new Set(rootedTwins(instance, root));
52
+ const services = Object.keys(instance.services).filter((s) => !rooted.has(s)).sort();
45
53
  const at = new Date().toISOString();
46
54
  for (const service of services)
47
55
  beginPlaceholderPull(service, join(instance.dirs.data, service), at);
@@ -56,7 +64,7 @@ export async function seedWorld(name, options = {}) {
56
64
  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}`);
57
65
  }
58
66
  const runner = typeof Bun !== 'undefined' ? ['bun', entry] : [process.execPath, '--experimental-strip-types', '--no-warnings', entry];
59
- 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(',') } });
60
68
  }
61
69
  finally {
62
70
  for (const service of services)
@@ -19,7 +19,7 @@ export type ProjectInspection = {
19
19
  /** npm package → vendor/twin mapping — the shared vendor↔SDK knowledge used by both
20
20
  * `inspect-project` (adoption discovery) and `covers` (room-setup coverage proof).
21
21
  * The canonical entry for each pack is the real client package that pack's own tests
22
- * prove the twin against (see each `packages/twin/<pack>/package.json` devDependencies);
22
+ * prove the twin against (see each pack's package.json devDependencies);
23
23
  * the extra aliases are the vendor's other official/first-party clients of the same
24
24
  * API surface. */
25
25
  export declare const SDK_TWINS: Record<string, {
@@ -5,13 +5,13 @@ import { normalizePypiName, overlayPypiTwins, overlaySdkTwins } from "./pack-fac
5
5
  /** npm package → vendor/twin mapping — the shared vendor↔SDK knowledge used by both
6
6
  * `inspect-project` (adoption discovery) and `covers` (room-setup coverage proof).
7
7
  * The canonical entry for each pack is the real client package that pack's own tests
8
- * prove the twin against (see each `packages/twin/<pack>/package.json` devDependencies);
8
+ * prove the twin against (see each pack's package.json devDependencies);
9
9
  * the extra aliases are the vendor's other official/first-party clients of the same
10
10
  * API surface. */
11
11
  export const SDK_TWINS = {
12
12
  // (empty — every entry has moved onto its pack descriptor. See the note below.)
13
13
  };
14
- // descriptor-first migration (adding-a-twin.md §3), COMPLETE for this table: every pack declares its own sdk names
14
+ // descriptor-first migration (architecture.md, "The descriptor"), COMPLETE for this table: every pack declares its own sdk names
15
15
  // (`adoption.sdks` on its descriptor, compiled into the committed pack-facts artifact), and the
16
16
  // literal above is now empty — the map is populated entirely by the overlay below. It is kept as
17
17
  // the overlay's target, not as a home: the exported object identity is what every use site already
@@ -48,6 +48,11 @@ type CaPaths = {
48
48
  caKey: string;
49
49
  caCert: string;
50
50
  };
51
+ /** Whether this host opens raw sockets to other hosts, which the ambient TLS redirect proxy exists to intercept. A
52
+ * browser tab's Node (tabnode, which declares `process.versions.webcontainer`) opens none: its TLS is loopback pairing
53
+ * with no handshake, and a remote HTTPS call leaves through the page's HTTP transport. There no proxy can run and none
54
+ * is needed: the World's network policy is enforced by the injector in every Node program (world-core inject.cjs). */
55
+ export declare function hostHasRawEgress(versions?: Record<string, string | undefined>): boolean;
51
56
  export declare function opensslAvailable(): boolean;
52
57
  /** Generate (once) a throwaway CA in `tlsDir`. Idempotent: reuses an existing CA so a re-activate in
53
58
  * the same world trusts the same cert. Session/project-scoped — it lives under the instance dir and
@@ -1,4 +1,4 @@
1
- import { serveHttp } from '@volter/world-core';
1
+ import { serveHttp } from '@volter/world-core/runtime';
2
2
  import { worldNetworkPolicyFromEnv, allowsWorldNetworkEgress } from '@volter/world-core/network-policy';
3
3
  import { websocketRelay } from "./websocket-relay.js";
4
4
  // The ambient TLS redirect proxy — the substance of "any CLI, zero config" (WORLD_ACTIVATE.md §"How
@@ -191,6 +191,13 @@ function shouldBlockUntwinned(value, env) {
191
191
  function caPaths(tlsDir) {
192
192
  return { dir: tlsDir, caKey: join(tlsDir, 'ca-key.pem'), caCert: join(tlsDir, 'ca-cert.pem') };
193
193
  }
194
+ /** Whether this host opens raw sockets to other hosts, which the ambient TLS redirect proxy exists to intercept. A
195
+ * browser tab's Node (tabnode, which declares `process.versions.webcontainer`) opens none: its TLS is loopback pairing
196
+ * with no handshake, and a remote HTTPS call leaves through the page's HTTP transport. There no proxy can run and none
197
+ * is needed: the World's network policy is enforced by the injector in every Node program (world-core inject.cjs). */
198
+ export function hostHasRawEgress(versions = process.versions) {
199
+ return versions.webcontainer === undefined;
200
+ }
194
201
  export function opensslAvailable() {
195
202
  // TEST-ONLY seam (TWIN-64): forces "openssl unavailable" deterministically. A PATH-shim (fake
196
203
  // `openssl` on PATH) doesn't reliably work here because `spawnSync('openssl', …)` below is called
@@ -6,19 +6,20 @@
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.js";
16
+ import { vendorPackageDir } from '@volter/world-core/runtime';
16
17
  const PACKAGE = '@volter/twin-redis';
17
18
  const READY_TIMEOUT_MS = 60_000;
18
19
  /** The redis twin's cli: installed beside this runtime (its node_modules walk), else beside the World's config. */
19
20
  export function redisTwinCli(from) {
20
21
  for (const dir of from) {
21
- const found = findInstalledPackage(dir, PACKAGE);
22
+ const found = vendorPackageDir(dir, 'redis') ?? findInstalledPackage(dir, PACKAGE);
22
23
  const cli = found === undefined ? undefined : packCli(found);
23
24
  if (cli !== undefined && existsSync(cli))
24
25
  return cli;
@@ -69,6 +70,7 @@ export async function redisTwinUp(services, dataDir, cli, launch = {}) {
69
70
  const child = spawn(process.execPath, [...runtime, cli, 'serve', '--port', String(service.hostPort), '--root', root], {
70
71
  env: launch.env ?? process.env,
71
72
  detached: true,
73
+ windowsHide: true,
72
74
  stdio: ['ignore', logFd, logFd],
73
75
  });
74
76
  child.unref();
@@ -1,4 +1,4 @@
1
- import { serveHttp } from '@volter/world-core';
1
+ import { serveHttp } from '@volter/world-core/runtime';
2
2
  import { websocketRelay } from "./websocket-relay.js";
3
3
  import { sessionTrustEnv } from "./ca-trust.js";
4
4
  // The reflect attachment (docs/guides/route-a-cli-through-the-world.md): attach an UNMODIFIED consumer —
@@ -1,9 +1,9 @@
1
- import { type Check, type CredentialPayload, type PerformAction, type RemoteExecute, type RootConfig } from '@volter/world-core';
2
- import { loadChecks, type DeployReport, type ObserveReport } from '@volter/world-core';
1
+ import { type Check, type CredentialPayload, type PerformAction, type RemoteExecute, type RootConfig } from '@volter/world-core/runtime';
2
+ import { loadChecks, type DeployReport, type ObserveReport } from '@volter/world-core/runtime';
3
3
  import type { WorldConfig } from './schema.js';
4
4
  /** `$XDG_CONFIG_HOME/volter/kek`, else `~/.config/volter/kek`: the key every credential this user
5
5
  * seals is wrapped under. Made on first use, 0600, never printed. */
6
- export { userKek, userKekPath } from '@volter/world-core';
6
+ export { userKek, userKekPath } from '@volter/world-core/runtime';
7
7
  export declare function credentialPath(worldRoot: string, vendor: string): string;
8
8
  /** Rewrap every credential sealed beside the World (each vendor's and each link's) under the newest version of the key
9
9
  * that wrapped it: an operator's act after rotating the key, so no record still needs an old version. `current` is only
@@ -15,11 +15,15 @@ export declare function rewrapTwinCredentials(worldRoot: string): Promise<{
15
15
  current: string[];
16
16
  left: string[];
17
17
  }>;
18
- /** Seal a vendor's credential beside the World, bound to `bound` (destinationOf the root's or link's origin): it opens
19
- * for that destination only, so pointing the root or link elsewhere never sends it there. */
18
+ /** Seal a vendor's credential beside the World, under the current sealer (the host's vault, else the key: company
19
+ * decision 0034), bound to `bound` (destinationOf the root's or link's origin): it opens for that destination only, so
20
+ * pointing the root or link elsewhere never sends it there. Its ingest door's signing secret is sealed with it
21
+ * (architecture, "Ingest"): the one the vendor issued, given in the payload (`signingSecret`), else one minted here and
22
+ * answered once, to enter at the vendor where the operator chooses the webhook's secret. */
20
23
  export declare function sealTwinCredential(worldRoot: string, vendor: string, payload: CredentialPayload, at: string | undefined, bound: string): Promise<{
21
24
  placedAt: string;
22
25
  fingerprint: string;
26
+ mintedSigningSecret?: string;
23
27
  }>;
24
28
  /** Bind each credential sealed before bindings to its destination, keeping its placedAt, fingerprint and rotatedAt,
25
29
  * and only over the record that was read; and move every record still under the host's own key (v1) to the vault,
@@ -73,34 +77,7 @@ export declare function materializeRoots(config: WorldConfig, dataDir: string, w
73
77
  /** The root a state service under `controlRoot` is bound to — its own root.json, else a sibling
74
78
  * state's (slack records under `chat`; the root was written under `slack`). */
75
79
  export declare function rootForControlRoot(controlRoot: string, service: string): MaterializedRoot | null;
76
- /** The pack's mirror module (`src/<vendor>-mirror-ui.ts`: its shell, client and stylesheet), read as the
77
- * hosted mirror build reads it (apps/cloud/scripts/mirrors.ts); a pack's index need not re-export them. */
78
- export declare function packMirrorExports(vendor: string, from: string): Promise<Record<string, unknown> | null>;
79
- /** A pack's mirror as the pack builds it (`<vendor>MirrorHtml`, `build<Vendor>MirrorClient`,
80
- * `<vendor>MirrorStyles`), found the way a World finds the pack; null when the pack has none. The
81
- * client builds at serve under Bun and is served prebuilt under Node, by the pack's own builder. */
82
- export type PackMirror = {
83
- html: () => string;
84
- client: () => Promise<string>;
85
- styles?: () => Promise<string>; /** the account and a credential its screens sign in with (`<vendor>MirrorSignIn`): the account found in the twin's
86
- * state at `root`, the credential minted for it through the twin's own doors (`twin`), never a stored one read back */
87
- signIn?: (as: string, ctx: MirrorSignInContext) => Promise<{
88
- account: string;
89
- token: string;
90
- } | null> | {
91
- account: string;
92
- token: string;
93
- } | null;
94
- };
95
- /** What a pack's `<vendor>MirrorSignIn` is given: the twin's state root (to find the account) and the twin's own doors
96
- * (to mint a credential for it, as the World's seed does through `/_twin/…`). */
97
- export type MirrorSignInContext = {
98
- root: string;
99
- twin: (path: string, init?: RequestInit) => Promise<Response>;
100
- };
101
- export declare function packMirror(vendor: string, from: string): Promise<PackMirror | null>;
102
- /** The real state system's adapters for a vendor: registered with the pack (protocol 2), else the
103
- * pack's anchored exports (protocol 1: `perform<Name>Action`, `sync<Name>FromRemote`). */
80
+ /** The real state system's adapters for a vendor: the ones its pack names on its descriptor. */
104
81
  export type IngestAdapter = (request: Request, ctx: {
105
82
  root?: string;
106
83
  secret?: string;
@@ -111,6 +88,7 @@ export declare function adaptersFor(vendor: string, from: string): Promise<{
111
88
  root?: string;
112
89
  origin?: string;
113
90
  credential?: string;
91
+ scope?: string;
114
92
  }) => Promise<unknown>;
115
93
  ingest?: IngestAdapter;
116
94
  }>;
package/dist/src/root.js CHANGED
@@ -7,13 +7,14 @@
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.js";
11
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
12
+ import { existsSync, readFileSync } from 'node:fs';
12
13
  import { homedir } from 'node:os';
13
14
  import { dirname, join, relative, resolve } from 'node:path';
14
- import { authStrategyFor, currentSealer, destinationOf, sealsThroughVault, openSealedAt, getActiveWorldStore, getPack, buildRemoteExecute, clearRoot, readRoot, registerStateSystem, stateDirName, stateSystemFor, validateRemoteOrigin, writeRoot, } from '@volter/world-core';
15
+ import { authStrategyFor, currentSealer, destinationOf, sealsThroughVault, openSealedAt, getActiveWorldStore, getPack, buildRemoteExecute, clearRoot, readRoot, registerStateSystem, stateDirName, stateSystemFor, validateRemoteOrigin, writeRoot, } from '@volter/world-core/runtime';
15
16
  import { loadWorldConfig, worldChangesetRules, writeWorldConfig } from "./configs.js";
16
- import { changesetReadiness, collectObservations, foldObservations, loadChecks, openRootCredential, performEntries, rootCustody, protocolStanding } from '@volter/world-core';
17
+ import { changesetReadiness, collectObservations, foldObservations, loadChecks, openRootCredential, performEntries, rootCustody, vendorPackageDir } from '@volter/world-core/runtime';
17
18
  import { findWorldChangeset, listWorldChangesets } from "./changeset.js";
18
19
  import { importModule } from "./import-module.js";
19
20
  import { statusWorld } from "./runtime.js";
@@ -21,11 +22,10 @@ import { envNameVendor } from "./covers.js";
21
22
  import { parseEnvFile } from "./init.js";
22
23
  import { fakeEnvValue } from "./fixture-env.js";
23
24
  import { projectManifestDirs } from "./project-inspect.js";
24
- import { findInstalledPackage, twinPackageName } from "./catalog.js";
25
25
  // ── the user's sealing key ──────────────────────────────────────────────────────────────────
26
26
  /** `$XDG_CONFIG_HOME/volter/kek`, else `~/.config/volter/kek`: the key every credential this user
27
27
  * seals is wrapped under. Made on first use, 0600, never printed. */
28
- export { userKek, userKekPath } from '@volter/world-core';
28
+ export { userKek, userKekPath } from '@volter/world-core/runtime';
29
29
  // ── the sealed credential, beside the world ─────────────────────────────────────────────────
30
30
  export function credentialPath(worldRoot, vendor) {
31
31
  return join(resolve(worldRoot), stateDirName(), 'credentials', `${vendor}.json`);
@@ -77,14 +77,18 @@ export async function rewrapTwinCredentials(worldRoot) {
77
77
  }
78
78
  return out;
79
79
  }
80
- /** Seal a vendor's credential beside the World, bound to `bound` (destinationOf the root's or link's origin): it opens
81
- * for that destination only, so pointing the root or link elsewhere never sends it there. */
80
+ /** Seal a vendor's credential beside the World, under the current sealer (the host's vault, else the key: company
81
+ * decision 0034), bound to `bound` (destinationOf the root's or link's origin): it opens for that destination only, so
82
+ * pointing the root or link elsewhere never sends it there. Its ingest door's signing secret is sealed with it
83
+ * (architecture, "Ingest"): the one the vendor issued, given in the payload (`signingSecret`), else one minted here and
84
+ * answered once, to enter at the vendor where the operator chooses the webhook's secret. */
82
85
  export async function sealTwinCredential(worldRoot, vendor, payload, at, bound) {
83
86
  if (!/^origin=https?:\/\/[^/]+$/.test(bound))
84
87
  throw new Error(`a credential is sealed for a destination (destinationOf its root's or link's origin), not ${JSON.stringify(bound)}`);
85
- const sealed = await currentSealer().seal(payload, at ?? new Date().toISOString(), vendor, bound);
88
+ const minted = typeof payload.signingSecret === 'string' && payload.signingSecret.length >= 8 ? undefined : randomBytes(24).toString('hex');
89
+ const sealed = await currentSealer().seal(minted ? { ...payload, signingSecret: minted } : payload, at ?? new Date().toISOString(), vendor, bound);
86
90
  getActiveWorldStore().write(credentialPath(worldRoot, vendor), `${JSON.stringify(sealed, null, 2)}\n`, { secret: true });
87
- return { placedAt: sealed.placedAt, fingerprint: sealed.fingerprint };
91
+ return { placedAt: sealed.placedAt, fingerprint: sealed.fingerprint, ...(minted ? { mintedSigningSecret: minted } : {}) };
88
92
  }
89
93
  /** The inventory of credentials sealed before bindings, kept sealed beside them (`.legacy-inventory.json`, its own
90
94
  * slot): `pending` maps each such credential to the destination it is bound to, or null when it had none and so is
@@ -424,70 +428,35 @@ async function packModule(vendor, from) {
424
428
  const entry = packEntryPath(vendor, from);
425
429
  return entry ? await importModule(entry) : null;
426
430
  }
427
- /** The pack's module itself, installed beside the World or in this checkout: its every export (a
428
- * mirror's shell and client), where the registry holds only the descriptor. */
429
- function packEntryPath(vendor, from) {
430
- const installed = findInstalledPackage(from, twinPackageName(vendor));
431
- if (installed)
432
- return packEntry(installed);
433
- const checkout = resolve(import.meta.dir, '..', '..', 'twin', vendor, 'src', 'index.ts');
434
- return existsSync(checkout) ? checkout : null;
435
- }
436
- /** The pack's mirror module (`src/<vendor>-mirror-ui.ts`: its shell, client and stylesheet), read as the
437
- * hosted mirror build reads it (apps/cloud/scripts/mirrors.ts); a pack's index need not re-export them. */
438
- export async function packMirrorExports(vendor, from) {
439
- const entry = packEntryPath(vendor, from);
440
- if (!entry)
441
- return null;
442
- const dir = dirname(entry);
443
- const file = existsSync(dir) ? readdirSync(dir).find((f) => /-mirror-ui\.(ts|js)$/.test(f) && !f.includes('.test.')) : undefined;
444
- return importModule(file ? join(dir, file) : entry);
431
+ /** The pack's directory: installed beside the World, in its pack repository, or in the pack repository beside this
432
+ * platform checkout. */
433
+ function packDir(vendor, from) {
434
+ return vendorPackageDir(from, vendor) ?? vendorPackageDir(import.meta.dir, vendor);
445
435
  }
446
- export async function packMirror(vendor, from) {
447
- const installed = findInstalledPackage(from, twinPackageName(vendor));
448
- const dir = installed ?? resolve(import.meta.dir, '..', '..', 'twin', vendor);
449
- // the pack's mirror module, as the hosted build reads it (its index need not re-export every builder)
450
- const src = join(dir, 'src');
451
- const file = existsSync(src) ? readdirSync(src).find((f) => f.endsWith('-mirror-ui.ts')) : undefined;
452
- const entry = file ? join(src, file) : installed ? packEntry(installed) : join(src, 'index.ts');
453
- if (!existsSync(entry))
454
- return null;
455
- const mod = await importModule(entry);
456
- const pick = (re) => { const k = Object.keys(mod).find((key) => re.test(key) && typeof mod[key] === 'function'); return k ? mod[k] : undefined; };
457
- const html = pick(/^\w+MirrorHtml$/);
458
- const client = pick(/^build\w+MirrorClient$/);
459
- const styles = pick(/^\w+MirrorStyles$/);
460
- const signIn = pick(/^\w+MirrorSignIn$/);
461
- if (!html || !client)
462
- return null;
463
- return { html: html, client: client, ...(styles ? { styles: styles } : {}), ...(signIn ? { signIn: signIn } : {}) };
436
+ /** The pack's module itself: its every export, where the registry holds only the descriptor. */
437
+ function packEntryPath(vendor, from) {
438
+ const dir = packDir(vendor, from);
439
+ return dir === undefined ? null : packEntry(dir);
464
440
  }
465
441
  export async function adaptersFor(vendor, from) {
466
442
  const registered = stateSystemFor(vendor);
467
443
  // a registration is the whole state system for that twin (a stub in a test, a pack's own
468
- // descriptor); only an unregistered twin is resolved from the pack's anchored exports
444
+ // descriptor); an unregistered twin's are read from its pack's descriptor
469
445
  if (registered)
470
446
  return registered;
471
447
  const mod = await packModule(vendor, from);
472
448
  if (!mod)
473
449
  return {};
474
450
  const pack = mod.pack;
451
+ if (pack?.vendorBacked?.none)
452
+ throw new Error(`the ${vendor} pack has no vendor-backed half: ${pack.vendorBacked.none}`);
475
453
  if (pack?.stateSystem?.perform) {
476
454
  registerStateSystem(vendor, pack.stateSystem);
477
455
  return pack.stateSystem;
478
456
  }
479
- // a protocol 2 pack NAMES its adapters (docs/contributing/architecture.md#protocol-2-the-pack-is-a-plugin); only a protocol 1
480
- // pack is resolved from its anchored exports
481
- if (protocolStanding(pack?.protocol).major >= 2)
482
- throw new Error(`the ${vendor} pack declares protocol 2 but names no adapters — \`stateSystem: { perform, refresh?, ingest? }\` on its descriptor`);
483
- const name = (re) => Object.keys(mod).find((k) => re.test(k) && typeof mod[k] === 'function');
484
- const performName = name(/^perform\w+Action$/);
485
- const refreshName = name(/^refresh\w+$/) ?? name(/^sync\w+FromRemote$/);
486
- const ingestName = name(/^ingest\w+Event$/);
487
- const out = { ...(performName ? { perform: mod[performName] } : {}), ...(refreshName ? { refresh: mod[refreshName] } : {}), ...(ingestName ? { ingest: mod[ingestName] } : {}) };
488
- if (out.perform)
489
- registerStateSystem(vendor, out);
490
- return out;
457
+ // the kernel derives a pack's adapters from its manifest and surface (docs/contributing/architecture.md, "The
458
+ // real-system adapters"): its index registers packOf(manifest, surface)
459
+ 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`);
491
460
  }
492
461
  // ── the world's checks ──────────────────────────────────────────────────────────────────────
493
462
  /** `.volter/checks/*.ts|js`: each exports `check` (or default) as `{ name, run }`. The shipped
@@ -550,7 +519,7 @@ export async function refreshTwin(opts) {
550
519
  let value;
551
520
  let observations;
552
521
  try {
553
- ({ 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 } : {}) })));
522
+ ({ 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 } : {}) })));
554
523
  }
555
524
  catch (error) {
556
525
  // the vendor was asked and did not answer: the marker keeps the last fold's numbers and records the
@@ -7,7 +7,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
8
  import net from 'node:net';
9
9
  import { downWorld, statusWorld } from "./runtime.js";
10
- import { stateDirName } from '@volter/world-core';
10
+ import { stateDirName } from '@volter/world-core/runtime';
11
11
  /** Test-only seam (see runtime.ts `proxyDaemonDeadlineMs`): force the daemon-poll deadline to `ms`
12
12
  * for the duration of `fn`, then restore whatever was there before — deterministic, no sleeps. */
13
13
  export async function withProxyDeadline(ms, fn) {
@@ -78,8 +78,10 @@ export declare function sharingClaimAlive(claim: SharingClaim): boolean;
78
78
  * run code that is not Volter's (a World's apps, a command attached to a World, a tunnel, a dependency install) are
79
79
  * given an environment without it. */
80
80
  export declare function withoutVaultSignIn<T extends Record<string, string | undefined>>(env: T): T;
81
- /** The process's environment as the world admits it: what `stripEnv` names stays outside. */
82
81
  export declare function admittedEnv(strip: string[] | undefined, env?: NodeJS.ProcessEnv): Record<string, string>;
82
+ /** The names a twin of this World was to issue and did not (its door failed): kept out of every command's env, so a
83
+ * caller's own value (a real key in their shell) never stands in for the credential the World meant to issue. */
84
+ export declare const UNISSUED_ENV = "VOLTER_WORLD_UNISSUED";
83
85
  export declare function saveWorldInstance(instance: WorldInstance): void;
84
86
  /**
85
87
  * THE MANAGED-DATABASE BINDING (architecture.md, managed infrastructure: the data plane's declared exception): for a
@@ -197,12 +199,16 @@ export declare function listWorlds(root?: string): Array<{
197
199
  degraded?: boolean;
198
200
  config?: string;
199
201
  }>;
202
+ /** The env with each `$app-url` value made the application URL its booter recorded. Unrecorded, the name is left
203
+ * unset (the application's own env files decide) and the command is told how to record it: never a guess. */
204
+ export declare function withAppUrl<E extends Record<string, string | undefined>>(name: string, root: string, env: E): E;
200
205
  /** Runs the command in the world's env in the foreground and resolves to its exit code. The child is
201
206
  * awaited, not blocked on: a runtime that runs no synchronous child (the browser engine) still runs
202
207
  * this one, and the caller sees no difference — stdio is inherited either way. */
203
208
  export declare function runWithWorldEnv(name: string, command: string[], root?: string, options?: {
204
209
  cwd?: string;
205
210
  verbose?: boolean;
211
+ env?: Record<string, string>;
206
212
  }): Promise<number>;
207
213
  /** Local CLI attachment with one instance-bound consumer record per concurrent command.
208
214
  * The synchronous SDK method remains compatible; its consumers are explicitly untracked. */