@volter/world-runtime 3.0.62 → 3.0.64

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.
@@ -27,7 +27,10 @@ export declare function packCli(dir: string): string | undefined;
27
27
  /** The nearest `node_modules/<name>` above `from` that holds a package.json — node's own walk. */
28
28
  export declare const findInstalledPackage: typeof installedPackageDir;
29
29
  export declare function checkoutCatalog(root: string): Catalog;
30
- export declare function installedCatalog(root: string): Catalog;
30
+ export declare function installedCatalog(root: string, options?: {
31
+ sources?: Readonly<Record<string, string>>;
32
+ vendors?: readonly string[];
33
+ }): Catalog;
31
34
  /**
32
35
  * One catalog over several sources (docs/contributing/architecture.md, "The catalog: where twins come from"): each
33
36
  * vendor is served by the pack of the higher major; on a tie the earlier source keeps it. `kind` is the first source's.
@@ -39,6 +42,9 @@ export declare function overlayPackFactsFile(file: string): void;
39
42
  export declare function packFactsFile(dir: string): string | undefined;
40
43
  /** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
41
44
  * packages. Only the selected packs' own facts are overlaid on the kernel's. */
42
- export declare function resolveCatalog(root: string): Catalog;
45
+ export declare function resolveCatalog(root: string, options?: {
46
+ sources?: Readonly<Record<string, string>>;
47
+ vendors?: readonly string[];
48
+ }): Catalog;
43
49
  /** The pack directory a service boots from: its `package` resolved from the world root at boot. */
44
50
  export declare function resolveServicePackage(root: string, pkg: string): string;
@@ -84,8 +84,8 @@ export function checkoutCatalog(root) {
84
84
  },
85
85
  };
86
86
  }
87
- export function installedCatalog(root) {
88
- const twins = new Map([...installedVendorPacks(root)].filter(([, dir]) => packCli(dir) !== undefined));
87
+ export function installedCatalog(root, options = {}) {
88
+ const twins = new Map([...installedVendorPacks(root, options)].filter(([, dir]) => packCli(dir) !== undefined));
89
89
  return {
90
90
  kind: 'installed',
91
91
  root: resolve(root),
@@ -149,9 +149,9 @@ export function packFactsFile(dir) {
149
149
  }
150
150
  /** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
151
151
  * packages. Only the selected packs' own facts are overlaid on the kernel's. */
152
- export function resolveCatalog(root) {
152
+ export function resolveCatalog(root, options = {}) {
153
153
  const resolved = resolve(root);
154
- const installed = installedCatalog(resolved);
154
+ const installed = installedCatalog(resolved, options);
155
155
  const repo = packRepository(resolved);
156
156
  const catalog = repo === undefined ? installed : mergedCatalog([checkoutCatalog(repo), installed]);
157
157
  for (const vendor of catalog.vendors()) {
@@ -183,9 +183,10 @@ export type InitOptions = {
183
183
  force?: boolean;
184
184
  allowUnknown?: boolean;
185
185
  acknowledge?: Record<string, string>;
186
- /** The twins, named outright instead of detected from the repo (a bare world: `init --bare
187
- * <org>/<world> --twins a,b`). */
186
+ /** The twins deliberately selected instead of including every detected vendor. */
188
187
  vendors?: string[];
188
+ /** Installed package choices by vendor, before discovery resolves competing publishers. */
189
+ sources?: Record<string, string>;
189
190
  /** A world with no app: served as `/<org>/<world>/`. */
190
191
  bare?: string;
191
192
  };
package/dist/src/init.js CHANGED
@@ -518,7 +518,7 @@ export function planWorldInit(name, repoPath, options = {}) {
518
518
  /** a path the config carries: relative to the world root when the config lives under it
519
519
  * (portable between teammates), absolute otherwise (a pilot emitted away from its root) */
520
520
  const carried = (path) => (isInside(root, path) ? relative(root, path) : path);
521
- const catalogSource = resolveCatalog(root);
521
+ const catalogSource = resolveCatalog(root, { sources: options.sources, vendors: options.vendors });
522
522
  const catalog = new Set(catalogSource.vendors());
523
523
  // the catalog index's defaults (the installed `@volter/twin-catalog`), which name the twin to install for a vendor
524
524
  // this catalog lacks
@@ -604,9 +604,12 @@ export function planWorldInit(name, repoPath, options = {}) {
604
604
  return null;
605
605
  }
606
606
  })();
607
- const handlersTo = starterHandlers !== null ? ownedPath(out, `handlers/${vendor}.json`) : null;
607
+ const handlersPath = ownedPath(out, `handlers/${vendor}.json`);
608
+ // A published generative pack need not ship its author's journeys. Its scenario file is
609
+ // still a World-owned input, including one the user writes after init.
610
+ const handlersTo = starterHandlers !== null || packFacts()[vendor]?.archetype === 'generative' || existsSync(handlersPath) ? handlersPath : null;
608
611
  if (handlersTo !== null)
609
- defaultsCopies.push({ vendor, kind: 'handlers', content: starterHandlers, to: handlersTo });
612
+ defaultsCopies.push({ vendor, kind: 'handlers', content: starterHandlers ?? `${JSON.stringify({ handlers: [] }, null, 2)}\n`, to: handlersTo });
610
613
  const handlersArg = handlersTo === null ? null : carried(handlersTo);
611
614
  // a pack's default data (src/semantics/seed.ts: calls to the vendor's own operations, as data) is copied beside a
612
615
  // runner that makes each call against the twin, both the author's from then on
@@ -22,6 +22,8 @@ export type PackFacts = {
22
22
  maxRequestBodySize?: number;
23
23
  /** Existing descriptor transport, compiled into the same artifact as endpoint/adoption facts. */
24
24
  transport?: string;
25
+ /** Descriptor serve family, compiled into the same artifact as transport and adoption facts. */
26
+ archetype?: string;
25
27
  adoption?: PackAdoption;
26
28
  hosts?: Array<{
27
29
  host?: string;
@@ -1450,6 +1450,7 @@ async function startService(config, service, worldEnv, paths, allocatedPorts, bo
1450
1450
  url,
1451
1451
  ...sidesRecord(sides),
1452
1452
  ...(mounted.version !== undefined ? { resolvedVersion: mounted.version } : {}),
1453
+ ...(mounted.name !== undefined ? { resolvedPackage: mounted.name } : {}),
1453
1454
  ...(mounted.protocol !== undefined ? { protocol: mounted.protocol } : {}),
1454
1455
  pid: child.pid ?? 0,
1455
1456
  log,
@@ -1471,9 +1472,11 @@ function mountedPackage(modulePath, serviceId) {
1471
1472
  const p = join(dir, 'package.json');
1472
1473
  if (existsSync(p)) {
1473
1474
  try {
1474
- const v = JSON.parse(readFileSync(p, 'utf8')).version;
1475
- if (typeof v === 'string') {
1476
- out.version = v;
1475
+ const manifest = JSON.parse(readFileSync(p, 'utf8'));
1476
+ if (typeof manifest.name === 'string')
1477
+ out.name = manifest.name;
1478
+ if (typeof manifest.version === 'string') {
1479
+ out.version = manifest.version;
1477
1480
  out.packagePath = p;
1478
1481
  }
1479
1482
  }
@@ -1503,7 +1506,7 @@ function lockfileCheck(service, modulePath) {
1503
1506
  if (mounted.version === undefined)
1504
1507
  throw new Error(`Twin "${service.id}" pins version ${service.version} but no package.json was found above ${modulePath} — nothing to hold the pin against`);
1505
1508
  if (!versionSatisfies(mounted.version, service.version))
1506
- throw new Error(`Twin "${service.id}" pins version ${service.version} and the mounted package is ${mounted.version} (${mounted.packagePath}) — install the version the World names (npm install -D @volter/twin-${service.id}@${service.version}, as a committed lockfile does), or move the pin to it: "version" on the service in .volter/world.json (volter-world outdated lists both)`);
1509
+ throw new Error(`Twin "${service.id}" pins version ${service.version} and the mounted package is ${mounted.version} (${mounted.packagePath}) — install the version the World names (npm install -D --save-exact ${service.package ?? mounted.name ?? `@volter/twin-${service.id}`}@${service.version}), or trial an update in a separate app directory before changing source.version in .volter/world.json (volter-world outdated lists both)`);
1507
1510
  }
1508
1511
  if (mounted.protocol?.standing === 'refused')
1509
1512
  throw new Error(`Twin "${service.id}" targets protocol major ${mounted.protocol.major}; this platform serves ${PROTOCOL_VERSION} only (runtime contract R16)`);
@@ -1846,6 +1849,7 @@ async function startColocatedServices(config, colocated, isolation, worldEnv, pa
1846
1849
  url,
1847
1850
  ...sidesRecord(sides),
1848
1851
  ...(mounted.version !== undefined ? { resolvedVersion: mounted.version } : {}),
1852
+ ...(mounted.name !== undefined ? { resolvedPackage: mounted.name } : {}),
1849
1853
  ...(mounted.protocol !== undefined ? { protocol: mounted.protocol } : {}),
1850
1854
  pid: child.pid ?? 0,
1851
1855
  log,
@@ -2140,8 +2144,10 @@ export async function upWorld(configId, options = {}, abort) {
2140
2144
  try {
2141
2145
  const services = {};
2142
2146
  const recordService = (started) => {
2143
- const branchState = loaded.config.services.find(service => service.id === started.id)?.branchState;
2144
- services[started.id] = { ...started, branchState: branchState ?? 'supported' };
2147
+ const selected = loaded.config.services.find(service => service.id === started.id);
2148
+ services[started.id] = { ...started, branchState: selected?.branchState ?? 'supported',
2149
+ ...(selected?.package || selected?.version ? { pinnedSource: { ...(selected.package ? { package: selected.package } : {}), ...(selected.version ? { version: selected.version } : {}) } } : {}),
2150
+ };
2145
2151
  };
2146
2152
  const inheritedEnv = admittedEnv(loaded.config.stripEnv);
2147
2153
  const env = {
@@ -345,6 +345,13 @@ export type WorldServiceInstance = {
345
345
  /** R17 — what the host actually mounted for a colocated twin: the package version read from
346
346
  * the module's own package.json, and the protocol standing (R16) it declared. */
347
347
  resolvedVersion?: string;
348
+ /** Package identity captured before boot, never inferred from a later installation. */
349
+ resolvedPackage?: string;
350
+ /** Configured selection at boot; distinct from the package actually mounted. */
351
+ pinnedSource?: {
352
+ package?: string;
353
+ version?: string;
354
+ };
348
355
  protocol?: {
349
356
  declared?: string;
350
357
  major: number;
@@ -214,8 +214,8 @@ function assertLegacyWorldConfig(value, path) {
214
214
  assertExternalServiceConfig(service, path);
215
215
  }
216
216
  else {
217
- if (service.package !== undefined && !(typeof service.package === 'string' && /^@volter\/twin-[a-z0-9][a-z0-9-]*$/.test(service.package))) {
218
- throw new Error(`Service "${service.id}" package must name a twin package (@volter/twin-<vendor>) in ${path}`);
217
+ if (service.package !== undefined && !(typeof service.package === 'string' && /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(service.package))) {
218
+ throw new Error(`Service "${service.id}" package must be an npm package name; the installed descriptor establishes its vendor and protocol in ${path}`);
219
219
  }
220
220
  if (service.package === undefined && (!service.command || typeof service.command !== 'string')) {
221
221
  throw new Error(`Service "${service.id}" must define command (or package) in ${path}`);
@@ -120,6 +120,12 @@ export type WorldLayout = {
120
120
  services: Record<string, {
121
121
  url?: string;
122
122
  protocol?: unknown;
123
+ resolvedPackage?: string;
124
+ resolvedVersion?: string;
125
+ pinnedSource?: {
126
+ package?: string;
127
+ version?: string;
128
+ };
123
129
  }>;
124
130
  };
125
131
  /** The host a world's doors run in: its layout, and how a request reaches a twin's wire — a local
@@ -2352,7 +2352,11 @@ export class WorldDoors {
2352
2352
  refresh = null;
2353
2353
  }
2354
2354
  const last = [...parentEntries(state, this.controlRoot(name))].reverse().find((e) => e.landsId && e.receipt);
2355
- return Response.json({ twin: name, state, protocol: this.up().services[name]?.protocol ?? null, screens: await this.screensOf(name), root: root ?? null, credential: sealedCredentialInfo(this.worldRoot, name), refresh, position: wholeLog(state, this.controlRoot(name)).length, lastReceipt: last?.receipt ?? null });
2355
+ const mounted = this.up().services[name];
2356
+ const origin = readBranchMeta(state, this.controlRoot(name))?.origin;
2357
+ const identity = { mounted: mounted?.resolvedPackage ? { package: mounted.resolvedPackage, version: mounted.resolvedVersion ?? null } : null, pinned: mounted?.pinnedSource ?? null };
2358
+ const dataOrigin = origin ? { kind: 'shared-history', url: origin.at } : refresh ? { kind: 'vendor-refresh', url: root?.url ?? null } : { kind: 'unrecorded', url: null };
2359
+ return Response.json({ twin: name, state, identity, dataOrigin, execution: { rootConnected: Boolean(root), deploy: root?.deploy ?? null }, protocol: mounted?.protocol ?? null, screens: await this.screensOf(name), root: root ?? null, credential: sealedCredentialInfo(this.worldRoot, name), refresh, position: wholeLog(state, this.controlRoot(name)).length, lastReceipt: last?.receipt ?? null });
2356
2360
  }
2357
2361
  if (verb === 'refresh' && request.method === 'POST') {
2358
2362
  const root = rootForControlRoot(this.controlRoot(name), name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-runtime",
3
- "version": "3.0.62",
3
+ "version": "3.0.64",
4
4
  "description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
5
5
  "keywords": [
6
6
  "twin",
@@ -69,14 +69,14 @@
69
69
  "node": ">=22.3"
70
70
  },
71
71
  "peerDependencies": {
72
- "@volter/world-core": "3.0.62",
73
- "@volter/world-console": "3.0.57"
72
+ "@volter/world-core": "3.0.64",
73
+ "@volter/world-console": "3.0.59"
74
74
  },
75
75
  "dependencies": {
76
76
  "@electric-sql/pglite": "0.5.8",
77
77
  "@electric-sql/pglite-pgvector": "0.0.9",
78
78
  "@volter/world-access": "3.0.1",
79
- "@volter/world-core": "3.0.62",
79
+ "@volter/world-core": "3.0.64",
80
80
  "mongodb": "~6.20.0",
81
81
  "pg-gateway": "0.3.0-beta.4",
82
82
  "smol-toml": "^1.8.0",
package/src/catalog.ts CHANGED
@@ -98,8 +98,8 @@ export function checkoutCatalog(root: string): Catalog {
98
98
  };
99
99
  }
100
100
 
101
- export function installedCatalog(root: string): Catalog {
102
- const twins = new Map([...installedVendorPacks(root)].filter(([, dir]) => packCli(dir) !== undefined));
101
+ export function installedCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[] } = {}): Catalog {
102
+ const twins = new Map([...installedVendorPacks(root, options)].filter(([, dir]) => packCli(dir) !== undefined));
103
103
  return {
104
104
  kind: 'installed',
105
105
  root: resolve(root),
@@ -156,9 +156,9 @@ export function packFactsFile(dir: string): string | undefined {
156
156
 
157
157
  /** The catalog for a World root: its pack repository (or the one beside its platform checkout), merged with installed
158
158
  * packages. Only the selected packs' own facts are overlaid on the kernel's. */
159
- export function resolveCatalog(root: string): Catalog {
159
+ export function resolveCatalog(root: string, options: { sources?: Readonly<Record<string, string>>; vendors?: readonly string[] } = {}): Catalog {
160
160
  const resolved = resolve(root);
161
- const installed = installedCatalog(resolved);
161
+ const installed = installedCatalog(resolved, options);
162
162
  const repo = packRepository(resolved);
163
163
  const catalog = repo === undefined ? installed : mergedCatalog([checkoutCatalog(repo), installed]);
164
164
  for (const vendor of catalog.vendors()) {
package/src/init.ts CHANGED
@@ -258,9 +258,10 @@ export type InitOptions = {
258
258
  force?: boolean;
259
259
  allowUnknown?: boolean;
260
260
  acknowledge?: Record<string, string>;
261
- /** The twins, named outright instead of detected from the repo (a bare world: `init --bare
262
- * <org>/<world> --twins a,b`). */
261
+ /** The twins deliberately selected instead of including every detected vendor. */
263
262
  vendors?: string[];
263
+ /** Installed package choices by vendor, before discovery resolves competing publishers. */
264
+ sources?: Record<string, string>;
264
265
  /** A world with no app: served as `/<org>/<world>/`. */
265
266
  bare?: string;
266
267
  };
@@ -721,7 +722,7 @@ export function planWorldInit(name: string, repoPath: string, options: InitOptio
721
722
  * (portable between teammates), absolute otherwise (a pilot emitted away from its root) */
722
723
  const carried = (path: string): string => (isInside(root, path) ? relative(root, path) : path);
723
724
 
724
- const catalogSource: Catalog = resolveCatalog(root);
725
+ const catalogSource: Catalog = resolveCatalog(root, { sources: options.sources, vendors: options.vendors });
725
726
  const catalog = new Set(catalogSource.vendors());
726
727
  // the catalog index's defaults (the installed `@volter/twin-catalog`), which name the twin to install for a vendor
727
728
  // this catalog lacks
@@ -800,8 +801,11 @@ export function planWorldInit(name: string, repoPath: string, options: InitOptio
800
801
  return answering.length ? `${JSON.stringify({ ...doc, handlers: answering }, null, 2)}\n` : null;
801
802
  } catch { return null; }
802
803
  })();
803
- const handlersTo = starterHandlers !== null ? ownedPath(out, `handlers/${vendor}.json`) : null;
804
- if (handlersTo !== null) defaultsCopies.push({ vendor, kind: 'handlers', content: starterHandlers!, to: handlersTo });
804
+ const handlersPath = ownedPath(out, `handlers/${vendor}.json`);
805
+ // A published generative pack need not ship its author's journeys. Its scenario file is
806
+ // still a World-owned input, including one the user writes after init.
807
+ const handlersTo = starterHandlers !== null || packFacts()[vendor]?.archetype === 'generative' || existsSync(handlersPath) ? handlersPath : null;
808
+ if (handlersTo !== null) defaultsCopies.push({ vendor, kind: 'handlers', content: starterHandlers ?? `${JSON.stringify({ handlers: [] }, null, 2)}\n`, to: handlersTo });
805
809
  const handlersArg = handlersTo === null ? null : carried(handlersTo);
806
810
  // a pack's default data (src/semantics/seed.ts: calls to the vendor's own operations, as data) is copied beside a
807
811
  // runner that makes each call against the twin, both the author's from then on
package/src/pack-facts.ts CHANGED
@@ -17,6 +17,8 @@ export type PackFacts = {
17
17
  maxRequestBodySize?: number;
18
18
  /** Existing descriptor transport, compiled into the same artifact as endpoint/adoption facts. */
19
19
  transport?: string;
20
+ /** Descriptor serve family, compiled into the same artifact as transport and adoption facts. */
21
+ archetype?: string;
20
22
  adoption?: PackAdoption;
21
23
  hosts?: Array<{ host?: string; suffix?: string; hostPattern?: string; pathPattern?: string; key?: string; exclude?: true }>;
22
24
  hostsNone?: string;
package/src/runtime.ts CHANGED
@@ -1441,6 +1441,7 @@ async function startService(
1441
1441
  url,
1442
1442
  ...sidesRecord(sides),
1443
1443
  ...(mounted.version !== undefined ? { resolvedVersion: mounted.version } : {}),
1444
+ ...(mounted.name !== undefined ? { resolvedPackage: mounted.name } : {}),
1444
1445
  ...(mounted.protocol !== undefined ? { protocol: mounted.protocol } : {}),
1445
1446
  pid: child.pid ?? 0,
1446
1447
  log,
@@ -1452,7 +1453,7 @@ async function startService(
1452
1453
  };
1453
1454
  }
1454
1455
 
1455
- type MountedPackage = { version?: string; packagePath?: string; protocol?: { declared?: string; major: number; standing: string } };
1456
+ type MountedPackage = { name?: string; version?: string; packagePath?: string; protocol?: { declared?: string; major: number; standing: string } };
1456
1457
  /** What the host is about to mount, read WITHOUT importing the pack (the kernel never imports
1457
1458
  * packs): the nearest package.json above the module for the version, pack-facts for the
1458
1459
  * protocol standing the catalog recorded — keyed by the vendor dir the module sits in, else
@@ -1463,7 +1464,7 @@ function mountedPackage(modulePath: string, serviceId: string): MountedPackage {
1463
1464
  for (let i = 0; i < 6; i++) {
1464
1465
  const p = join(dir, 'package.json');
1465
1466
  if (existsSync(p)) {
1466
- try { const v = (JSON.parse(readFileSync(p, 'utf8')) as { version?: unknown }).version; if (typeof v === 'string') { out.version = v; out.packagePath = p; } } catch { /* unreadable: no version to hold a pin against */ }
1467
+ try { const manifest = JSON.parse(readFileSync(p, 'utf8')) as { name?: unknown; version?: unknown }; if (typeof manifest.name === 'string') out.name = manifest.name; if (typeof manifest.version === 'string') { out.version = manifest.version; out.packagePath = p; } } catch { /* unreadable: no version to hold a pin against */ }
1467
1468
  break;
1468
1469
  }
1469
1470
  const up = dirname(dir); if (up === dir) break; dir = up;
@@ -1479,7 +1480,7 @@ function lockfileCheck(service: WorldServiceConfig, modulePath: string): Mounted
1479
1480
  const mounted = mountedPackage(modulePath, service.id);
1480
1481
  if (service.version !== undefined) {
1481
1482
  if (mounted.version === undefined) throw new Error(`Twin "${service.id}" pins version ${service.version} but no package.json was found above ${modulePath} — nothing to hold the pin against`);
1482
- if (!versionSatisfies(mounted.version, service.version)) throw new Error(`Twin "${service.id}" pins version ${service.version} and the mounted package is ${mounted.version} (${mounted.packagePath}) — install the version the World names (npm install -D @volter/twin-${service.id}@${service.version}, as a committed lockfile does), or move the pin to it: "version" on the service in .volter/world.json (volter-world outdated lists both)`);
1483
+ if (!versionSatisfies(mounted.version, service.version)) throw new Error(`Twin "${service.id}" pins version ${service.version} and the mounted package is ${mounted.version} (${mounted.packagePath}) — install the version the World names (npm install -D --save-exact ${service.package ?? mounted.name ?? `@volter/twin-${service.id}`}@${service.version}), or trial an update in a separate app directory before changing source.version in .volter/world.json (volter-world outdated lists both)`);
1483
1484
  }
1484
1485
  if (mounted.protocol?.standing === 'refused') throw new Error(`Twin "${service.id}" targets protocol major ${mounted.protocol.major}; this platform serves ${PROTOCOL_VERSION} only (runtime contract R16)`);
1485
1486
  return mounted;
@@ -1789,6 +1790,7 @@ async function startColocatedServices(
1789
1790
  url,
1790
1791
  ...sidesRecord(sides),
1791
1792
  ...(mounted.version !== undefined ? { resolvedVersion: mounted.version } : {}),
1793
+ ...(mounted.name !== undefined ? { resolvedPackage: mounted.name } : {}),
1792
1794
  ...(mounted.protocol !== undefined ? { protocol: mounted.protocol } : {}),
1793
1795
  pid: child.pid ?? 0,
1794
1796
  log,
@@ -2028,8 +2030,10 @@ export async function upWorld(configId: string, options: UpWorldOptions = {}, ab
2028
2030
  try {
2029
2031
  const services: Record<string, WorldServiceInstance> = {};
2030
2032
  const recordService = (started: WorldServiceInstance) => {
2031
- const branchState = loaded.config.services.find(service => service.id === started.id)?.branchState;
2032
- services[started.id] = { ...started, branchState: branchState ?? 'supported' };
2033
+ const selected = loaded.config.services.find(service => service.id === started.id);
2034
+ services[started.id] = { ...started, branchState: selected?.branchState ?? 'supported',
2035
+ ...(selected?.package || selected?.version ? { pinnedSource: { ...(selected.package ? { package: selected.package } : {}), ...(selected.version ? { version: selected.version } : {}) } } : {}),
2036
+ };
2033
2037
  };
2034
2038
  const inheritedEnv = admittedEnv(loaded.config.stripEnv);
2035
2039
  const env: Record<string, string> = {
package/src/schema.ts CHANGED
@@ -308,6 +308,10 @@ export type WorldServiceInstance = {
308
308
  /** R17 — what the host actually mounted for a colocated twin: the package version read from
309
309
  * the module's own package.json, and the protocol standing (R16) it declared. */
310
310
  resolvedVersion?: string;
311
+ /** Package identity captured before boot, never inferred from a later installation. */
312
+ resolvedPackage?: string;
313
+ /** Configured selection at boot; distinct from the package actually mounted. */
314
+ pinnedSource?: { package?: string; version?: string };
311
315
  protocol?: { declared?: string; major: number; standing: string };
312
316
  /** Absent for 'external' services — we do not assign them a port. */
313
317
  port?: number;
@@ -607,8 +611,8 @@ function assertLegacyWorldConfig(value: unknown, path: string): WorldConfig {
607
611
  if (service.type === 'external') {
608
612
  assertExternalServiceConfig(service, path);
609
613
  } else {
610
- if (service.package !== undefined && !(typeof service.package === 'string' && /^@volter\/twin-[a-z0-9][a-z0-9-]*$/.test(service.package))) {
611
- throw new Error(`Service "${service.id}" package must name a twin package (@volter/twin-<vendor>) in ${path}`);
614
+ if (service.package !== undefined && !(typeof service.package === 'string' && /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(service.package))) {
615
+ throw new Error(`Service "${service.id}" package must be an npm package name; the installed descriptor establishes its vendor and protocol in ${path}`);
612
616
  }
613
617
  if (service.package === undefined && (!service.command || typeof service.command !== 'string')) {
614
618
  throw new Error(`Service "${service.id}" must define command (or package) in ${path}`);
@@ -351,7 +351,7 @@ function streamEnv(id: string, protocol: string): Record<string, string> {
351
351
 
352
352
  /** What the doors need of a world: where each twin's state lives and which twins it has. A booted
353
353
  * local instance is one; a hosted World describes itself. */
354
- export type WorldLayout = { dirs: { data: string }; envFile?: string; services: Record<string, { url?: string; protocol?: unknown }> };
354
+ export type WorldLayout = { dirs: { data: string }; envFile?: string; services: Record<string, { url?: string; protocol?: unknown; resolvedPackage?: string; resolvedVersion?: string; pinnedSource?: { package?: string; version?: string } }> };
355
355
  /** The host a world's doors run in: its layout, and how a request reaches a twin's wire — a local
356
356
  * host forwards to the twin's own port, a hosted World calls the pack in-process. */
357
357
  export type DoorHost = {
@@ -1961,7 +1961,11 @@ export class WorldDoors {
1961
1961
  const marker = getActiveWorldStore().read(join(this.controlRoot(name), stateDirName(), 'world', name, 'refresh.json'));
1962
1962
  let refresh: unknown = null; try { if (marker !== null) refresh = JSON.parse(marker); } catch { refresh = null; }
1963
1963
  const last = [...parentEntries(state, this.controlRoot(name))].reverse().find((e) => e.landsId && e.receipt);
1964
- return Response.json({ twin: name, state, protocol: this.up().services[name]?.protocol ?? null, screens: await this.screensOf(name), root: root ?? null, credential: sealedCredentialInfo(this.worldRoot, name), refresh, position: wholeLog(state, this.controlRoot(name)).length, lastReceipt: last?.receipt ?? null });
1964
+ const mounted = this.up().services[name];
1965
+ const origin = readBranchMeta(state, this.controlRoot(name))?.origin;
1966
+ const identity = { mounted: mounted?.resolvedPackage ? { package: mounted.resolvedPackage, version: mounted.resolvedVersion ?? null } : null, pinned: mounted?.pinnedSource ?? null };
1967
+ const dataOrigin = origin ? { kind: 'shared-history', url: origin.at } : refresh ? { kind: 'vendor-refresh', url: root?.url ?? null } : { kind: 'unrecorded', url: null };
1968
+ return Response.json({ twin: name, state, identity, dataOrigin, execution: { rootConnected: Boolean(root), deploy: root?.deploy ?? null }, protocol: mounted?.protocol ?? null, screens: await this.screensOf(name), root: root ?? null, credential: sealedCredentialInfo(this.worldRoot, name), refresh, position: wholeLog(state, this.controlRoot(name)).length, lastReceipt: last?.receipt ?? null });
1965
1969
  }
1966
1970
  if (verb === 'refresh' && request.method === 'POST') {
1967
1971
  const root = rootForControlRoot(this.controlRoot(name), name);