@volter/twin-standard 1.0.32 → 1.0.34

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.
@@ -0,0 +1,240 @@
1
+ // Published descriptor compiler. Pack locations are explicit; imports happen only when called by a publisher.
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { PACK_ARCHETYPES, PROTOCOL_VERSION, protocolStanding } from '@volter/world-core/runtime';
5
+
6
+ export type PackFacts = {
7
+ /** Explicit vendor-server backing; this entry declares no pack protocol or factory. */
8
+ serverBacking?: { command: string };
9
+ maxRequestBodySize?: number;
10
+ adoption?: { sdks?: string[]; pypi?: string[]; scopes?: string[]; envStems?: string[]; worldIds?: string[]; tools?: Array<{ package: string; usage: 'build' | 'deployment' }>; configFiles?: Array<{ file: string; usage: 'application' | 'build' | 'deployment'; when?: string }> };
11
+ hosts?: Array<{ host?: string; suffix?: string; hostPattern?: string; pathPattern?: string; demandPathPattern?: string | false; key?: string; exclude?: true }>;
12
+ hostsNone?: string;
13
+ hostsClaimed?: { door: string; note: string };
14
+ /** The subject types the twin projects (packRegistry `resources`) — invariant R2 at the resource
15
+ * level checks a replay creates every one of them. */
16
+ resources?: string[];
17
+ /** The wire transport class (packRegistry PackTransport) — the invariant matrix keys
18
+ * variants off it too (a raw-tcp twin has no workerd fetch path, so R12b is n/a). */
19
+ transport?: string;
20
+ /** Optional native frontend of the same state owner, selected explicitly on the pack CLI. */
21
+ /** The serve-family (packRegistry PackArchetype) — the axis the invariant matrix keys
22
+ * strictness off. Closed enum, REQUIRED: every pack declares one (the catalog was
23
+ * fully classified 2026-09-02; the scaffolder's A1 TODO makes a new pack's
24
+ * ratification unskippable because an undeclared archetype is RED here). */
25
+ archetype?: string;
26
+ /** R16: the protocol major the package targets and its standing against this kernel. */
27
+ protocol?: { declared?: string; major: number; standing: 'current' | 'deprecated' | 'refused' | 'assumed' };
28
+ /** R20: pack-shipped asset paths (relative to the pack dir) the serve path reads through the seam. */
29
+ assets?: string[];
30
+ /** R2: declared types no replay can create, each with its reason (adopted debt, legible). */
31
+ resourcesUnreachable?: Record<string, string>;
32
+ endpointEnv?: { name: string; templates?: Record<string, string> };
33
+ endpointEnvNone?: string;
34
+ /** The door that issues the application a credential the twin accepts (see TwinPack.credentialDoor). */
35
+ credentialDoor?: { path: string; body: Record<string, unknown>; fill: Record<string, string> };
36
+ credentialIssuer?: string;
37
+ /** The Prisma driver adapter this database twin is reached through (see TwinPack.prismaAdapter). */
38
+ prismaAdapter?: { adapter: string; export: string; urlEnv: string };
39
+ /** The managed-infrastructure kind this pack is the World's backing for (architecture.md, "The descriptor"). */
40
+ managedService?: { kind: 'redis' | 'mongodb'; scheme: string; note: string };
41
+ /** The World's managed database the runtime binds the twin to (see TwinPack.managedDatabase). */
42
+ managedDatabase?: { kind: 'postgres'; arg: string; note: string };
43
+ /** Listeners the twin opens beside its main port (see TwinPack.sidePorts). */
44
+ sidePorts?: Array<{ name: string; option: string; flag: string; path?: string; note: string }>;
45
+ /** DERIVED, never declared: the pack's `create<Name>TwinServer` factory export — the
46
+ * `({port,root,readOnly}) => {port,stop}` contract the colocated host mounts
47
+ * (world-runtime/src/host.ts). Read off the module's own exports because the name is not
48
+ * mechanically derivable from the vendor string (createUpstashRedisTwinServer,
49
+ * createGoogleOAuthTwinServer); the export itself is the single source of truth. Consumed
50
+ * by `volter-world init` to emit `colocate:` service entries (runtime contract R2a). */
51
+ serveExport?: string;
52
+ };
53
+
54
+ export type PackFactsFile = {
55
+ '//': string;
56
+ /** Vendor → facts from its current descriptor or explicit vendor-server backing. */
57
+ packs: Record<string, PackFacts>;
58
+ /** vendor packs whose index.ts exports no `pack` descriptor yet — the migration worklist. */
59
+ undeclared: string[];
60
+ };
61
+
62
+ const sortObject = <T>(o: Record<string, T>): Record<string, T> =>
63
+ Object.fromEntries(Object.entries(o).sort(([a], [b]) => a.localeCompare(b)));
64
+
65
+ /** Host rules are DATA with a closed grammar (packRegistry HostRule): exactly one selector, regex
66
+ * sources that compile, keys that are one pack's alone (and never another pack's vendor id),
67
+ * and an exclude only where the same key has something to exclude from. */
68
+ function validateHostRules(vendor: string, rules: NonNullable<PackFacts['hosts']>, vendors: string[], claimedKeys: Map<string, string>, worldIds: string[]): void {
69
+ const keys = new Set<string>();
70
+ for (const rule of rules) {
71
+ const selectors = ['host', 'suffix', 'hostPattern'].filter((k) => (rule as Record<string, unknown>)[k] !== undefined);
72
+ if (selectors.length !== 1) throw new Error(`pack-facts: ${vendor} host rule ${JSON.stringify(rule)} — exactly one of host | suffix | hostPattern`);
73
+ for (const k of ['hostPattern', 'pathPattern'] as const) {
74
+ if (rule[k] !== undefined) { try { new RegExp(rule[k]!); } catch (e) { throw new Error(`pack-facts: ${vendor} host rule ${k} ${JSON.stringify(rule[k])} does not compile: ${(e as Error).message}`); } }
75
+ }
76
+ if (rule.demandPathPattern !== undefined && rule.demandPathPattern !== false) {
77
+ if (typeof rule.demandPathPattern !== 'string') throw new Error(`pack-facts: ${vendor} demandPathPattern must be a regex source or false`);
78
+ try { new RegExp(rule.demandPathPattern); } catch (e) { throw new Error(`pack-facts: ${vendor} demandPathPattern does not compile: ${(e as Error).message}`); }
79
+ }
80
+ if (rule.exclude !== undefined && rule.exclude !== true) throw new Error(`pack-facts: ${vendor} host rule ${JSON.stringify(rule)} — exclude is \`true\` or absent, never a falsy flag`);
81
+ const key = rule.key ?? vendor;
82
+ // A foreign key is a WORLD identity too: `volter-world init` emits <KEY>_TWIN_URL only for the
83
+ // ids in adoption.worldIds (covers.ts injectorVendorKeysFor), so a key not listed there would
84
+ // route in the injector and never be wired by the world — and a pack claiming ONLY foreign
85
+ // keys would leave its own <VENDOR>_TWIN_URL inert while looking covered.
86
+ if (key !== vendor && !worldIds.includes(key)) throw new Error(`pack-facts: ${vendor} claims key "${key}" but does not list it in adoption.worldIds — the routing identity must also be a world identity`);
87
+ if (!/^[a-z0-9-]+$/.test(key)) throw new Error(`pack-facts: ${vendor} host rule key ${JSON.stringify(key)} — keys are [a-z0-9-]+ (the <KEY>_TWIN_URL stem)`);
88
+ if (key !== vendor && vendors.includes(key)) throw new Error(`pack-facts: ${vendor} claims key "${key}", which is another pack's vendor id — a pack's hosts are its own`);
89
+ const owner = claimedKeys.get(key);
90
+ if (owner !== undefined && owner !== vendor) throw new Error(`pack-facts: key "${key}" is claimed by both ${owner} and ${vendor} — a key belongs to one pack`);
91
+ claimedKeys.set(key, vendor);
92
+ keys.add(key);
93
+ }
94
+ for (const key of keys) {
95
+ if (rules.filter((r) => (r.key ?? vendor) === key).every((r) => r.exclude === true)) throw new Error(`pack-facts: ${vendor} key "${key}" has only exclude rules — nothing to exclude from`);
96
+ }
97
+ // A pack may answer ONLY under foreign keys (aws: the consolidated twin has no `aws` host and
98
+ // AWS_TWIN_URL is null by design) — the worldIds rule above is what makes that explicit.
99
+ }
100
+
101
+ /** Compile descriptors at explicit package directories; no repository layout or publisher namespace is assumed. */
102
+ export async function compilePackFacts(dirs: Array<{ vendor: string; dir: string }>): Promise<PackFactsFile> {
103
+ const claimedKeys = new Map<string, string>();
104
+ const claimedTools = new Map<string, string>();
105
+ const packDirs = new Map(dirs.map((e) => [e.vendor, e.dir] as const));
106
+ const vendors = [...packDirs.keys()].sort();
107
+ const packs: Record<string, PackFacts> = {};
108
+ const undeclared: string[] = [];
109
+ for (const vendor of vendors) {
110
+ const entry = join(packDirs.get(vendor)!, 'src', 'index.ts');
111
+ if (!existsSync(entry)) { undeclared.push(vendor); continue; }
112
+ const mod = await import(entry);
113
+ const pack = mod.pack;
114
+ if (pack === undefined || typeof pack.vendor !== 'string') { undeclared.push(vendor); continue; }
115
+ if (pack.vendor !== vendor) {
116
+ throw new Error(`pack-facts: ${entry} declares vendor "${pack.vendor}" but lives in ${vendor}/ of the pack repository — the directory IS the identity; rename one.`);
117
+ }
118
+ if (pack.hosts !== undefined && pack.hostsNone !== undefined) {
119
+ throw new Error(`pack-facts: ${vendor} declares BOTH hosts and hostsNone — a ruling is one or the other.`);
120
+ }
121
+ if (pack.hosts !== undefined) validateHostRules(vendor, pack.hosts, vendors, claimedKeys, pack.adoption?.worldIds ?? []);
122
+ for (const tool of pack.adoption?.tools ?? []) {
123
+ if (!tool || typeof tool.package !== 'string' || tool.package === '' || (tool.usage !== 'build' && tool.usage !== 'deployment')) {
124
+ throw new Error(`pack-facts: ${vendor} adoption tool must be { package, usage: "build" | "deployment" }`);
125
+ }
126
+ const owner = claimedTools.get(tool.package);
127
+ if (owner !== undefined && owner !== vendor) throw new Error(`pack-facts: tool package "${tool.package}" is claimed by both ${owner} and ${vendor}`);
128
+ claimedTools.set(tool.package, vendor);
129
+ }
130
+ // the World reads its name and templates (init.ts, served-world.ts): a bare string sets nothing, silently
131
+ const ee = pack.endpointEnv as unknown;
132
+ if (ee !== undefined && (typeof ee !== 'object' || ee === null || typeof (ee as { name?: unknown }).name !== 'string' || typeof (ee as { note?: unknown }).note !== 'string')) {
133
+ throw new Error(`pack-facts: ${vendor}'s endpointEnv is ${JSON.stringify(ee)}; it is { name, templates?, note } (the variable the vendor's SDK reads, and why)`);
134
+ }
135
+ if (pack.endpointEnv !== undefined && pack.endpointEnvNone !== undefined) {
136
+ throw new Error(`pack-facts: ${vendor} declares BOTH endpointEnv and endpointEnvNone — a ruling is one or the other.`);
137
+ }
138
+ if (pack.archetype === undefined) {
139
+ throw new Error(`pack-facts: ${vendor} declares no archetype — every pack ratifies its serve-family (closed enum: ${PACK_ARCHETYPES.join(' | ')}; see packRegistry PackArchetype)`);
140
+ }
141
+ if (!(PACK_ARCHETYPES as readonly string[]).includes(pack.archetype)) {
142
+ throw new Error(`pack-facts: ${vendor} declares unknown archetype "${String(pack.archetype)}" (closed enum: ${PACK_ARCHETYPES.join(' | ')})`);
143
+ }
144
+ // raw-tcp consistency: the one raw-protocol pack today serves CRUD state over its wire.
145
+ if (pack.archetype === 'proxy' && pack.transport === 'raw-tcp') {
146
+ throw new Error(`pack-facts: ${vendor} — proxy archetype over raw-tcp transport is undeclared territory; rule it in the contract first.`);
147
+ }
148
+ const facts: PackFacts = {};
149
+ if (pack.adoption !== undefined) facts.adoption = pack.adoption;
150
+ if (pack.hosts !== undefined) facts.hosts = pack.hosts;
151
+ if (pack.hostsNone !== undefined) facts.hostsNone = pack.hostsNone;
152
+ if (pack.hostsClaimed !== undefined) {
153
+ if (!pack.hostsClaimed.door.startsWith('/') || !pack.hostsClaimed.note.trim()) throw new Error(`pack-facts: ${vendor} hostsClaimed needs a door path and a note`);
154
+ if (pack.hosts === undefined) throw new Error(`pack-facts: ${vendor} claims hosts at runtime but declares none of its own: its door is reached on its own host`);
155
+ facts.hostsClaimed = pack.hostsClaimed;
156
+ }
157
+ facts.archetype = pack.archetype;
158
+ // R16 — the protocol the package targets, and where it stands against this kernel: the kernel serves its own
159
+ // major and no other, so any other declaration, or none, is refused here.
160
+ {
161
+ const standing = protocolStanding(pack.protocol);
162
+ if (standing.standing === 'refused') throw new Error(`pack-facts: ${vendor} declares protocol ${JSON.stringify(pack.protocol)} — this kernel serves ${PROTOCOL_VERSION} and no other major (runtime contract R16)`);
163
+ facts.protocol = { ...(pack.protocol !== undefined ? { declared: pack.protocol } : {}), major: standing.major, standing: standing.standing };
164
+ }
165
+ facts.transport = pack.transport;
166
+ if (pack.maxRequestBodySize !== undefined) {
167
+ if (!Number.isSafeInteger(pack.maxRequestBodySize) || pack.maxRequestBodySize <= 0) throw new Error(`pack-facts: ${vendor} maxRequestBodySize must be positive integer bytes`);
168
+ facts.maxRequestBodySize = pack.maxRequestBodySize;
169
+ }
170
+ if (pack.assets !== undefined) facts.assets = pack.assets; // R20 — what the deploy step places behind the assets binding
171
+ if (pack.resourcesUnreachable !== undefined) facts.resourcesUnreachable = pack.resourcesUnreachable; // R2 adopted-as-debt, by name and reason
172
+ facts.resources = pack.resources; // the declared subject types — R2's resource-level claim
173
+ if (pack.endpointEnv !== undefined) facts.endpointEnv = pack.endpointEnv;
174
+ if (pack.endpointEnvNone !== undefined) facts.endpointEnvNone = pack.endpointEnvNone;
175
+ if (pack.credentialDoor !== undefined) facts.credentialDoor = pack.credentialDoor;
176
+ if (pack.credentialIssuer !== undefined) {
177
+ if (!/^[a-z][a-z0-9-]*$/.test(pack.credentialIssuer) || pack.credentialIssuer === vendor) throw new Error(`pack-facts: ${vendor} credentialIssuer must name another pack`);
178
+ facts.credentialIssuer = pack.credentialIssuer;
179
+ }
180
+ if (pack.prismaAdapter !== undefined) {
181
+ if (pack.endpointEnv?.templates?.[pack.prismaAdapter.urlEnv] === undefined && pack.credentialDoor?.fill[pack.prismaAdapter.urlEnv] === undefined) {
182
+ throw new Error(`pack-facts: ${vendor} declares prismaAdapter.urlEnv "${pack.prismaAdapter.urlEnv}" but neither its endpointEnv templates nor its credentialDoor fill such a variable — the adapter's URL is one of theirs.`);
183
+ }
184
+ facts.prismaAdapter = pack.prismaAdapter;
185
+ }
186
+ if (pack.managedDatabase !== undefined) {
187
+ const md = pack.managedDatabase as { kind?: unknown; arg?: unknown; note?: unknown };
188
+ if (md.kind !== 'postgres') throw new Error(`pack-facts: ${vendor} declares managedDatabase.kind "${String(md.kind)}"; the runtime binds only the World's managed postgres`);
189
+ if (typeof md.arg !== 'string' || !/^--[a-z][a-z-]*$/.test(md.arg)) throw new Error(`pack-facts: ${vendor} declares managedDatabase.arg "${String(md.arg)}"; it is the serve flag the URL follows (--name)`);
190
+ if (typeof md.note !== 'string' || md.note.trim() === '') throw new Error(`pack-facts: ${vendor} declares managedDatabase with no note`);
191
+ facts.managedDatabase = pack.managedDatabase;
192
+ }
193
+ if (pack.managedService !== undefined) {
194
+ const ms = pack.managedService as { kind?: unknown; scheme?: unknown; note?: unknown };
195
+ if (ms.kind !== 'redis' && ms.kind !== 'mongodb') throw new Error(`pack-facts: ${vendor} declares managedService.kind "${String(ms.kind)}"; the managed-infrastructure kinds a twin backs are redis and mongodb`);
196
+ if (typeof ms.scheme !== 'string' || !/^[a-z][a-z0-9+.-]*$/.test(ms.scheme)) throw new Error(`pack-facts: ${vendor} declares managedService.scheme "${String(ms.scheme)}"; it is the published URL's scheme`);
197
+ if (typeof ms.note !== 'string' || ms.note.trim() === '') throw new Error(`pack-facts: ${vendor} declares managedService with no note`);
198
+ if (pack.bin === undefined) throw new Error(`pack-facts: ${vendor} declares managedService with no bin; the managed infrastructure starts the pack by its bin`);
199
+ const other = Object.entries(packs).find(([, f]) => f.managedService?.kind === ms.kind);
200
+ if (other) throw new Error(`pack-facts: ${vendor} and ${other[0]} both declare managedService.kind "${ms.kind}"; one pack backs a kind`);
201
+ facts.managedService = pack.managedService;
202
+ }
203
+ if (pack.sidePorts !== undefined) {
204
+ for (const side of pack.sidePorts as Array<Record<string, unknown>>) {
205
+ if (typeof side.name !== 'string' || !/^[a-z][a-z0-9-]*$/.test(side.name)) throw new Error(`pack-facts: ${vendor} declares a side port named "${String(side.name)}"; a name is lowercase (inspect)`);
206
+ if (typeof side.option !== 'string' || !/^[a-z][A-Za-z0-9]*$/.test(side.option)) throw new Error(`pack-facts: ${vendor} side port ${side.name}: option "${String(side.option)}" is the factory's camelCase option`);
207
+ if (typeof side.flag !== 'string' || !/^--[a-z][a-z-]*$/.test(side.flag)) throw new Error(`pack-facts: ${vendor} side port ${side.name}: flag "${String(side.flag)}" is the serve flag (--name)`);
208
+ if (side.path !== undefined && (typeof side.path !== 'string' || !side.path.startsWith('/'))) throw new Error(`pack-facts: ${vendor} side port ${side.name}: path "${String(side.path)}" starts with /`);
209
+ if (typeof side.note !== 'string' || side.note.trim() === '') throw new Error(`pack-facts: ${vendor} side port ${side.name} has no note`);
210
+ }
211
+ facts.sidePorts = pack.sidePorts;
212
+ }
213
+ // The serve factory is DERIVED from the module's exports (see PackFacts.serveExport);
214
+ // a pack exporting SEVERAL factories must declare which one `cli.ts serve` boots — the
215
+ // exports alone cannot reveal that judgment (linear defaults to its DERIVED server).
216
+ const serveExports = Object.keys(mod).filter(
217
+ (k) => /^create\w*TwinServer$/.test(k) && typeof (mod as Record<string, unknown>)[k] === 'function',
218
+ );
219
+ if (pack.serveExport !== undefined) {
220
+ if (!serveExports.includes(pack.serveExport)) {
221
+ throw new Error(`pack-facts: ${vendor} declares serveExport "${pack.serveExport}" but its index exports no such create*TwinServer factory (found: ${serveExports.join(', ') || 'none'}).`);
222
+ }
223
+ facts.serveExport = pack.serveExport;
224
+ } else if (serveExports.length > 1) {
225
+ throw new Error(`pack-facts: ${vendor} exports ${serveExports.length} create*TwinServer factories (${serveExports.join(', ')}) — declare \`serveExport\` on its descriptor naming the one cli.ts serve boots.`);
226
+ } else if (serveExports.length === 1) {
227
+ facts.serveExport = serveExports[0]!;
228
+ }
229
+ if (Object.keys(facts).length > 0) packs[vendor] = facts;
230
+ }
231
+ return {
232
+ '//': 'GENERATED by scripts/pack-facts.ts — current pack descriptors and explicit world-runtime server backings; no prior-artifact entries are retained.',
233
+ packs: sortObject(packs),
234
+ undeclared,
235
+ };
236
+ }
237
+
238
+ export function renderPackFacts(file: PackFactsFile): string {
239
+ return JSON.stringify(file, null, 2) + '\n';
240
+ }
@@ -12,10 +12,10 @@ import { attributionName } from '../verdict.ts';
12
12
  import { redis } from '@volter/world-core';
13
13
  import { gzipSync } from 'node:zlib';
14
14
  import { parse, visit } from 'graphql';
15
- import { existsSync, readFileSync } from 'node:fs';
15
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
16
16
  import { join } from 'node:path';
17
17
  import { databaseSession, type DatabaseSession, getActiveWorldStore, MemoryBlobStore, MemoryWorldStore, setActiveBlobStore, setActiveWorldStore, setDeliveryObserver, setInProcessTwins, type InProcessTwin, type TwinStream, type TwinStreamConnection } from '@volter/world-core/runtime';
18
- import { VENDOR_HOSTS } from '@volter/world-core/vendor-hosts';
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, isResp, at, captureAt, echoFailures, expandFills, expectFailures, fill, fillJson, formBody, isGit, isLine, isSql, isSocket, isWait, jwtAuthorization, oauth1Authorization, presignedPath, sigv4Headers, waitMs } from './journey-kit.ts';
21
21
  import { lanesOf, lineShape, shapeChecker, type LineConnection, type ShapeChecker } from './shape.ts';
@@ -30,7 +30,20 @@ function socketAt(open: string, host: string): { host: string; path: string } {
30
30
  return { host: u.host, path: `${u.pathname}${u.search}` };
31
31
  }
32
32
 
33
- export function usePacks(dir: string): void { packsRoot = dir; }
33
+ const baselineFacts = packFactsDocument.packs as unknown as HostFacts;
34
+ let walkFacts: HostFacts = baselineFacts;
35
+ let walkHosts = createHostTable(walkFacts).hosts;
36
+ export function usePacks(dir: string): void {
37
+ packsRoot = dir;
38
+ const installed: Record<string, HostFacts[string]> = {};
39
+ for (const name of readdirSync(dir)) {
40
+ const file = join(dir, name, 'generated/pack-facts.json');
41
+ if (existsSync(file)) Object.assign(installed, JSON.parse(readFileSync(file, 'utf8')).packs);
42
+ }
43
+ walkFacts = { ...baselineFacts, ...installed };
44
+ walkHosts = createHostTable(walkFacts, Object.keys(installed)).hosts;
45
+ shapes.clear(); fieldsByVendor.clear();
46
+ }
34
47
  const packsDir = (): string => { if (packsRoot === undefined) throw new Error('usePacks(<packs dir>) first'); return packsRoot; };
35
48
 
36
49
  /** The walk's World: the twin's own code on its in-memory World and blob stores (the backends a serverless
@@ -78,14 +91,14 @@ const shapeOf = (vendor: string): Promise<ShapeChecker | undefined> => { if (!sh
78
91
  * name, as vendor-hosts.cjs compiles them. */
79
92
  function routingKeys(vendor: string): string[] {
80
93
  const name = vendor.split('/')[0]!;
81
- const hosts = ((packFactsDocument as { packs: Record<string, { hosts?: Array<{ key?: string }> }> }).packs[name]?.hosts) ?? [];
82
- return [...new Set(hosts.map((rule) => rule.key ?? name))].filter((key) => VENDOR_HOSTS[key] !== undefined);
94
+ const hosts = walkFacts[name]?.hosts ?? [];
95
+ return [...new Set(hosts.map((rule) => rule.key ?? name))].filter((key) => walkHosts[key] !== undefined);
83
96
  }
84
97
  /** Whether one of `keys`' host rules claims `url` (an absolute URL; a path is the life's own host). */
85
98
  const claims = (keys: string[], url: string): boolean => {
86
99
  if (!/^https?:\/\//.test(url) || !URL.canParse(url)) return false;
87
100
  const u = new URL(url);
88
- return keys.some((key) => VENDOR_HOSTS[key]!(u.hostname, u.pathname));
101
+ return keys.some((key) => walkHosts[key]!(u.hostname, u.pathname));
89
102
  };
90
103
 
91
104
  type Answer = { status: number; body: unknown; text: string; headers: Record<string, string> };
@@ -155,7 +168,7 @@ function takeReplies(c: Connection): LineReply[] {
155
168
  /** A pack served for a walk the way a World runs it (its server.ts on a port, the World's root and scenario its options):
156
169
  * the walk's requests go to it over HTTP, the vendor's host in their Host header, instead of to the fetch in process;
157
170
  * its sockets are WebSockets to that port, and a line connection (`stream`) is a TCP connection to its protocol port. */
158
- export type ServedTwin = (root: string, options: { scenarioPath?: string }) => Promise<{ fetch: (request: Request) => Promise<Response>; stop: () => void; socket?: (url: string, headers: Record<string, string>) => WebSocket; stream?: TwinStream }>;
171
+ export type ServedTwin = (root: string, options: { scenarioPath?: string; application?: import('@volter/world-core').PackFetchOptions['application'] }) => Promise<{ fetch: (request: Request) => Promise<Response>; stop: () => void; socket?: (url: string, headers: Record<string, string>) => WebSocket; stream?: TwinStream }>;
159
172
 
160
173
  export async function walk(journey: Journey, root: string, collect: Failure[], opts: { beats: boolean; verbose?: boolean; hooks?: StepHooks; captured?: Captured; served?: ServedTwin }): Promise<Captured> {
161
174
  attributionName(journey.author);
@@ -206,7 +219,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
206
219
  }
207
220
  return { status: a!.status, ...(a!.headers ? { headers: a!.headers } : {}), ...(a!.bodyError === undefined ? {} : { bodyError: a!.bodyError }), body: typeof a!.body === 'string' ? a!.body : a!.body === undefined ? '' : JSON.stringify(a!.body) };
208
221
  };
209
- const served = opts.served ? await opts.served(root, scenarioPath ? { scenarioPath } : {}) : undefined;
222
+ const served = opts.served ? await opts.served(root, { ...(scenarioPath ? { scenarioPath } : {}), application }) : undefined;
210
223
  const twin = served ? served.fetch : factory(root, { ...(scenarioPath ? { scenarioPath } : {}), application });
211
224
  const shape = await shapeOf(journey.vendor);
212
225
  const host = journey.host ?? 'twin.local';
@@ -2,7 +2,6 @@
2
2
  // index. What the catalog's walkers drive (behavior journeys, the spec census probe, the invariants)
3
3
  // since the worker render left with the v1 host.
4
4
  import { join } from 'node:path';
5
- import packFacts from '@volter/world-core/generated/pack-facts.json' with { type: 'json' };
6
5
  import { type PgliteHost, startPgliteHost } from './walk-database.ts';
7
6
  import { type TwinStream, worldNow } from '@volter/world-core/runtime';
8
7
  import type { PackFetchOptions } from '@volter/world-core';
@@ -20,7 +19,7 @@ export async function vendorFetch(twinDir: string, vendor: string): Promise<Vend
20
19
  const name = Object.keys(mod).find((k) => /^create\w+?Fetch$/.test(k) && !/TwinFetch$/.test(k) && typeof mod[k] === 'function');
21
20
  if (!name) return undefined;
22
21
  const factory = mod[name] as (options: { root: string; database?: string; scenarioPath?: string; application?: ApplicationStandIn }) => ((request: Request) => Response | Promise<Response>) & Record<string, unknown>;
23
- if (!declaresManagedDatabase(vendor)) return (root: string, options = {}) => factory({ root, ...options });
22
+ if (!(mod.pack as { managedDatabase?: unknown } | undefined)?.managedDatabase) return (root: string, options = {}) => factory({ root, ...options });
24
23
  // A pack whose data plane is the World's managed Postgres is walked over one, as a World binds it: each World (each
25
24
  // root) its own fresh database, started on its first request and stopped when the walk's process ends, its clock the
26
25
  // walked World's (set before each request: the walk moves the World clock between steps).
@@ -41,12 +40,6 @@ export async function vendorFetch(twinDir: string, vendor: string): Promise<Vend
41
40
  const hosts: PgliteHost[] = [];
42
41
  process.once('exit', () => { for (const host of hosts) { try { process.kill(host.pid, 'SIGTERM'); } catch { /* ended */ } } });
43
42
 
44
- /** Whether a pack declares the World's managed database (its descriptor's `managedDatabase`, in the pack facts: this
45
- * checkout's kernel's, as the walk's other readers of them import them). */
46
- function declaresManagedDatabase(vendor: string): boolean {
47
- return (packFacts as { packs: Record<string, { managedDatabase?: unknown }> }).packs[vendor.split('/')[0]!]?.managedDatabase !== undefined;
48
- }
49
-
50
43
  /** Named views of an answer: what a journey step's `view` asserts against. The kit's own: `jwt:<field>`, the payload of the
51
44
  * JSON Web Token at that field of the answer, decoded as the application verifying it reads it (a session token's
52
45
  * claims). A pack's own (`answerViews` in its index) beside them. */