@volter/world-runtime 2.0.55 → 3.0.1

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
@@ -1,5 +1,5 @@
1
1
  {
2
- "//": "PACKLESS-VENDOR REGISTRY — every external-service-shaped npm dep / env stem the catalog KNOWS but has no pack for, plus the not-a-service suppressions and the external-shape patterns. ONE home (absorbs covers.ts's inline NOT_AN_EXTERNAL_SERVICE / UNKNOWN_DEP_KNOWN_SERVICES / UNKNOWN_DEP_PATTERNS and the 2026-08-31 census rulings ledger — company-layer notes/census-rulings-2026-08-31.md). disposition 'demanded' = a real coverage gap, stays LOUD (the demand queue); 'acknowledged' = adjudicated acceptable, covers auto-acknowledges with the recorded reason. AN ENTRY DIES THE DAY ITS PACK IS BORN (scripts/packless-claims.test.ts enforces disjointness with pack adoption + SDK_TWINS).",
2
+ "//": "PACKLESS-VENDOR REGISTRY — every external-service-shaped npm dep / env stem the catalog KNOWS but has no pack for, plus the not-a-service suppressions and the external-shape patterns. ONE home (absorbs covers.ts's inline NOT_AN_EXTERNAL_SERVICE / UNKNOWN_DEP_KNOWN_SERVICES / UNKNOWN_DEP_PATTERNS and the 2026-08-31 census rulings ledger — company-layer notes/census-rulings-2026-08-31.md). disposition 'demanded' = a real coverage gap, stays LOUD (the demand queue); 'acknowledged' = adjudicated acceptable, covers auto-acknowledges with the recorded reason. AN ENTRY DIES THE DAY ITS PACK IS BORN (scripts/packless-claims.ts enforces disjointness with pack adoption + SDK_TWINS).",
3
3
  "deps": {
4
4
  "@1password/sdk": {
5
5
  "disposition": "demanded",
@@ -407,11 +407,11 @@
407
407
  },
408
408
  "bitly": {
409
409
  "disposition": "demanded",
410
- "reason": "vendor bitly — the twin EXISTS (packages/twin/bitly, born 2026-09-02) and serves this client's traffic through the api-ssl.bitly.com injector entry. The npm name stays listed here rather than in the pack's adoption.sdks because `bitly` is a COMMUNITY wrapper, not a Bitly-published client (Bitly ships no first-party npm SDK; Dub's own importer uses plain fetch), and adoption.sdks is for official clients only."
410
+ "reason": "vendor bitly — the twin EXISTS (@volter/twin-bitly, born 2026-09-02) and serves this client's traffic through the api-ssl.bitly.com injector entry. The npm name stays listed here rather than in the pack's adoption.sdks because `bitly` is a COMMUNITY wrapper, not a Bitly-published client (Bitly ships no first-party npm SDK; Dub's own importer uses plain fetch), and adoption.sdks is for official clients only."
411
411
  },
412
412
  "paypal": {
413
413
  "disposition": "demanded",
414
- "reason": "vendor paypal — the twin EXISTS (packages/twin/paypal, born 2026-09-13) and serves this consumer's payouts, webhooks and Log-in-with-PayPal traffic through the api(-m).(sandbox.)paypal.com and www.(sandbox.)paypal.com injector entries. The bare `paypal` npm name stays listed here rather than in the pack's adoption.sdks because it is NOT a PayPal-published client (PayPal's own npm clients are `paypal-rest-sdk`, which the pack DOES claim, and the @paypal/* checkout SDKs, which model Orders/Payments/Vault — surface this twin does not serve), and adoption.sdks is for official clients of the surface the pack can honor. The bitly precedent."
414
+ "reason": "vendor paypal — the twin EXISTS (@volter/twin-paypal, born 2026-09-13) and serves this consumer's payouts, webhooks and Log-in-with-PayPal traffic through the api(-m).(sandbox.)paypal.com and www.(sandbox.)paypal.com injector entries. The bare `paypal` npm name stays listed here rather than in the pack's adoption.sdks because it is NOT a PayPal-published client (PayPal's own npm clients are `paypal-rest-sdk`, which the pack DOES claim, and the @paypal/* checkout SDKs, which model Orders/Payments/Vault — surface this twin does not serve), and adoption.sdks is for official clients of the surface the pack can honor. The bitly precedent."
415
415
  },
416
416
  "googleads": {
417
417
  "disposition": "demanded",
@@ -419,7 +419,7 @@
419
419
  },
420
420
  "tiktok": {
421
421
  "disposition": "demanded",
422
- "reason": "vendor tiktok — the twin EXISTS (packages/twin/tiktok, born 2026-09-13) and serves this vendor's traffic through the www.tiktok.com and open.tiktokapis.com injector entries. The npm name stays listed here rather than in the pack's adoption.sdks because `tiktok` is a COMMUNITY wrapper, not a TikTok-published client (TikTok ships no first-party npm SDK for Login Kit or the Display API; Dub's own integration uses plain fetch), and adoption.sdks is for official clients only."
422
+ "reason": "vendor tiktok — the twin EXISTS (@volter/twin-tiktok, born 2026-09-13) and serves this vendor's traffic through the www.tiktok.com and open.tiktokapis.com injector entries. The npm name stays listed here rather than in the pack's adoption.sdks because `tiktok` is a COMMUNITY wrapper, not a TikTok-published client (TikTok ships no first-party npm SDK for Login Kit or the Display API; Dub's own integration uses plain fetch), and adoption.sdks is for official clients only."
423
423
  }
424
424
  },
425
425
  "envStems": {
@@ -969,6 +969,7 @@
969
969
  "@nocobase/sdk": "self-instance client; targets the app's own self-hosted deployment, which is local in a keyless world",
970
970
  "@opentelemetry/instrumentation-aws-sdk": "OpenTelemetry instrumentation wrapper, not a service client",
971
971
  "@payloadcms/sdk": "self-instance client; targets the app's own self-hosted deployment, which is local in a keyless world",
972
+ "@prisma/client": "the Prisma ORM client, not a service: its database is a connection, judged by the schema's datasource",
972
973
  "@restatedev/restate-sdk": "self-instance client; targets the app's own self-hosted deployment, which is local in a keyless world",
973
974
  "@rocket.chat/api-client": "Rocket.Chat monorepo internal package, not an external service",
974
975
  "@rocket.chat/federation-sdk": "Rocket.Chat monorepo internal package, not an external service",
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ // volter-world-actions-runner: the World's Actions runner as a process (architecture, "The World's Actions runner").
3
+ // volter-world-actions-runner --github <the World's GitHub twin> --token <a World user's token> --work <dir> [--once]
4
+ // It runs every queued workflow run, then (without --once) looks again every two seconds. Steps inherit this process's
5
+ // environment: start it with the World's (volter-world run), so every call a step makes reaches the World's twins.
6
+ import { hasFlag, optionValue } from '@volter/world-core/args';
7
+ import { mkdirSync } from 'node:fs';
8
+ import { runQueued } from "./actions-runner.js";
9
+ const args = process.argv.slice(2);
10
+ const github = optionValue(args, '--github') ?? process.env.GITHUB_API_URL;
11
+ const token = optionValue(args, '--token') ?? process.env.GITHUB_TOKEN;
12
+ const work = optionValue(args, '--work') ?? '.volter/actions-runner';
13
+ if (!github || !token) {
14
+ process.stderr.write('usage: volter-world-actions-runner --github <url> --token <token> [--work <dir>] [--once]\n');
15
+ process.exit(2);
16
+ }
17
+ mkdirSync(work, { recursive: true });
18
+ const opts = { github, token, workRoot: work, env: {}, log: (line) => process.stdout.write(`${line}\n`) };
19
+ for (;;) {
20
+ const ran = await runQueued(opts);
21
+ if (hasFlag(args, '--once'))
22
+ process.exit(0);
23
+ if (ran === 0)
24
+ await new Promise((r) => setTimeout(r, 2000));
25
+ }
@@ -0,0 +1,18 @@
1
+ export type ActionsRunnerOptions = {
2
+ /** The World's GitHub twin (its API and its git plane at the same origin). */
3
+ github: string;
4
+ /** A token of the World's GitHub user, which lists the runs it may run. */
5
+ token: string;
6
+ /** Where job workspaces are made (under the World's data). */
7
+ workRoot: string;
8
+ /** The World's environment, which every step inherits (its twins' addresses and keys). */
9
+ env: Record<string, string>;
10
+ /** Written to as the runner works. */
11
+ log?: (line: string) => void;
12
+ };
13
+ type Row = Record<string, unknown>;
14
+ /** One run: started, each job's steps run in order of `needs`, completed with its conclusion. */
15
+ export declare function runOnce(opts: ActionsRunnerOptions, repo: string, run: Row): Promise<'success' | 'failure'>;
16
+ /** Every queued run of every repository, one at a time, oldest first. */
17
+ export declare function runQueued(opts: ActionsRunnerOptions): Promise<number>;
18
+ export {};
@@ -0,0 +1,281 @@
1
+ // THE WORLD'S ACTIONS RUNNER — GitHub's hosted runner as a World service (docs/contributing/architecture.md, "The World's
2
+ // Actions runner"). It takes queued workflow runs from the World's GitHub twin (the Actions API lists them; the twin's
3
+ // runner door starts and completes them), checks each run's repository out at its commit over the twin's git plane,
4
+ // and runs its jobs' steps on this machine with GitHub's job environment and the World's own, so every call a step makes
5
+ // to a vendor goes to the World's twin of it. GitHub's own actions it performs itself; any other action fails its step
6
+ // by name.
7
+ //
8
+ // What it does not do (named, so a workflow that needs them fails plainly): matrices beyond their first combination,
9
+ // services and containers, composite and JavaScript actions from the marketplace, reusable workflows, concurrency groups.
10
+ import { spawn } from 'node:child_process';
11
+ import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { parse as parseYaml } from 'yaml';
14
+ async function api(opts, method, path, body, token = opts.token) {
15
+ const res = await fetch(`${opts.github.replace(/\/$/, '')}${path}`, {
16
+ method, headers: { authorization: `Bearer ${token}`, accept: 'application/vnd.github+json', ...(body === undefined ? {} : { 'content-type': 'application/json' }) }, ...(body === undefined ? {} : { body: JSON.stringify(body) }),
17
+ });
18
+ const text = await res.text();
19
+ let parsed = {};
20
+ try {
21
+ parsed = text ? JSON.parse(text) : {};
22
+ }
23
+ catch {
24
+ parsed = { text };
25
+ }
26
+ return { status: res.status, body: parsed };
27
+ }
28
+ /** The repositories the runner serves: the user's own and its organizations'. */
29
+ async function repositories(opts) {
30
+ const out = new Set();
31
+ const mine = await api(opts, 'GET', '/user/repos?per_page=100');
32
+ for (const r of mine.body ?? [])
33
+ if (typeof r?.full_name === 'string')
34
+ out.add(r.full_name);
35
+ const orgs = await api(opts, 'GET', '/user/orgs?per_page=100');
36
+ for (const o of orgs.body ?? []) {
37
+ const repos = await api(opts, 'GET', `/orgs/${String(o.login)}/repos?per_page=100`);
38
+ for (const r of repos.body ?? [])
39
+ if (typeof r?.full_name === 'string')
40
+ out.add(r.full_name);
41
+ }
42
+ return [...out].sort();
43
+ }
44
+ /** `${{ … }}` in a string, as far as workflows use it: secrets, env, github, inputs, matrix, steps' outputs, literals. */
45
+ function expand(text, scope) {
46
+ return text.replace(/\$\{\{\s*([^}]+?)\s*\}\}/g, (_, expr) => {
47
+ const v = evaluate(expr, scope);
48
+ return v === undefined || v === null ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v);
49
+ });
50
+ }
51
+ function lookup(path, scope) {
52
+ return path.split('.').reduce((o, k) => (o && typeof o === 'object' ? o[k] : undefined), scope);
53
+ }
54
+ /** An expression: a literal, a context path, `a == b`, `a != b`, `a && b`, `a || b`, `!a`, and the status functions. */
55
+ function evaluate(expr, scope) {
56
+ const e = expr.trim();
57
+ if (e.includes('||'))
58
+ return e.split('||').map((p) => evaluate(p, scope)).find(Boolean) ?? false;
59
+ if (e.includes('&&'))
60
+ return e.split('&&').every((p) => Boolean(evaluate(p, scope)));
61
+ const cmp = /^(.+?)\s*(==|!=)\s*(.+)$/.exec(e);
62
+ if (cmp) {
63
+ const a = evaluate(cmp[1], scope);
64
+ const b = evaluate(cmp[3], scope);
65
+ return cmp[2] === '==' ? String(a) === String(b) : String(a) !== String(b);
66
+ }
67
+ if (e.startsWith('!'))
68
+ return !evaluate(e.slice(1), scope);
69
+ if (/^'.*'$/.test(e))
70
+ return e.slice(1, -1).replace(/''/g, "'");
71
+ if (/^-?\d+(\.\d+)?$/.test(e))
72
+ return Number(e);
73
+ if (e === 'true' || e === 'false')
74
+ return e === 'true';
75
+ if (e === 'always()')
76
+ return true;
77
+ if (e === 'success()')
78
+ return scope.__failed !== true;
79
+ if (e === 'failure()')
80
+ return scope.__failed === true;
81
+ if (e === 'cancelled()')
82
+ return false;
83
+ const fn = /^(startsWith|endsWith|contains)\((.+),(.+)\)$/.exec(e);
84
+ if (fn) {
85
+ const a = String(evaluate(fn[2], scope) ?? '');
86
+ const b = String(evaluate(fn[3], scope) ?? '');
87
+ return fn[1] === 'startsWith' ? a.startsWith(b) : fn[1] === 'endsWith' ? a.endsWith(b) : a.includes(b);
88
+ }
89
+ return lookup(e, scope);
90
+ }
91
+ const condition = (cond, scope) => (cond === undefined ? scope.__failed !== true : Boolean(evaluate(cond.replace(/^\$\{\{\s*|\s*\}\}$/g, ''), scope)));
92
+ /** A `KEY=value` file (GITHUB_ENV, GITHUB_OUTPUT), heredoc form (`KEY<<EOF … EOF`) included. */
93
+ function readKv(file) {
94
+ if (!existsSync(file))
95
+ return {};
96
+ const out = {};
97
+ const lines = readFileSync(file, 'utf8').split('\n');
98
+ for (let i = 0; i < lines.length; i++) {
99
+ const line = lines[i];
100
+ const heredoc = /^([^=<]+)<<(.+)$/.exec(line);
101
+ if (heredoc) {
102
+ const value = [];
103
+ for (i++; i < lines.length && lines[i] !== heredoc[2]; i++)
104
+ value.push(lines[i]);
105
+ out[heredoc[1]] = value.join('\n');
106
+ }
107
+ else if (line.includes('='))
108
+ out[line.slice(0, line.indexOf('='))] = line.slice(line.indexOf('=') + 1);
109
+ }
110
+ return out;
111
+ }
112
+ function sh(command, cwd, env, shell, log) {
113
+ return new Promise((resolve) => {
114
+ const file = join(cwd, `.volter-step-${Date.now()}-${Math.random().toString(36).slice(2)}.sh`);
115
+ writeFileSync(file, command);
116
+ const argv = shell === 'pwsh' ? ['pwsh', ['-File', file]] : shell === 'sh' ? ['sh', ['-e', file]] : ['bash', ['--noprofile', '--norc', '-eo', 'pipefail', file]];
117
+ const child = spawn(argv[0], [...argv[1]], { cwd, env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
118
+ child.stdout.on('data', (d) => { for (const l of d.toString().split('\n'))
119
+ if (l)
120
+ log(` ${l}`); });
121
+ child.stderr.on('data', (d) => { for (const l of d.toString().split('\n'))
122
+ if (l)
123
+ log(` ${l}`); });
124
+ child.on('close', (code) => { rmSync(file, { force: true }); resolve(code ?? 1); });
125
+ });
126
+ }
127
+ /** One of GitHub's own actions, performed by the runner; an action it cannot perform fails by name. */
128
+ function builtin(step, env, workspace) {
129
+ const uses = String(step.uses);
130
+ const name = uses.replace(/@.*$/, '');
131
+ if (name === 'actions/checkout')
132
+ return { ok: true, message: 'checked out (the workspace is the run\'s commit)' };
133
+ if (name === 'actions/setup-node') {
134
+ // setup-node with a registry-url writes an .npmrc naming it and NODE_AUTH_TOKEN, and points npm at it
135
+ const registry = typeof step.with?.['registry-url'] === 'string' ? String(step.with['registry-url']) : undefined;
136
+ if (registry) {
137
+ const npmrc = join(workspace, '..', '.npmrc-setup-node');
138
+ const host = registry.replace(/^https?:/, '').replace(/\/?$/, '/');
139
+ writeFileSync(npmrc, `registry=${registry}\n${host}:_authToken=\${NODE_AUTH_TOKEN}\nalways-auth=true\n`);
140
+ appendFileSync(env.GITHUB_ENV, `NPM_CONFIG_USERCONFIG=${npmrc}\n`);
141
+ }
142
+ return { ok: true, message: 'node: the World machine\'s' };
143
+ }
144
+ if (name === 'oven-sh/setup-bun')
145
+ return { ok: true, message: 'bun: the World machine\'s' };
146
+ if (name === 'actions/upload-artifact' || name === 'actions/download-artifact' || name === 'actions/cache')
147
+ return { ok: true, message: `${name}: recorded by the GitHub twin` };
148
+ return { ok: false, message: `${uses} is not an action the World's runner performs (GitHub's own: checkout, setup-node, setup-bun, upload-artifact, download-artifact, cache)` };
149
+ }
150
+ /** One run: started, each job's steps run in order of `needs`, completed with its conclusion. */
151
+ export async function runOnce(opts, repo, run) {
152
+ const log = opts.log ?? (() => { });
153
+ const id = Number(run.id);
154
+ const started = await api(opts, 'POST', `/_twin/repos/${repo}/actions/runs/${id}/start`);
155
+ if (started.status !== 200) {
156
+ log(`run ${repo}#${id}: not started (${started.status})`);
157
+ return 'failure';
158
+ }
159
+ const jobToken = String(started.body.token);
160
+ const secrets = started.body.secrets ?? {};
161
+ // a workflow granting id-token: write: where the job asks GitHub's OIDC provider for its token, and with what
162
+ const idTokenRequest = started.body.id_token_request;
163
+ // the repository's configuration variables (`vars`), its organization's under them
164
+ const vars = {};
165
+ for (const path of [`/orgs/${repo.split('/')[0]}/actions/variables?per_page=30`, `/repos/${repo}/actions/variables?per_page=30`]) {
166
+ const v = await api(opts, 'GET', path);
167
+ for (const x of v.body.variables ?? [])
168
+ vars[String(x.name)] = String(x.value);
169
+ }
170
+ const file = await api(opts, 'GET', `/repos/${repo}/contents/${encodeURI(String(run.path))}?ref=${String(run.head_sha)}`);
171
+ // the repository's and its owner's ids, which a hosted runner's job environment names
172
+ const repoInfo = (await api(opts, 'GET', `/repos/${repo}`)).body;
173
+ const workflow = parseYaml(Buffer.from(String(file.body.content ?? ''), 'base64').toString('utf8'));
174
+ const root = mkdtempSync(join(opts.workRoot, `run-${id}-`));
175
+ const workspace = join(root, 'workspace');
176
+ const clone = `${opts.github.replace(/\/$/, '').replace(/^(https?:\/\/)/, `$1x-access-token:${jobToken}@`)}/${repo}.git`;
177
+ const git = async (args, cwd) => sh(`git ${args.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(' ')}`, cwd, { ...process.env, ...opts.env }, 'bash', log);
178
+ let conclusion = 'success';
179
+ try {
180
+ mkdirSync(root, { recursive: true });
181
+ if (await git(['clone', '--quiet', clone, workspace], root) !== 0 || await git(['checkout', '--quiet', String(run.head_sha)], workspace) !== 0)
182
+ throw new Error('the repository could not be checked out from the GitHub twin');
183
+ const jobs = Object.entries(workflow.jobs ?? {});
184
+ const done = new Map();
185
+ const ready = (j) => [].concat(j.needs ?? []).every((n) => done.has(n));
186
+ while (done.size < jobs.length) {
187
+ const next = jobs.find(([k, j]) => !done.has(k) && ready(j));
188
+ if (!next)
189
+ break;
190
+ const [key, job] = next;
191
+ const needsFailed = [].concat(job.needs ?? []).some((n) => done.get(n) !== 'success');
192
+ const matrix = Object.fromEntries(Object.entries(job.strategy?.matrix ?? {}).filter(([k]) => k !== 'include' && k !== 'exclude').map(([k, v]) => [k, Array.isArray(v) ? v[0] : v]));
193
+ const github = { server_url: opts.github, api_url: opts.github, repository: repo, repository_owner: repo.split('/')[0], sha: run.head_sha, ref: `refs/heads/${String(run.head_branch)}`, ref_name: run.head_branch, event_name: run.event, run_id: id, run_number: run.run_number, workflow: workflow.name, job: key, token: jobToken, workspace, actor: run.actor && typeof run.actor === 'object' ? run.actor.login : 'github-actions' };
194
+ const scope = { github, secrets: { ...secrets, GITHUB_TOKEN: jobToken }, vars, matrix, needs: {}, steps: {}, env: {}, __failed: needsFailed };
195
+ if (needsFailed || !condition(job.if, scope)) {
196
+ done.set(key, 'skipped');
197
+ log(`job ${key}: skipped`);
198
+ continue;
199
+ }
200
+ log(`job ${key}: ${job.name ?? key}`);
201
+ const files = { env: join(root, `${key}.env`), output: join(root, `${key}.output`), path: join(root, `${key}.path`) };
202
+ // a hosted runner is a fresh machine: no package manifest cached from an earlier job (a release just published
203
+ // may be newer than it). The tarballs this machine holds are kept, being content-addressed by version; bun's
204
+ // manifests are not read, by a global bunfig for the job
205
+ const config = join(root, `${key}.config`);
206
+ mkdirSync(config, { recursive: true });
207
+ writeFileSync(join(config, '.bunfig.toml'), '[install.cache]\ndisableManifest = true\n');
208
+ for (const f of Object.values(files))
209
+ writeFileSync(f, '');
210
+ const baseEnv = {
211
+ ...process.env, ...opts.env, CI: 'true', GITHUB_ACTIONS: 'true', GITHUB_REPOSITORY: repo, GITHUB_REPOSITORY_OWNER: repo.split('/')[0],
212
+ GITHUB_SHA: String(run.head_sha), GITHUB_REF: String(github.ref), GITHUB_REF_NAME: String(run.head_branch), GITHUB_EVENT_NAME: String(run.event), GITHUB_RUN_ID: String(id),
213
+ GITHUB_RUN_NUMBER: String(run.run_number ?? 1), GITHUB_WORKFLOW: String(workflow.name ?? ''), GITHUB_JOB: key, GITHUB_WORKSPACE: workspace, GITHUB_SERVER_URL: opts.github,
214
+ GITHUB_API_URL: opts.github, GITHUB_ENV: files.env, GITHUB_OUTPUT: files.output, GITHUB_PATH: files.path, RUNNER_OS: process.platform === 'darwin' ? 'macOS' : 'Linux', RUNNER_TEMP: root,
215
+ GITHUB_TOKEN: jobToken, XDG_CONFIG_HOME: config,
216
+ // what else a GitHub-hosted runner's job environment holds (npm's provenance reads each of these)
217
+ GITHUB_WORKFLOW_REF: `${repo}/${String(run.path)}@${String(github.ref)}`, GITHUB_RUN_ATTEMPT: '1', RUNNER_ENVIRONMENT: 'github-hosted',
218
+ GITHUB_REPOSITORY_ID: String(repoInfo.id ?? ''), GITHUB_REPOSITORY_OWNER_ID: String(repoInfo.owner?.id ?? ''),
219
+ ...(idTokenRequest ? { ACTIONS_ID_TOKEN_REQUEST_URL: idTokenRequest.url, ACTIONS_ID_TOKEN_REQUEST_TOKEN: idTokenRequest.token } : {}),
220
+ };
221
+ let failed = false;
222
+ for (const [i, step] of (job.steps ?? []).entries()) {
223
+ const env = { ...baseEnv, ...readKv(files.env) };
224
+ const extraPath = readFileSync(files.path, 'utf8').split('\n').filter(Boolean).reverse();
225
+ if (extraPath.length)
226
+ env.PATH = `${extraPath.join(':')}:${env.PATH ?? ''}`;
227
+ const stepScope = { ...scope, env: { ...Object.fromEntries(Object.entries({ ...workflow.env, ...job.env, ...step.env }).map(([k, v]) => [k, expand(String(v), scope)])) }, __failed: failed };
228
+ for (const [k, v] of Object.entries(stepScope.env))
229
+ env[k] = String(v);
230
+ if (!condition(step.if, stepScope)) {
231
+ log(` step ${i + 1} ${step.name ?? step.uses ?? 'run'}: skipped`);
232
+ continue;
233
+ }
234
+ log(` step ${i + 1}: ${step.name ?? step.uses ?? String(step.run).split('\n')[0]}`);
235
+ let ok;
236
+ if (step.uses) {
237
+ // its inputs are expressions too (`registry-url: ${{ vars.NPM_REGISTRY }}`)
238
+ const inputs = Object.fromEntries(Object.entries(step.with ?? {}).map(([k, v]) => [k, typeof v === 'string' ? expand(v, stepScope) : v]));
239
+ const r = builtin({ ...step, with: inputs }, env, workspace);
240
+ log(` ${r.message}`);
241
+ ok = r.ok;
242
+ }
243
+ else {
244
+ const cwd = join(workspace, step['working-directory'] ?? job.defaults?.run?.['working-directory'] ?? '');
245
+ ok = (await sh(expand(String(step.run ?? ''), stepScope), cwd, env, step.shell ?? job.defaults?.run?.shell ?? 'bash', log)) === 0;
246
+ }
247
+ if (step.id)
248
+ scope.steps[step.id] = { outputs: readKv(files.output), outcome: ok ? 'success' : 'failure', conclusion: ok || step['continue-on-error'] ? 'success' : 'failure' };
249
+ if (!ok && !step['continue-on-error'])
250
+ failed = true;
251
+ }
252
+ done.set(key, failed ? 'failure' : 'success');
253
+ scope.needs[key] = { result: failed ? 'failure' : 'success', outputs: readKv(files.output) };
254
+ if (failed)
255
+ conclusion = 'failure';
256
+ }
257
+ }
258
+ catch (error) {
259
+ log(`run ${repo}#${id}: ${error.message}`);
260
+ conclusion = 'failure';
261
+ }
262
+ finally {
263
+ await api(opts, 'POST', `/_twin/repos/${repo}/actions/runs/${id}/complete`, { conclusion });
264
+ rmSync(root, { recursive: true, force: true });
265
+ }
266
+ log(`run ${repo}#${id}: ${conclusion}`);
267
+ return conclusion;
268
+ }
269
+ /** Every queued run of every repository, one at a time, oldest first. */
270
+ export async function runQueued(opts) {
271
+ let ran = 0;
272
+ for (const repo of await repositories(opts)) {
273
+ const runs = await api(opts, 'GET', `/repos/${repo}/actions/runs?status=queued&per_page=100`);
274
+ const queued = (runs.body.workflow_runs ?? []).sort((a, b) => Number(a.id) - Number(b.id));
275
+ for (const run of queued) {
276
+ await runOnce(opts, repo, run);
277
+ ran += 1;
278
+ }
279
+ }
280
+ return ran;
281
+ }
@@ -2,7 +2,7 @@
2
2
  // carry parent-side pins, including clones across checkouts, without a global World registry.
3
3
  import { existsSync, readdirSync } from 'node:fs';
4
4
  import { join, relative, resolve, sep } from 'node:path';
5
- import { assertStateRemovable, readBranchMeta, stateDirName, withAncestryLock, withStateRemoval, getActiveWorldStore, worldPaths, worldStateRoot } from '@volter/world-core';
5
+ import { assertStateRemovable, readBranchMeta, stateDirName, withAncestryLock, withStateRemoval, getActiveWorldStore, worldPaths, worldStateRoot } from '@volter/world-core/runtime';
6
6
  function checkWorldAncestry(root, path) {
7
7
  const base = join(root, stateDirName(), 'worlds');
8
8
  if (!existsSync(base))
@@ -20,7 +20,7 @@
20
20
  // a re-`up` (which wipes the dir) or `down --purge` clears it: a recorded URL never outlives the
21
21
  // instance it described.
22
22
  import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
23
- import { withFileLock } from '@volter/world-core';
23
+ import { withFileLock } from '@volter/world-core/runtime';
24
24
  import { join } from 'node:path';
25
25
  import { spawnSync } from 'node:child_process';
26
26
  import net from 'node:net';
@@ -0,0 +1,29 @@
1
+ import type { WorldServiceConfig } from './schema.js';
2
+ export type ServerBackingFacts = {
3
+ serverBacking: {
4
+ command: string;
5
+ };
6
+ adoption: {
7
+ sdks: string[];
8
+ pypi: string[];
9
+ envStems: string[];
10
+ };
11
+ hostsNone: string;
12
+ endpointEnv: {
13
+ name: string;
14
+ templates: Record<string, string>;
15
+ note: string;
16
+ };
17
+ };
18
+ /**
19
+ * A vendor whose open-source server a World runs as its service instead of a twin (architecture, "A vendor's own
20
+ * server as a backing"): the media plane no pack can hold as data. The service is the vendor's own binary on an 'auto'
21
+ * port, loopback only; the application's key and secret are the ones the server is started with, so its SDK signs
22
+ * what the server verifies. What the server does not do on its own (LiveKit's egress, a separate service) the
23
+ * service's note names.
24
+ */
25
+ export declare const VENDOR_SERVER_BACKINGS: Record<string, {
26
+ service: WorldServiceConfig;
27
+ env: Record<string, string>;
28
+ facts: ServerBackingFacts;
29
+ }>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * A vendor whose open-source server a World runs as its service instead of a twin (architecture, "A vendor's own
3
+ * server as a backing"): the media plane no pack can hold as data. The service is the vendor's own binary on an 'auto'
4
+ * port, loopback only; the application's key and secret are the ones the server is started with, so its SDK signs
5
+ * what the server verifies. What the server does not do on its own (LiveKit's egress, a separate service) the
6
+ * service's note names.
7
+ */
8
+ export const VENDOR_SERVER_BACKINGS = (() => {
9
+ const key = 'twin-livekit-key';
10
+ // livekit-server wants a secret of at least 32 characters
11
+ const secret = 'twin-livekit-secret-for-the-worlds-own-server';
12
+ return {
13
+ livekit: {
14
+ service: {
15
+ '//': 'LiveKit\'s own server (livekit-server, Apache-2.0; `brew install livekit` or LiveKit\'s install script puts it '
16
+ + 'on PATH), loopback only, rooms in its memory until `down`. Egress is a separate LiveKit service this does not run: '
17
+ + 'EgressService calls answer its error. Its webhooks go where the application says: add `--config-body` '
18
+ + `"webhook: {api_key: ${key}, urls: [<the app's webhook URL>]}" to args.`,
19
+ id: 'livekit',
20
+ type: 'twin',
21
+ command: 'livekit-server',
22
+ // media on the loopback address over UDP from LiveKit's default range; ICE over TCP off (its fixed 7881 would
23
+ // collide with a second server)
24
+ args: ['--bind', '127.0.0.1', '--node-ip', '127.0.0.1', '--keys', `${key}: ${secret}`, '--rtc.tcp_port', '0'],
25
+ port: 'auto',
26
+ rootArg: false,
27
+ controlPlane: true,
28
+ ready: { httpUrl: '${url}/' },
29
+ injectEnvTemplates: {
30
+ LIVEKIT_URL: 'ws://${host}:${port}',
31
+ LIVEKIT_API_KEY: key,
32
+ LIVEKIT_API_SECRET: secret,
33
+ },
34
+ },
35
+ env: { LIVEKIT_API_KEY: key, LIVEKIT_API_SECRET: secret },
36
+ facts: {
37
+ // Current consumers: RH2's package.json and server SDK imports; LiveKit's server is the backing, not a pack.
38
+ serverBacking: { command: 'livekit-server' },
39
+ adoption: { sdks: ['livekit-client', 'livekit-server-sdk', '@livekit/rtc-node'], pypi: ['livekit', 'livekit-agents', 'livekit-api'], envStems: ['LIVEKIT'] },
40
+ hostsNone: 'The World runs the vendor server on its declared endpoint; SDKs read LIVEKIT_URL.',
41
+ endpointEnv: { name: 'LIVEKIT_URL', templates: { LIVEKIT_URL: 'ws://${host}:${port}', LIVEKIT_API_KEY: key, LIVEKIT_API_SECRET: secret }, note: 'Credentials and URL are the same ones the World starts its LiveKit server with.' },
42
+ },
43
+ },
44
+ };
45
+ })();
@@ -9,7 +9,7 @@
9
9
  import { cpSync, existsSync, readdirSync, readFileSync, rmSync } from 'node:fs';
10
10
  import { dirname, join, resolve } from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
- import { captureHistory, historyAtInstant, forkTwin, stateGeneration, withAncestryLock, worldPaths, worldStateRoot } from '@volter/world-core';
12
+ import { captureHistory, historyAtInstant, forkTwin, stateGeneration, withHistoryLocks, worldPaths, worldStateRoot } from '@volter/world-core/runtime';
13
13
  import { downWorld, saveWorldInstance, statusWorld, upWorld } from "./runtime.js";
14
14
  import { parseInfraDefinition, pgliteDown, pgliteUp } from "./pglite-backing.js";
15
15
  import { redisTwinCli, redisTwinDown, redisTwinUp } from "./redis-backing.js";
@@ -29,7 +29,8 @@ export async function branchWorld(base, name, options = {}) {
29
29
  const at = (options.now ?? new Date()).toISOString();
30
30
  // Capture source identity before awaiting child compute. A concurrent reset may win;
31
31
  // in that case forking fails and the task-owned child is torn down, never an empty success.
32
- const source = withAncestryLock(() => Object.keys(from.services).sort().map(service => {
32
+ const baseDirs = Object.keys(from.services).flatMap((service) => { const fromRoot = join(from.dirs.data, service); return stateServices(fromRoot).map((state) => worldPaths(state, fromRoot).dir); });
33
+ const source = withHistoryLocks(baseDirs, () => Object.keys(from.services).sort().map(service => {
33
34
  const fromRoot = join(from.dirs.data, service);
34
35
  return { service, fromRoot, states: stateServices(fromRoot).map(state => {
35
36
  let view = options.at?.views?.[service] ?? options.at?.views?.[state] ?? captureHistory(state, fromRoot).view;
@@ -6,7 +6,7 @@ import { keepProcessAlive } from '@volter/world-core/lifecycle';
6
6
  // NOT in the vendor-agnostic kernel — control-plane must never name a vendor (A2). The kernel
7
7
  // only exports the vendor-INDEPENDENT `createTwinProxy` utility; choosing which vendors a given
8
8
  // app world proxies is the orchestrator's job.
9
- import { createTwinProxy } from '@volter/world-core';
9
+ import { createTwinProxy } from '@volter/world-core/runtime';
10
10
  const port = Number(process.env.PORT || 0);
11
11
  const target = process.env.TARGET_URL;
12
12
  if (!target)
@@ -0,0 +1,38 @@
1
+ /** The index as npm carries it. */
2
+ export declare const CATALOG_INDEX_PACKAGE = "@volter/twin-catalog";
3
+ /** A source; `workflow` is the file under `.github/workflows/` its packages are released by (release.yml unless named). */
4
+ export type Source = {
5
+ name: string;
6
+ repository: string;
7
+ scope: string;
8
+ official: boolean;
9
+ protocol: '3';
10
+ workflow?: string;
11
+ };
12
+ export type Version = {
13
+ version: string;
14
+ status: 'live' | 'pending' | 'rejected';
15
+ at: string;
16
+ by: string;
17
+ reason?: string;
18
+ commit?: string;
19
+ };
20
+ export type Entry = {
21
+ vendor: string;
22
+ packages: Array<{
23
+ name: string;
24
+ source: string;
25
+ versions: Version[];
26
+ }>;
27
+ };
28
+ export declare const readSources: (index: string) => Source[];
29
+ export declare function readVendors(index: string): Entry[];
30
+ /** A package's vendor: `@volter/twin-<vendor>`, or the part after `twin-` in any scope. */
31
+ export declare const vendorOf: (name: string) => string;
32
+ /** Each vendor's default twin: its official package, else its live outside package; the
33
+ * highest live version of it. */
34
+ export declare function defaults(index: string): Record<string, {
35
+ name: string;
36
+ version: string;
37
+ source: string;
38
+ }>;
@@ -0,0 +1,37 @@
1
+ // The catalog index as the runtime reads it (docs/contributing/architecture.md, "The catalog: where twins come from"):
2
+ // its sources, its vendors' packages and versions, and each vendor's default twin. The index is a directory of data (the
3
+ // `volter-ai/twin-catalog` repository, published as the npm package `@volter/twin-catalog`); scripts/twin-catalog.ts
4
+ // checks and writes it, and `init` reads the defaults from the installed package.
5
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ /** The index as npm carries it. */
8
+ export const CATALOG_INDEX_PACKAGE = '@volter/twin-catalog';
9
+ export const readSources = (index) => JSON.parse(readFileSync(join(index, 'sources.json'), 'utf8'));
10
+ export function readVendors(index) {
11
+ const dir = join(index, 'vendors');
12
+ return existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith('.json')).sort().map((f) => JSON.parse(readFileSync(join(dir, f), 'utf8'))) : [];
13
+ }
14
+ /** A package's vendor: `@volter/twin-<vendor>`, or the part after `twin-` in any scope. */
15
+ export const vendorOf = (name) => name.replace(/^@[^/]+\//, '').replace(/^twin-/, '');
16
+ const semver = (v) => v.split(/[.-]/).slice(0, 3).map((n) => Number(n) || 0);
17
+ const newer = (a, b) => { const x = semver(a); const y = semver(b); for (let i = 0; i < 3; i++)
18
+ if (x[i] !== y[i])
19
+ return x[i] - y[i]; return 0; };
20
+ /** Each vendor's default twin: its official package, else its live outside package; the
21
+ * highest live version of it. */
22
+ export function defaults(index) {
23
+ const sources = new Map(readSources(index).map((s) => [s.name, s]));
24
+ const rank = (s) => (!s ? 0 : s.official ? 2 : 1);
25
+ const out = {};
26
+ for (const e of readVendors(index)) {
27
+ const candidates = e.packages
28
+ .map((p) => ({ p, s: sources.get(p.source), live: p.versions.filter((v) => v.status === 'live').sort((a, b) => newer(b.version, a.version))[0] }))
29
+ .filter((c) => c.live && c.s)
30
+ .sort((a, b) => rank(b.s) - rank(a.s));
31
+ const best = candidates[0];
32
+ // an outside twin is a default only where no official twin is
33
+ if (best && (best.s.official || candidates.every((c) => !c.s.official)))
34
+ out[e.vendor] = { name: best.p.name, version: best.live.version, source: best.p.source };
35
+ }
36
+ return out;
37
+ }
@@ -1,5 +1,5 @@
1
+ import { installedPackageDir } from '@volter/world-core/runtime';
1
2
  export declare const TWIN_PACKAGE_PREFIX = "@volter/twin-";
2
- /** Packages under the `@volter/twin-` prefix that are NOT twins. */
3
3
  export declare function twinPackageName(vendor: string): string;
4
4
  export type CatalogKind = 'checkout' | 'installed';
5
5
  export type Catalog = {
@@ -14,6 +14,9 @@ export type Catalog = {
14
14
  version(vendor: string): string | undefined;
15
15
  /** The birth stamp: the checkout's git sha, or a digest of the installed versions. */
16
16
  sha(): string;
17
+ /** Where the vendor's pack comes from: this checkout or an installed package (a merged catalog's sources differ per
18
+ * vendor, so a World boots each twin the way its own source serves it). */
19
+ kindOf(vendor: string): CatalogKind;
17
20
  };
18
21
  /** The pack's module entry, FROM ITS MANIFEST (docs/contributing/architecture.md#the-publishing-pipeline): `exports['.']`
19
22
  * — `src/index.ts` in the checkout, `dist/src/index.js` once published — so the same resolution
@@ -22,10 +25,20 @@ export declare function packEntry(dir: string): string;
22
25
  /** The pack's cli, from its manifest's `bin` (`world-<vendor>` when named, else the first), or undefined when the directory is no pack. */
23
26
  export declare function packCli(dir: string): string | undefined;
24
27
  /** The nearest `node_modules/<name>` above `from` that holds a package.json — node's own walk. */
25
- export declare function findInstalledPackage(from: string, name: string): string | undefined;
28
+ export declare const findInstalledPackage: typeof installedPackageDir;
26
29
  export declare function checkoutCatalog(root: string): Catalog;
27
30
  export declare function installedCatalog(root: string): Catalog;
28
- /** The catalog for a world root: the checkout when `packages/twin` is there, else what is installed. */
31
+ /**
32
+ * One catalog over several sources (docs/contributing/architecture.md, "The catalog: where twins come from"): each
33
+ * vendor is served by the pack of the higher major; on a tie the earlier source keeps it. `kind` is the first source's.
34
+ */
35
+ export declare function mergedCatalog(sources: Catalog[]): Catalog;
36
+ /** Overlays one facts file on the kernel's (a file that does not read is skipped: the kernel's facts stand). */
37
+ export declare function overlayPackFactsFile(file: string): void;
38
+ /** The facts file a released pack carries (`generated/pack-facts.json`, written by pack-release), when it has one. */
39
+ export declare function packFactsFile(dir: string): string | undefined;
40
+ /** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
41
+ * packages. Only the selected packs' own facts are overlaid on the kernel's. */
29
42
  export declare function resolveCatalog(root: string): Catalog;
30
43
  /** The pack directory a service boots from: its `package` resolved from the world root at boot. */
31
44
  export declare function resolveServicePackage(root: string, pkg: string): string;