@volter/twin-standard 1.0.88 → 1.0.90

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.
@@ -36,7 +36,7 @@ export async function assessPack(packDir, options = {}) {
36
36
  const units = [packDir, ...lanes.map((name) => join(packDir, name))];
37
37
  if (packDir.split(/[\\/]/).at(-1) !== vendor)
38
38
  throw new Error('Place the artifact in a directory named for its vendor before assessment');
39
- usePacks(join(packDir, '..'));
39
+ usePacks(join(packDir, '..'), { dependenciesFrom: packDir });
40
40
  const gates = await p3Gate(units);
41
41
  const life = expandFills(read(join(packDir, 'journeys/customer-life.json')));
42
42
  if (!Array.isArray(life.steps) || !life.steps.length)
@@ -356,7 +356,7 @@ async function lifeServed(pack) {
356
356
  if (!existsSync(lifePath))
357
357
  return ['journeys/customer-life.json is missing'];
358
358
  const { inMemoryWorld, usePacks, walk } = await import("./walk/behavior-journey.js");
359
- usePacks(dirname(pack));
359
+ usePacks(dirname(pack), { dependenciesFrom: pack });
360
360
  const env = globalThis.process.env;
361
361
  const served = async (root, options) => {
362
362
  const before = env.VOLTER_WORLD_BOOT_ID;
@@ -1,6 +1,8 @@
1
1
  import { type TwinStream } from '@volter/world-core/runtime';
2
2
  import { type Captured, type GitStep, type Journey, type JourneyStep, type LineStep, type RespStep } from './journey-kit.js';
3
- export declare function usePacks(dir: string): void;
3
+ export declare function usePacks(dir: string, options?: {
4
+ dependenciesFrom?: string;
5
+ }): void;
4
6
  /** The walk's World: the twin's own code on its in-memory World and blob stores (the backends a serverless
5
7
  * entry runs on), so a walk of any length is state changes in memory and never touches the disk; the World
6
8
  * clock, pinned to 2026-01-01, is world state in the same store, and the World is sealed. Each root is a fresh key
@@ -13,7 +13,7 @@ import { gzipSync } from 'node:zlib';
13
13
  import { parse, visit } from 'graphql';
14
14
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
15
15
  import { join } from 'node:path';
16
- import { databaseSession, getActiveWorldStore, MemoryBlobStore, MemoryWorldStore, setActiveBlobStore, setActiveWorldStore, setDeliveryObserver, setInProcessTwins } from '@volter/world-core/runtime';
16
+ import { databaseSession, getActivePackAssets, getActiveWorldStore, LocalPackAssets, MemoryBlobStore, MemoryWorldStore, setActiveBlobStore, setActivePackAssets, setActiveWorldStore, setDeliveryObserver, setInProcessTwins } from '@volter/world-core/runtime';
17
17
  import { createHostTable } from '@volter/world-core/host-routing';
18
18
  import packFactsDocument from '@volter/world-core/generated/pack-facts.json' with { type: 'json' };
19
19
  import { isMysql, isResp, at, captureAt, echoFailures, expandFills, expectFailures, fill, fillJson, formBody, isGit, isLine, isSql, isSocket, isWait, jwtAuthorization, oauth1Authorization, presignedPath, sigv4Headers, waitMs } from "./journey-kit.js";
@@ -22,6 +22,8 @@ import { walkPackDirectory } from "./pack-directory.js";
22
22
  import { xmlMembers } from "./xml-view.js";
23
23
  /** The pack repository the walk finds a vendor's pack in, set by the caller before its first walk. */
24
24
  let packsRoot;
25
+ /** Installed companions resolve from the selected pack, including its workspace-local dependencies. */
26
+ let dependenciesRoot;
25
27
  /** Where a socket step opens: an absolute `ws://` or `wss://` url on its own host, a path on the life's host otherwise. */
26
28
  function socketAt(open, host) {
27
29
  if (!/^wss?:\/\//.test(open))
@@ -34,8 +36,9 @@ let walkFacts = baselineFacts;
34
36
  let walkHosts = createHostTable(walkFacts).hosts;
35
37
  const directories = new Map();
36
38
  const installedFacts = new Set();
37
- export function usePacks(dir) {
39
+ export function usePacks(dir, options = {}) {
38
40
  packsRoot = dir;
41
+ dependenciesRoot = options.dependenciesFrom ?? dir;
39
42
  const installed = {};
40
43
  for (const name of readdirSync(dir)) {
41
44
  const file = join(dir, name, 'generated/pack-facts.json');
@@ -53,9 +56,10 @@ export function usePacks(dir) {
53
56
  }
54
57
  const packsDir = () => { if (packsRoot === undefined)
55
58
  throw new Error('usePacks(<packs dir>) first'); return packsRoot; };
59
+ const dependenciesDir = () => dependenciesRoot ?? packsDir();
56
60
  function directoryFor(vendor) {
57
61
  if (!directories.has(vendor)) {
58
- const directory = walkPackDirectory(packsDir(), vendor);
62
+ const directory = walkPackDirectory(packsDir(), vendor, dependenciesDir());
59
63
  directories.set(vendor, directory);
60
64
  if (directory) {
61
65
  const name = vendor.split('/')[0];
@@ -80,6 +84,8 @@ function directoryFor(vendor) {
80
84
  export function inMemoryWorld(label) {
81
85
  const previousStore = setActiveWorldStore(new MemoryWorldStore());
82
86
  const previousBlobs = setActiveBlobStore(new MemoryBlobStore());
87
+ const previousAssets = getActivePackAssets();
88
+ setActivePackAssets(new LocalPackAssets((vendor) => walkPackDirectory(packsDir(), vendor, dependenciesDir())));
83
89
  const previousClock = process.env.TWIN_WORLD_CLOCK_FILE;
84
90
  // sealed: nothing a walk does reaches a host outside the World (a webhook to a customer's server is refused, not sent)
85
91
  const previousEgress = process.env.VOLTER_TWIN_STRICT_EGRESS;
@@ -93,6 +99,7 @@ export function inMemoryWorld(label) {
93
99
  restore: () => {
94
100
  setActiveWorldStore(previousStore);
95
101
  setActiveBlobStore(previousBlobs);
102
+ setActivePackAssets(previousAssets);
96
103
  if (previousClock === undefined)
97
104
  delete process.env.TWIN_WORLD_CLOCK_FILE;
98
105
  else
@@ -119,7 +126,7 @@ function defaultType(body) {
119
126
  return 'text/plain;charset=UTF-8';
120
127
  }
121
128
  }
122
- const vendorFetch = (vendor) => vendorFetchFrom(packsDir(), vendor);
129
+ const vendorFetch = (vendor) => vendorFetchFrom(packsDir(), vendor, dependenciesDir());
123
130
  const shapes = new Map();
124
131
  const shapeOf = (vendor) => { if (!shapes.has(vendor))
125
132
  shapes.set(vendor, shapeChecker(directoryFor(vendor))); return shapes.get(vendor); };
@@ -218,7 +225,7 @@ export async function walk(journey, root, collect, opts) {
218
225
  return {};
219
226
  }
220
227
  const factory = await vendorFetch(journey.vendor);
221
- const views = await vendorAnswerViews(packsDir(), journey.vendor);
228
+ const views = await vendorAnswerViews(packsDir(), journey.vendor, dependenciesDir());
222
229
  if (!factory) {
223
230
  collect.push({ step: '(setup)', beat: 'ADAPTER', detail: `no create<Name>TwinFetch adapter enrolled for ${journey.vendor}` });
224
231
  return {};
@@ -370,7 +377,7 @@ export async function walk(journey, root, collect, opts) {
370
377
  }
371
378
  const viaServed = served?.stream;
372
379
  if (!viaServed)
373
- streams ??= await vendorStream(packsDir(), journey.vendor);
380
+ streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
374
381
  if (!viaServed && !streams) {
375
382
  collect.push({ step: step.name, beat: 'ADAPTER', detail: `no create<Name>TwinStream adapter enrolled for ${journey.vendor}` });
376
383
  return;
@@ -437,7 +444,7 @@ export async function walk(journey, root, collect, opts) {
437
444
  if (served?.stream && Object.keys(step.connect).length)
438
445
  throw new Error('a served RESP walk configures its server, not the client connection');
439
446
  if (!served?.stream)
440
- streams ??= await vendorStream(packsDir(), journey.vendor);
447
+ streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
441
448
  const factory = served?.stream ?? (respStream ??= streams?.(root, step.connect));
442
449
  if (!factory)
443
450
  throw new Error('no byte-stream adapter');
@@ -511,7 +518,7 @@ export async function walk(journey, root, collect, opts) {
511
518
  let body;
512
519
  if (step.signIn) {
513
520
  if (!served?.stream)
514
- streams ??= await vendorStream(packsDir(), journey.vendor);
521
+ streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
515
522
  const factory = served?.stream ?? streams?.(root, {});
516
523
  if (!factory)
517
524
  throw new Error(`no create<Name>TwinStream adapter enrolled for ${journey.vendor}`);
@@ -1 +1 @@
1
- export declare function walkPackDirectory(from: string, vendor: string): string | undefined;
1
+ export declare function walkPackDirectory(from: string, vendor: string, dependenciesFrom?: string): string | undefined;
@@ -2,14 +2,14 @@
2
2
  import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { installedVendorPacks, isDerivedPack, packVendor } from '@volter/world-core/runtime';
5
- export function walkPackDirectory(from, vendor) {
5
+ export function walkPackDirectory(from, vendor, dependenciesFrom = from) {
6
6
  if (!/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)?$/.test(vendor))
7
7
  return undefined;
8
8
  const [name, lane] = vendor.split('/');
9
9
  const sibling = join(from, name);
10
10
  const root = isDerivedPack(sibling) && packVendor(sibling) === name
11
11
  ? sibling
12
- : installedVendorPacks(from, { vendors: [name] }).get(name);
12
+ : installedVendorPacks(dependenciesFrom, { vendors: [name] }).get(name);
13
13
  const unit = root && (lane ? join(root, lane) : root);
14
14
  return unit && existsSync(join(unit, 'src', 'index.ts')) ? unit : undefined;
15
15
  }
@@ -7,14 +7,14 @@ export type VendorFetchFactory = (root: string, options?: {
7
7
  scenarioPath?: string;
8
8
  application?: ApplicationStandIn;
9
9
  }) => (request: Request) => Response | Promise<Response>;
10
- export declare function vendorFetch(twinDir: string, vendor: string): Promise<VendorFetchFactory | undefined>;
10
+ export declare function vendorFetch(twinDir: string, vendor: string, dependenciesFrom?: string): Promise<VendorFetchFactory | undefined>;
11
11
  /** Named views of an answer: what a journey step's `view` asserts against. The kit's own: `jwt:<field>`, the payload of the
12
12
  * JSON Web Token at that field of the answer, decoded as the application verifying it reads it (a session token's
13
13
  * claims). A pack's own (`answerViews` in its index) beside them. */
14
14
  export type AnswerViews = Record<string, (body: unknown) => unknown>;
15
- export declare function vendorAnswerViews(twinDir: string, vendor: string): Promise<AnswerViews>;
15
+ export declare function vendorAnswerViews(twinDir: string, vendor: string, dependenciesFrom?: string): Promise<AnswerViews>;
16
16
  /** A vendor's byte-stream wire (a line protocol), in process: the pack's `create<Name>TwinStream`, what a
17
17
  * hosted World hands a WebSocket's bytes. The walk drives it for a journey's line steps. */
18
18
  export type VendorStreamFactory = (root: string, options?: Record<string, unknown>) => TwinStream;
19
- export declare function vendorStream(twinDir: string, vendor: string): Promise<VendorStreamFactory | undefined>;
19
+ export declare function vendorStream(twinDir: string, vendor: string, dependenciesFrom?: string): Promise<VendorStreamFactory | undefined>;
20
20
  export {};
@@ -13,8 +13,8 @@ import { join } from 'node:path';
13
13
  import { startPgliteHost } from "./walk-database.js";
14
14
  import { worldNow } from '@volter/world-core/runtime';
15
15
  import { walkPackDirectory } from "./pack-directory.js";
16
- export async function vendorFetch(twinDir, vendor) {
17
- const directory = walkPackDirectory(twinDir, vendor);
16
+ export async function vendorFetch(twinDir, vendor, dependenciesFrom = twinDir) {
17
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
18
18
  if (!directory)
19
19
  return undefined;
20
20
  let mod;
@@ -59,9 +59,9 @@ const jwtView = (field) => (body) => {
59
59
  return null;
60
60
  }
61
61
  };
62
- export async function vendorAnswerViews(twinDir, vendor) {
62
+ export async function vendorAnswerViews(twinDir, vendor, dependenciesFrom = twinDir) {
63
63
  let own = {};
64
- const directory = walkPackDirectory(twinDir, vendor);
64
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
65
65
  try {
66
66
  if (directory)
67
67
  own = (await import(__rewriteRelativeImportExtension(join(directory, 'src', 'index.ts')))).answerViews ?? {};
@@ -69,8 +69,8 @@ export async function vendorAnswerViews(twinDir, vendor) {
69
69
  catch { /* none of its own */ }
70
70
  return new Proxy(own, { get: (views, name) => (typeof name === 'string' && name.startsWith('jwt:') ? jwtView(name.slice(4)) : views[name]) });
71
71
  }
72
- export async function vendorStream(twinDir, vendor) {
73
- const directory = walkPackDirectory(twinDir, vendor);
72
+ export async function vendorStream(twinDir, vendor, dependenciesFrom = twinDir) {
73
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
74
74
  if (!directory)
75
75
  return undefined;
76
76
  let mod;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-standard",
3
- "version": "1.0.88",
3
+ "version": "1.0.90",
4
4
  "description": "The Protocol 3 standard for Volter twin packs: the criteria a pack is graded by, the grader, the gate a pack repository and the twin catalog run on every change and submission, and the spec derivation the grade checks against. Pin a version: every place a pack is judged judges by that standard.",
5
5
  "keywords": [
6
6
  "twin",
@@ -75,7 +75,7 @@
75
75
  "yaml": "2.9.0"
76
76
  },
77
77
  "peerDependencies": {
78
- "@volter/world-core": "3.0.96",
78
+ "@volter/world-core": "3.0.98",
79
79
  "@volter/world-runtime": "*",
80
80
  "playwright": "^1.63.0"
81
81
  },
package/src/assessment.ts CHANGED
@@ -27,7 +27,7 @@ export async function assessPack(packDir: string, options: AssessmentOptions = {
27
27
  const lanes = readdirSync(packDir).filter((name) => existsSync(join(packDir, name, 'src/manifest.ts')) && !existsSync(join(packDir, name, 'package.json')));
28
28
  const units = [packDir, ...lanes.map((name) => join(packDir, name))];
29
29
  if (packDir.split(/[\\/]/).at(-1) !== vendor) throw new Error('Place the artifact in a directory named for its vendor before assessment');
30
- usePacks(join(packDir, '..'));
30
+ usePacks(join(packDir, '..'), { dependenciesFrom: packDir });
31
31
  const gates = await p3Gate(units);
32
32
  const life = expandFills(read(join(packDir, 'journeys/customer-life.json'))) as Journey;
33
33
  if (!Array.isArray(life.steps) || !life.steps.length) throw new Error('Assessment needs a nonempty customer journey');
@@ -318,7 +318,7 @@ async function lifeServed(pack: string): Promise<string[]> {
318
318
  const lifePath = join(pack, 'journeys', 'customer-life.json');
319
319
  if (!existsSync(lifePath)) return ['journeys/customer-life.json is missing'];
320
320
  const { inMemoryWorld, usePacks, walk } = await import('./walk/behavior-journey.ts');
321
- usePacks(dirname(pack));
321
+ usePacks(dirname(pack), { dependenciesFrom: pack });
322
322
  const env = (globalThis as { process: { env: Record<string, string | undefined> } }).process.env;
323
323
  const served = async (root: string, options: { scenarioPath?: string }) => {
324
324
  const before = env.VOLTER_WORLD_BOOT_ID;
@@ -14,7 +14,7 @@ import { gzipSync } from 'node:zlib';
14
14
  import { parse, visit } from 'graphql';
15
15
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
16
16
  import { join } from 'node:path';
17
- import { databaseSession, type DatabaseSession, getActiveWorldStore, MemoryBlobStore, MemoryWorldStore, setActiveBlobStore, setActiveWorldStore, setDeliveryObserver, setInProcessTwins, type InProcessTwin, type TwinStream, type TwinStreamConnection } from '@volter/world-core/runtime';
17
+ import { databaseSession, type DatabaseSession, getActivePackAssets, getActiveWorldStore, LocalPackAssets, MemoryBlobStore, MemoryWorldStore, setActiveBlobStore, setActivePackAssets, setActiveWorldStore, setDeliveryObserver, setInProcessTwins, type InProcessTwin, type TwinStream, type TwinStreamConnection } from '@volter/world-core/runtime';
18
18
  import { createHostTable, type HostFacts } from '@volter/world-core/host-routing';
19
19
  import packFactsDocument from '@volter/world-core/generated/pack-facts.json' with { type: 'json' };
20
20
  import { type Captured, type GitStep, type Journey, type JourneyStep, type LineStep, type SocketStep, type SqlStep, type RespStep, type MysqlStep, isMysql, isResp, at, captureAt, echoFailures, expandFills, expectFailures, fill, fillJson, formBody, isGit, isLine, isSql, isSocket, isWait, jwtAuthorization, oauth1Authorization, presignedPath, sigv4Headers, waitMs } from './journey-kit.ts';
@@ -24,6 +24,8 @@ import { xmlMembers } from './xml-view.ts';
24
24
 
25
25
  /** The pack repository the walk finds a vendor's pack in, set by the caller before its first walk. */
26
26
  let packsRoot: string | undefined;
27
+ /** Installed companions resolve from the selected pack, including its workspace-local dependencies. */
28
+ let dependenciesRoot: string | undefined;
27
29
  /** Where a socket step opens: an absolute `ws://` or `wss://` url on its own host, a path on the life's host otherwise. */
28
30
  function socketAt(open: string, host: string): { host: string; path: string } {
29
31
  if (!/^wss?:\/\//.test(open)) return { host, path: open };
@@ -36,8 +38,9 @@ let walkFacts: HostFacts = baselineFacts;
36
38
  let walkHosts = createHostTable(walkFacts).hosts;
37
39
  const directories = new Map<string, string | undefined>();
38
40
  const installedFacts = new Set<string>();
39
- export function usePacks(dir: string): void {
41
+ export function usePacks(dir: string, options: { dependenciesFrom?: string } = {}): void {
40
42
  packsRoot = dir;
43
+ dependenciesRoot = options.dependenciesFrom ?? dir;
41
44
  const installed: Record<string, HostFacts[string]> = {};
42
45
  for (const name of readdirSync(dir)) {
43
46
  const file = join(dir, name, 'generated/pack-facts.json');
@@ -51,9 +54,10 @@ export function usePacks(dir: string): void {
51
54
  directories.clear();
52
55
  }
53
56
  const packsDir = (): string => { if (packsRoot === undefined) throw new Error('usePacks(<packs dir>) first'); return packsRoot; };
57
+ const dependenciesDir = (): string => dependenciesRoot ?? packsDir();
54
58
  function directoryFor(vendor: string): string {
55
59
  if (!directories.has(vendor)) {
56
- const directory = walkPackDirectory(packsDir(), vendor);
60
+ const directory = walkPackDirectory(packsDir(), vendor, dependenciesDir());
57
61
  directories.set(vendor, directory);
58
62
  if (directory) {
59
63
  const name = vendor.split('/')[0]!;
@@ -79,6 +83,8 @@ function directoryFor(vendor: string): string {
79
83
  export function inMemoryWorld(label: string): { root: () => string; restore: () => void } {
80
84
  const previousStore = setActiveWorldStore(new MemoryWorldStore());
81
85
  const previousBlobs = setActiveBlobStore(new MemoryBlobStore());
86
+ const previousAssets = getActivePackAssets();
87
+ setActivePackAssets(new LocalPackAssets((vendor) => walkPackDirectory(packsDir(), vendor, dependenciesDir())));
82
88
  const previousClock = process.env.TWIN_WORLD_CLOCK_FILE;
83
89
  // sealed: nothing a walk does reaches a host outside the World (a webhook to a customer's server is refused, not sent)
84
90
  const previousEgress = process.env.VOLTER_TWIN_STRICT_EGRESS;
@@ -92,6 +98,7 @@ export function inMemoryWorld(label: string): { root: () => string; restore: ()
92
98
  restore: () => {
93
99
  setActiveWorldStore(previousStore);
94
100
  setActiveBlobStore(previousBlobs);
101
+ setActivePackAssets(previousAssets);
95
102
  if (previousClock === undefined) delete process.env.TWIN_WORLD_CLOCK_FILE; else process.env.TWIN_WORLD_CLOCK_FILE = previousClock;
96
103
  if (previousEgress === undefined) delete process.env.VOLTER_TWIN_STRICT_EGRESS; else process.env.VOLTER_TWIN_STRICT_EGRESS = previousEgress;
97
104
  },
@@ -107,7 +114,7 @@ function defaultType(body: unknown): string {
107
114
  if (typeof body !== 'string') return 'application/json';
108
115
  try { parseExactJson(body); return 'application/json'; } catch { return 'text/plain;charset=UTF-8'; }
109
116
  }
110
- const vendorFetch = (vendor: string) => vendorFetchFrom(packsDir(), vendor);
117
+ const vendorFetch = (vendor: string) => vendorFetchFrom(packsDir(), vendor, dependenciesDir());
111
118
  const shapes = new Map<string, Promise<ShapeChecker | undefined>>();
112
119
  const shapeOf = (vendor: string): Promise<ShapeChecker | undefined> => { if (!shapes.has(vendor)) shapes.set(vendor, shapeChecker(directoryFor(vendor))); return shapes.get(vendor)!; };
113
120
 
@@ -210,7 +217,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
210
217
  // the life's host is a hostname (the vendor's, `api.x.ai`); a URL there would put a second scheme in every request
211
218
  if (journey.host !== undefined && !/^[A-Za-z0-9.-]+(:\d+)?$/.test(journey.host)) { collect.push({ step: '(setup)', beat: 'HOST', detail: `the life's host "${journey.host}" is no hostname: write the vendor's host alone (api.example.com), no scheme or path` }); return {}; }
212
219
  const factory = await vendorFetch(journey.vendor);
213
- const views = await vendorAnswerViews(packsDir(), journey.vendor);
220
+ const views = await vendorAnswerViews(packsDir(), journey.vendor, dependenciesDir());
214
221
  if (!factory) { collect.push({ step: '(setup)', beat: 'ADAPTER', detail: `no create<Name>TwinFetch adapter enrolled for ${journey.vendor}` }); return {}; }
215
222
  // the life's companions (`companions`): the packs its vendor calls, each over this walk's root, on the same World and
216
223
  // clock; reached in process by the vendor's ctx.vendorFetch and by a step to a URL their host rules claim
@@ -323,7 +330,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
323
330
  // never a way back to the in-process stream (which would pass a listener wired apart from the World)
324
331
  if (served?.stream && Object.keys(step.connect).length) { collect.push({ step: step.name, beat: 'CONNECTION', detail: `a served walk opens "${step.on}" as the application does, over TCP to the listener; connect options (${Object.keys(step.connect).join(', ')}) configure only the in-process stream` }); return; }
325
332
  const viaServed = served?.stream;
326
- if (!viaServed) streams ??= await vendorStream(packsDir(), journey.vendor);
333
+ if (!viaServed) streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
327
334
  if (!viaServed && !streams) { collect.push({ step: step.name, beat: 'ADAPTER', detail: `no create<Name>TwinStream adapter enrolled for ${journey.vendor}` }); return; }
328
335
  const opened: Connection = { conn: undefined as unknown as TwinStreamConnection, out: '', closed: false, shape: {} };
329
336
  opened.conn = (viaServed ?? streams!(root, step.connect))({ write: (bytes) => { opened.out += decoder.decode(bytes); }, end: () => { opened.closed = true; } }, '127.0.0.1');
@@ -367,7 +374,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
367
374
  try {
368
375
  if (step.connect) {
369
376
  if (served?.stream && Object.keys(step.connect).length) throw new Error('a served RESP walk configures its server, not the client connection');
370
- if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor);
377
+ if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
371
378
  const factory = served?.stream ?? (respStream ??= streams?.(root, step.connect)); if (!factory) throw new Error('no byte-stream adapter');
372
379
  const c = { conn: undefined as unknown as TwinStreamConnection, out: '', closed: false };
373
380
  c.conn = factory({ write: (b) => { c.out += redis.binaryText(b); }, end: () => { c.closed = true; } }, '127.0.0.1');
@@ -405,7 +412,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
405
412
  try {
406
413
  let body: Record<string, unknown> | undefined;
407
414
  if (step.signIn) {
408
- if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor);
415
+ if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
409
416
  const factory = served?.stream ?? streams?.(root, {}); if (!factory) throw new Error(`no create<Name>TwinStream adapter enrolled for ${journey.vendor}`);
410
417
  let out: number[] = [];
411
418
  const conn = factory({ write: (b) => { out.push(...b); }, end: () => {} }, '127.0.0.1');
@@ -3,13 +3,13 @@ import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { installedVendorPacks, isDerivedPack, packVendor } from '@volter/world-core/runtime';
5
5
 
6
- export function walkPackDirectory(from: string, vendor: string): string | undefined {
6
+ export function walkPackDirectory(from: string, vendor: string, dependenciesFrom = from): string | undefined {
7
7
  if (!/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)?$/.test(vendor)) return undefined;
8
8
  const [name, lane] = vendor.split('/');
9
9
  const sibling = join(from, name!);
10
10
  const root = isDerivedPack(sibling) && packVendor(sibling) === name
11
11
  ? sibling
12
- : installedVendorPacks(from, { vendors: [name!] }).get(name!);
12
+ : installedVendorPacks(dependenciesFrom, { vendors: [name!] }).get(name!);
13
13
  const unit = root && (lane ? join(root, lane) : root);
14
14
  return unit && existsSync(join(unit, 'src', 'index.ts')) ? unit : undefined;
15
15
  }
@@ -13,8 +13,8 @@ type ApplicationStandIn = NonNullable<PackFetchOptions['application']>;
13
13
  * life's application answers stand in for the application the vendor asks (`application`, this fetch's alone). */
14
14
  export type VendorFetchFactory = (root: string, options?: { scenarioPath?: string; application?: ApplicationStandIn }) => (request: Request) => Response | Promise<Response>;
15
15
 
16
- export async function vendorFetch(twinDir: string, vendor: string): Promise<VendorFetchFactory | undefined> {
17
- const directory = walkPackDirectory(twinDir, vendor);
16
+ export async function vendorFetch(twinDir: string, vendor: string, dependenciesFrom = twinDir): Promise<VendorFetchFactory | undefined> {
17
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
18
18
  if (!directory) return undefined;
19
19
  let mod: Record<string, unknown>;
20
20
  try { mod = (await import(join(directory, 'src', 'index.ts'))) as Record<string, unknown>; } catch { return undefined; }
@@ -52,9 +52,9 @@ const jwtView = (field: string) => (body: unknown): unknown => {
52
52
  try { return JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8')); } catch { return null; }
53
53
  };
54
54
 
55
- export async function vendorAnswerViews(twinDir: string, vendor: string): Promise<AnswerViews> {
55
+ export async function vendorAnswerViews(twinDir: string, vendor: string, dependenciesFrom = twinDir): Promise<AnswerViews> {
56
56
  let own: AnswerViews = {};
57
- const directory = walkPackDirectory(twinDir, vendor);
57
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
58
58
  try {
59
59
  if (directory) own = ((await import(join(directory, 'src', 'index.ts'))) as { answerViews?: AnswerViews }).answerViews ?? {};
60
60
  } catch { /* none of its own */ }
@@ -65,8 +65,8 @@ export async function vendorAnswerViews(twinDir: string, vendor: string): Promis
65
65
  * hosted World hands a WebSocket's bytes. The walk drives it for a journey's line steps. */
66
66
  export type VendorStreamFactory = (root: string, options?: Record<string, unknown>) => TwinStream;
67
67
 
68
- export async function vendorStream(twinDir: string, vendor: string): Promise<VendorStreamFactory | undefined> {
69
- const directory = walkPackDirectory(twinDir, vendor);
68
+ export async function vendorStream(twinDir: string, vendor: string, dependenciesFrom = twinDir): Promise<VendorStreamFactory | undefined> {
69
+ const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
70
70
  if (!directory) return undefined;
71
71
  let mod: Record<string, unknown>;
72
72
  try { mod = (await import(join(directory, 'src', 'index.ts'))) as Record<string, unknown>; } catch { return undefined; }