@volter/twin-standard 1.0.154 → 1.0.155

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.
@@ -371,7 +371,7 @@ export function cliFile(vendor, live = false) {
371
371
  const Name = pascal(vendor);
372
372
  const ingest = live ? `// the RTMP ingest's side port, which the World allocates (the manifest's sidePorts)\nconst ingestPort = Number(optionValue(rest, '--ingest-port', '0')) || undefined;\n` : '';
373
373
  const served = live ? ` process.stdout.write(s.ingestPort ? \`${vendor} twin ingest at rtmp://127.0.0.1:\${s.ingestPort}\\n\` : '${vendor} twin: no RTMP ingest\\n');\n` : '';
374
- return `#!/usr/bin/env node\n// world-${vendor}: serve the ${vendor} twin.\nimport { hasFlag, optionValue } from '@volter/world-core/args';\nimport { keepProcessAlive } from '@volter/world-core/lifecycle';\nimport { create${Name}TwinServer } from './server.ts';\n\nconst [cmd, ...rest] = process.argv.slice(2);\nconst port = Number(optionValue(rest, '--port', '0')) || undefined;\nconst root = optionValue(rest, '--root') || undefined;\nconst readOnly = hasFlag(rest, '--read-only');\n// the World's managed Postgres, which the runtime binds a pack declaring managedDatabase to\nconst database = optionValue(rest, '--database') || undefined;\n// the World's scenario file, for a model vendor's turns\nconst scenarioPath = optionValue(rest, '--scenario') || undefined;\n${ingest}\nif (cmd === 'serve' || cmd === undefined) {\n const s = await create${Name}TwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}), ...(database ? { database } : {}), ...(scenarioPath ? { scenarioPath } : {})${live ? ', ...(ingestPort ? { ingestPort } : {})' : ''} });\n process.stdout.write(\`${vendor} twin\${readOnly ? ' [read-only]' : ''} at http://127.0.0.1:\${s.port}\\n\`);\n${served} await keepProcessAlive();\n} else {\n process.stdout.write('Usage: world-${vendor} serve [--port N] [--root DIR] [--read-only] [--database URL] [--scenario FILE]${live ? ' [--ingest-port N]' : ''}\\n');\n}\n`;
374
+ return `#!/usr/bin/env node\n// world-${vendor}: serve the ${vendor} twin.\nimport { hasFlag, optionValue } from '@volter/world-core/args';\nimport { keepProcessAlive } from '@volter/world-core/lifecycle';\nimport { create${Name}TwinServer } from './server.ts';\n\nconst [cmd, ...rest] = process.argv.slice(2);\nconst port = Number(optionValue(rest, '--port', '0')) || undefined;\nconst root = optionValue(rest, '--root') || undefined;\nconst readOnly = hasFlag(rest, '--read-only');\n// the selected twin's private Postgres, which the runtime binds a pack declaring managedDatabase to\nconst database = optionValue(rest, '--database') || undefined;\n// the World's scenario file, for a model vendor's turns\nconst scenarioPath = optionValue(rest, '--scenario') || undefined;\n${ingest}\nif (cmd === 'serve' || cmd === undefined) {\n const s = await create${Name}TwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}), ...(database ? { database } : {}), ...(scenarioPath ? { scenarioPath } : {})${live ? ', ...(ingestPort ? { ingestPort } : {})' : ''} });\n process.stdout.write(\`${vendor} twin\${readOnly ? ' [read-only]' : ''} at http://127.0.0.1:\${s.port}\\n\`);\n${served} await keepProcessAlive();\n} else {\n process.stdout.write('Usage: world-${vendor} serve [--port N] [--root DIR] [--read-only] [--database URL] [--scenario FILE]${live ? ' [--ingest-port N]' : ''}\\n');\n}\n`;
375
375
  }
376
376
  /** A vendor id as a name in code: `npm-registry` is `NpmRegistry`. */
377
377
  const pascal = (vendor) => vendor.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join('');
@@ -421,7 +421,6 @@ Contributor mechanics belong to the [pack procedure](https://world-docs.volter.a
421
421
  }
422
422
  export function initFiles(vendor) {
423
423
  const Name = pascal(vendor);
424
- const semantics = `${Name[0].toLowerCase()}${Name.slice(1)}Semantics`;
425
424
  return {
426
425
  'package.json': `${JSON.stringify({
427
426
  name: `@volter/twin-${vendor}`, version: '1.0.0', description: `The ${vendor} twin (Protocol 3).`, author: 'Volter (https://github.com/volter-ai)', license: 'Apache-2.0',
@@ -454,8 +453,6 @@ export function initFiles(vendor) {
454
453
  * surface of its own under `<lane>/src`, keeping the vendor's one store and sharing its `src/semantics/shared.ts`, and the
455
454
  * pack's server routing each lane's host to it. */
456
455
  export function laneInitFiles(vendor, lanes) {
457
- const Name = pascal(vendor);
458
- const camel = (s) => { const p = pascal(s); return `${p[0].toLowerCase()}${p.slice(1)}`; };
459
456
  const single = initFiles(vendor);
460
457
  const files = {
461
458
  'package.json': single['package.json'].replace('"spec",', lanes.map((l) => `"${l}",`).join('\n ')),
@@ -1,3 +1,4 @@
1
+ import { type TwinPack } from '@volter/world-core/runtime';
1
2
  export type PackFacts = {
2
3
  /** Explicit vendor-server backing; this entry declares no pack protocol or factory. */
3
4
  serverBacking?: {
@@ -62,11 +63,7 @@ export type PackFacts = {
62
63
  };
63
64
  endpointEnvNone?: string;
64
65
  /** The door that issues the application a credential the twin accepts (see TwinPack.credentialDoor). */
65
- credentialDoor?: {
66
- path: string;
67
- body: Record<string, unknown>;
68
- fill: Record<string, string>;
69
- };
66
+ credentialDoor?: TwinPack['credentialDoor'];
70
67
  credentialIssuer?: string;
71
68
  /** The Prisma driver adapter this database twin is reached through (see TwinPack.prismaAdapter). */
72
69
  prismaAdapter?: {
@@ -88,10 +85,14 @@ export type PackFacts = {
88
85
  paths: string[];
89
86
  };
90
87
  };
91
- /** The World's managed database the runtime binds the twin to (see TwinPack.managedDatabase). */
88
+ /** The selected twin's private SQL identity and credential field (see TwinPack.managedDatabase). */
92
89
  managedDatabase?: {
93
90
  kind: 'postgres';
94
91
  arg: string;
92
+ user: string;
93
+ database: string;
94
+ clock: 'wall';
95
+ credentialField: string;
95
96
  note: string;
96
97
  };
97
98
  /** Listeners the twin opens beside its main port (see TwinPack.sidePorts). */
@@ -175,9 +175,16 @@ export async function compilePackFacts(dirs) {
175
175
  if (pack.managedDatabase !== undefined) {
176
176
  const md = pack.managedDatabase;
177
177
  if (md.kind !== 'postgres')
178
- throw new Error(`pack-facts: ${vendor} declares managedDatabase.kind "${String(md.kind)}"; the runtime binds only the World's managed postgres`);
178
+ throw new Error(`pack-facts: ${vendor} declares managedDatabase.kind "${String(md.kind)}"; private SQL supports postgres`);
179
179
  if (typeof md.arg !== 'string' || !/^--[a-z][a-z-]*$/.test(md.arg))
180
180
  throw new Error(`pack-facts: ${vendor} declares managedDatabase.arg "${String(md.arg)}"; it is the serve flag the URL follows (--name)`);
181
+ for (const key of ['user', 'database'])
182
+ if (typeof md[key] !== 'string' || !md[key] || /[\r\n\0$]/u.test(md[key]) || Buffer.byteLength(md[key], 'utf8') > 63)
183
+ throw new Error(`pack-facts: ${vendor} private database ${key} must be a literal PostgreSQL identifier of at most 63 bytes`);
184
+ if (md.clock !== 'wall')
185
+ throw new Error(`pack-facts: ${vendor} private postgres must declare its wall clock`);
186
+ if (typeof md.credentialField !== 'string' || !Object.values(pack.credentialDoor?.fill ?? {}).includes(md.credentialField) || /^(?:file|directory|side):/.test(md.credentialField))
187
+ throw new Error(`pack-facts: ${vendor} private database must name its credential door's scalar URL field`);
181
188
  if (typeof md.note !== 'string' || md.note.trim() === '')
182
189
  throw new Error(`pack-facts: ${vendor} declares managedDatabase with no note`);
183
190
  facts.managedDatabase = pack.managedDatabase;
@@ -367,8 +367,8 @@ export type WaitStep = {
367
367
  wait: string;
368
368
  };
369
369
  export declare const isWait: (step: Step) => step is WaitStep;
370
- /** What an application sends the World's managed database over the database's own wire, not through the vendor's API (a
371
- * pack whose data plane is the World's Postgres, the descriptor's `managedDatabase`): its migrations as its deploy applies
370
+ /** What an application sends the selected twin's database over its SQL wire, not through the vendor's HTTP API (a
371
+ * pack whose descriptor declares a private SQL product (`managedDatabase`)): its migrations as its deploy applies
372
372
  * them and the queries it sends, each on the application's one connection to the walked World's database (world-core
373
373
  * `databaseSession`), so a transaction it begins stays open into its next steps, as on its own connection. `sql` alone is
374
374
  * one simple Query (a migration file, a `BEGIN`); with `values` it is one extended-protocol statement, the values bind
@@ -4,4 +4,7 @@ export type ServedPack = {
4
4
  stop: () => void | Promise<void>;
5
5
  managedDatabaseUrl?: () => Promise<string>;
6
6
  };
7
- export declare function servePack(pack: string, options: Record<string, unknown>): Promise<ServedPack>;
7
+ export declare function servePack(pack: string, options: {
8
+ root?: string;
9
+ scenarioPath?: string;
10
+ }): Promise<ServedPack>;
@@ -8,8 +8,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
8
8
  };
9
9
  // The standard serves a pack with its declared backing, as the runtime binds it in a World.
10
10
  import { join } from 'node:path';
11
- import { worldNow } from '@volter/world-core/runtime';
12
- import { startPgliteHost } from "./walk-database.js";
11
+ import { startTwinDatabase } from "./walk-database.js";
13
12
  export async function servePack(pack, options) {
14
13
  const mod = await import(__rewriteRelativeImportExtension(join(pack, 'src', 'server.ts')));
15
14
  const create = Object.entries(mod).find(([name, value]) => /^create\w+TwinServer$/.test(name) && typeof value === 'function')?.[1];
@@ -17,8 +16,8 @@ export async function servePack(pack, options) {
17
16
  throw new Error('src/server.ts exports no create<Vendor>TwinServer');
18
17
  const descriptor = (await import(__rewriteRelativeImportExtension(join(pack, 'src', 'index.ts')))).pack;
19
18
  let database;
20
- if (descriptor?.managedDatabase?.kind === 'postgres' && options.database === undefined)
21
- database = await startPgliteHost();
19
+ if (descriptor?.managedDatabase?.kind === 'postgres')
20
+ database = await startTwinDatabase(pack, descriptor);
22
21
  let server;
23
22
  try {
24
23
  server = await create({ port: 0, ...options, ...(database ? { database: database.url } : {}) });
@@ -33,7 +32,7 @@ export async function servePack(pack, options) {
33
32
  let stopping;
34
33
  return {
35
34
  ...server,
36
- managedDatabaseUrl: async () => { backing.clock(worldNow()); return backing.url; },
35
+ managedDatabaseUrl: async () => backing.url,
37
36
  stop: () => stopping ??= (async () => { await server.stop(); await backing.stop(); })(),
38
37
  };
39
38
  }
@@ -10,8 +10,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
10
10
  // index. What the catalog's walkers drive (behavior journeys, the spec census probe, the invariants)
11
11
  // since the worker render left with the v1 host.
12
12
  import { join } from 'node:path';
13
- import { startPgliteHost } from "./walk-database.js";
14
- import { worldNow } from '@volter/world-core/runtime';
13
+ import { startTwinDatabase } from "./walk-database.js";
15
14
  import { walkPackDirectory } from "./pack-directory.js";
16
15
  export async function vendorFetch(twinDir, vendor, dependenciesFrom = twinDir) {
17
16
  const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
@@ -29,20 +28,20 @@ export async function vendorFetch(twinDir, vendor, dependenciesFrom = twinDir) {
29
28
  if (!name)
30
29
  return undefined;
31
30
  const factory = mod[name];
32
- if (!mod.pack?.managedDatabase)
31
+ const descriptor = mod.pack;
32
+ if (!descriptor?.managedDatabase)
33
33
  return (root, options = {}) => factory({ root, ...options });
34
- // A pack whose data plane is the World's managed Postgres is walked over one, as a World binds it: each World (each
35
- // root) its own fresh database, started on its first request and stopped when the walk's process ends, its clock the
36
- // walked World's (set before each request: the walk moves the World clock between steps).
34
+ // Each walked twin owns a fresh SQL product through the ordinary selected-twin lifecycle.
35
+ // Native SQL uses the descriptor's explicit wall clock; kernel World time is not claimed for it.
37
36
  return (root, options = {}) => {
38
37
  const bare = factory({ root, ...options });
39
38
  let bound;
40
39
  let host;
41
- const bind = () => (bound ??= startPgliteHost().then((started) => { host = started; return factory({ root, database: started.url, ...options }); }));
42
- const served = (request) => bind().then((fetch) => { host.clock(worldNow()); return fetch(request); });
43
- // the World's database itself, for what an application sends it over its own wire (a SQL step: journey-kit.ts)
44
- const managedDatabaseUrl = () => bind().then(() => { host.clock(worldNow()); return host.url; });
45
- // the walk's end stops its World's database, so the next World walked declares it at the same port
40
+ const bind = () => (bound ??= startTwinDatabase(directory, descriptor).then((started) => { host = started; return factory({ root, database: started.url, ...options }); }));
41
+ const served = (request) => bind().then((fetch) => { return fetch(request); });
42
+ // The twin's SQL product, for what an application sends over its own wire (journey-kit.ts).
43
+ const managedDatabaseUrl = () => bind().then(() => { return host.url; });
44
+ // The walk's end retires the selected twin's private engine through its World.
46
45
  const stopDatabase = async () => { if (bound === undefined)
47
46
  return; await bound.catch(() => undefined); await host?.stop(); };
48
47
  return Object.assign(served, bare, { managedDatabaseUrl, stopDatabase });
@@ -1,7 +1,6 @@
1
- export type PgliteHost = {
1
+ import type { TwinPack } from '@volter/world-core/runtime';
2
+ export type TwinDatabase = {
2
3
  url: string;
3
- port: number;
4
- clock: (at: string) => void;
5
4
  stop: () => Promise<void>;
6
5
  };
7
- export declare function startPgliteHost(): Promise<PgliteHost>;
6
+ export declare function startTwinDatabase(pack: string, descriptor: TwinPack): Promise<TwinDatabase>;
@@ -1,108 +1,76 @@
1
- // The walker's Postgres is a declared, task-owned World. The runtime supervises its lifetime and backing;
2
- // the walker owns when to use and release it, never the backend's process or data directory.
1
+ // A managed SQL walk selects the pack in a task-owned World. The same private twin
2
+ // engine lifecycle serves both the World and the walker; no infrastructure is assembled here.
3
3
  import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { createServer } from 'node:net';
5
- import { tmpdir } from 'node:os';
6
4
  import { createRequire } from 'node:module';
7
- import { dirname, extname, join } from 'node:path';
8
- import { worldNow } from '@volter/world-core/runtime';
5
+ import { dirname, join } from 'node:path';
9
6
  let loaded;
10
7
  const worldRuntime = () => loaded ??= (async () => {
11
- let entry;
12
8
  try {
13
- entry = createRequire(import.meta.url).resolve('@volter/world-runtime');
9
+ createRequire(import.meta.url).resolve('@volter/world-runtime');
14
10
  }
15
11
  catch {
16
- throw new Error('A managed database walk needs @volter/world-runtime installed beside @volter/twin-standard: npm i @volter/world-runtime');
12
+ throw new Error('A managed SQL walk needs @volter/world-runtime installed beside @volter/twin-standard');
17
13
  }
18
- return { runtime: await import('@volter/world-runtime'), infrastructureCli: join(dirname(entry), `infra-cli${extname(entry)}`) };
14
+ return import('@volter/world-runtime');
19
15
  })();
20
- async function freePort() {
21
- return new Promise((done, fail) => {
22
- const server = createServer();
23
- server.once('error', fail);
24
- server.listen(0, '127.0.0.1', () => {
25
- const port = server.address().port;
26
- server.close(() => done(port));
27
- });
28
- });
29
- }
30
- // Sequential replays declare the same database endpoint. A concurrent walk declares its own port. Only a successful
31
- // runtime teardown releases this slot; unknown cleanup never licenses reusing its port or deleting its state.
32
- let declaredPort;
33
- let declaredHeld = false;
34
- export async function startPgliteHost() {
16
+ export async function startTwinDatabase(pack, descriptor) {
35
17
  if (!process.env.VOLTER_WORLD)
36
- throw new Error('A managed database walk requires its enclosing World');
37
- const { runtime: { runWorld, setWorldClock, worldConfigDocument }, infrastructureCli } = await worldRuntime();
38
- const root = mkdtempSync(join(tmpdir(), 'walk-database-world-'));
39
- const name = 'pack-walk-postgres';
40
- const port = declaredPort !== undefined && !declaredHeld ? declaredPort : await freePort();
41
- declaredPort ??= port;
42
- const holdsDeclared = port === declaredPort;
43
- if (holdsDeclared)
44
- declaredHeld = true;
45
- const fake = { user: 'world', database: 'world', password: 'walk-only-fake-password' };
46
- const url = `postgresql://${fake.user}:${fake.password}@127.0.0.1:${port}/${fake.database}`;
18
+ throw new Error('A managed SQL walk requires its enclosing World');
19
+ const definition = descriptor.managedDatabase, door = descriptor.credentialDoor;
20
+ if (!definition || !door || !definition.credentialField)
21
+ throw new Error('The selected twin must declare its SQL credential door field');
22
+ const { runWorld, worldConfigDocument } = await worldRuntime();
23
+ // Keep the disposable World on the pack's own volume and package-resolution ancestry.
24
+ const root = mkdtempSync(join(dirname(pack), '.walk-database-'));
25
+ const manifest = JSON.parse(readFileSync(join(pack, 'package.json'), 'utf8'));
47
26
  const config = join(root, 'world.json');
48
- const command = [process.execPath, infrastructureCli];
49
- writeFileSync(join(root, 'world.infrastructure.yml'), JSON.stringify({ services: { postgres: {
50
- image: 'postgres:18-alpine', ports: [`127.0.0.1:${port}:5432`],
51
- environment: { POSTGRES_USER: fake.user, POSTGRES_DB: fake.database, POSTGRES_PASSWORD: fake.password },
52
- } } }, null, 2) + '\n');
53
- writeFileSync(config, JSON.stringify(worldConfigDocument({ id: name,
54
- env: { DATABASE_URL: url, VOLTER_WORLD_INFRA_BACKING: 'pglite' }, network: { egress: [] },
55
- services: [{ id: 'infrastructure', type: 'external', controlPlane: true, external: {
56
- up: [...command, 'up'], status: [...command, 'status'], down: [...command, 'down'],
57
- discover: [{ as: 'DATABASE_URL', jsonPath: 'connections.DATABASE_URL', protocol: 'postgres' }],
58
- readyWhen: { command: command[0], args: [...command.slice(1), 'status'], timeoutMs: 120_000, intervalMs: 500 },
59
- } }],
27
+ writeFileSync(config, JSON.stringify(worldConfigDocument({ id: 'pack-walk-sql', stripEnv: ['*'], network: { egress: [] },
28
+ services: [{ id: descriptor.vendor, type: 'twin', package: manifest.name }],
60
29
  }), null, 2) + '\n');
61
- setWorldClock(name, root, worldNow());
62
30
  const readyFile = join(root, 'ready.json'), releaseFile = join(root, 'release');
63
- // This is the registered consumer held by runWorld, not an infrastructure supervisor. It exposes only its own
64
- // connection env, and exits when the caller's sequence is done. Caller loss belongs to the runtime's run guardian.
31
+ // Ask the selected twin's actual product door. The runtime alone allocates and owns its engine.
65
32
  const hold = `
66
- import { existsSync, renameSync, watch, writeFileSync } from 'node:fs';
33
+ import { existsSync, readFileSync, renameSync, watch, writeFileSync } from 'node:fs';
67
34
  import { dirname } from 'node:path';
68
- const [ready, release] = process.argv.slice(1);
69
- if (!process.env.VOLTER_WORLD || !process.env.DATABASE_URL) throw new Error('Missing held World connection');
35
+ const [ready, release, vendor, path, body, field] = process.argv.slice(1);
36
+ if (!process.env.VOLTER_WORLD_INSTANCE) throw new Error('Missing held World instance');
37
+ const instance = JSON.parse(readFileSync(process.env.VOLTER_WORLD_INSTANCE, 'utf8'));
38
+ const service = instance.services[vendor];
39
+ if (!service?.url) throw new Error('Selected twin has no product endpoint');
40
+ const response = await fetch(new URL(path, service.url), {method:'POST',headers:{'content-type':'application/json'},body});
41
+ if (!response.ok) throw new Error('Selected twin SQL credential door refused: ' + response.status);
42
+ const result = await response.json();
43
+ if (typeof result[field] !== 'string' || !result[field]) throw new Error('SQL credential door omitted its declared field');
70
44
  const done = () => { if (existsSync(release)) process.exit(0); };
71
45
  watch(dirname(release), done);
72
- writeFileSync(ready + '.tmp', JSON.stringify({ url: process.env.DATABASE_URL }));
46
+ writeFileSync(ready + '.tmp', JSON.stringify({ url: result[field] }), {mode:0o600});
73
47
  renameSync(ready + '.tmp', ready);
74
48
  done();`;
75
49
  let finished;
76
- const task = runWorld(config, [process.execPath, '--input-type=module', '-e', hold, readyFile, releaseFile], {
77
- root, cwd: root, owner: 'pack-walk-managed-database',
50
+ const task = runWorld(config, [process.execPath, '--input-type=module', '-e', hold, readyFile, releaseFile,
51
+ descriptor.vendor, door.path, JSON.stringify(door.body), definition.credentialField], {
52
+ root, cwd: root, owner: 'pack-walk-twin-private-sql',
78
53
  }).then(result => { finished = { result, failed: false }; return result; }, error => {
79
54
  finished = { error, failed: true };
80
55
  throw error;
81
56
  });
82
- // Startup and normal stop await this promise; keep an early rejection handled while waiting for readiness.
83
57
  void task.catch(() => undefined);
84
58
  while (!existsSync(readyFile)) {
85
59
  if (finished) {
86
- if (!finished.failed && holdsDeclared)
87
- declaredHeld = false;
88
60
  if (finished.failed)
89
61
  throw finished.error;
90
- throw new Error(`Managed database World ended before its consumer was ready (exit ${finished.result.exitCode}); evidence: ${root}`);
62
+ throw new Error(`Twin SQL World ended before its consumer was ready (exit ${finished.result.exitCode}); evidence: ${root}`);
91
63
  }
92
64
  await new Promise(resolve => setTimeout(resolve, 100));
93
65
  }
94
66
  const ready = JSON.parse(readFileSync(readyFile, 'utf8'));
95
67
  let stopping;
96
- return {
97
- url: ready.url, port: Number(new URL(ready.url).port),
98
- clock: at => setWorldClock(name, root, at),
68
+ return { url: ready.url,
99
69
  stop: () => stopping ??= (async () => {
100
70
  writeFileSync(releaseFile, 'released\n');
101
71
  const result = await task;
102
72
  if (result.exitCode !== 0)
103
- throw new Error(`Managed database World consumer exited ${result.exitCode}; evidence: ${root}`);
104
- if (holdsDeclared)
105
- declaredHeld = false;
73
+ throw new Error(`Twin SQL World consumer exited ${result.exitCode}; evidence: ${root}`);
106
74
  })(),
107
75
  };
108
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-standard",
3
- "version": "1.0.154",
3
+ "version": "1.0.155",
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",
@@ -76,7 +76,7 @@
76
76
  "yaml": "2.9.0"
77
77
  },
78
78
  "peerDependencies": {
79
- "@volter/world-core": "3.0.161",
79
+ "@volter/world-core": "3.0.163",
80
80
  "@volter/world-runtime": "*",
81
81
  "playwright": "^1.63.0"
82
82
  },
@@ -360,7 +360,7 @@ export function cliFile(vendor: string, live = false): string {
360
360
  const Name = pascal(vendor);
361
361
  const ingest = live ? `// the RTMP ingest's side port, which the World allocates (the manifest's sidePorts)\nconst ingestPort = Number(optionValue(rest, '--ingest-port', '0')) || undefined;\n` : '';
362
362
  const served = live ? ` process.stdout.write(s.ingestPort ? \`${vendor} twin ingest at rtmp://127.0.0.1:\${s.ingestPort}\\n\` : '${vendor} twin: no RTMP ingest\\n');\n` : '';
363
- return `#!/usr/bin/env node\n// world-${vendor}: serve the ${vendor} twin.\nimport { hasFlag, optionValue } from '@volter/world-core/args';\nimport { keepProcessAlive } from '@volter/world-core/lifecycle';\nimport { create${Name}TwinServer } from './server.ts';\n\nconst [cmd, ...rest] = process.argv.slice(2);\nconst port = Number(optionValue(rest, '--port', '0')) || undefined;\nconst root = optionValue(rest, '--root') || undefined;\nconst readOnly = hasFlag(rest, '--read-only');\n// the World's managed Postgres, which the runtime binds a pack declaring managedDatabase to\nconst database = optionValue(rest, '--database') || undefined;\n// the World's scenario file, for a model vendor's turns\nconst scenarioPath = optionValue(rest, '--scenario') || undefined;\n${ingest}\nif (cmd === 'serve' || cmd === undefined) {\n const s = await create${Name}TwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}), ...(database ? { database } : {}), ...(scenarioPath ? { scenarioPath } : {})${live ? ', ...(ingestPort ? { ingestPort } : {})' : ''} });\n process.stdout.write(\`${vendor} twin\${readOnly ? ' [read-only]' : ''} at http://127.0.0.1:\${s.port}\\n\`);\n${served} await keepProcessAlive();\n} else {\n process.stdout.write('Usage: world-${vendor} serve [--port N] [--root DIR] [--read-only] [--database URL] [--scenario FILE]${live ? ' [--ingest-port N]' : ''}\\n');\n}\n`;
363
+ return `#!/usr/bin/env node\n// world-${vendor}: serve the ${vendor} twin.\nimport { hasFlag, optionValue } from '@volter/world-core/args';\nimport { keepProcessAlive } from '@volter/world-core/lifecycle';\nimport { create${Name}TwinServer } from './server.ts';\n\nconst [cmd, ...rest] = process.argv.slice(2);\nconst port = Number(optionValue(rest, '--port', '0')) || undefined;\nconst root = optionValue(rest, '--root') || undefined;\nconst readOnly = hasFlag(rest, '--read-only');\n// the selected twin's private Postgres, which the runtime binds a pack declaring managedDatabase to\nconst database = optionValue(rest, '--database') || undefined;\n// the World's scenario file, for a model vendor's turns\nconst scenarioPath = optionValue(rest, '--scenario') || undefined;\n${ingest}\nif (cmd === 'serve' || cmd === undefined) {\n const s = await create${Name}TwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}), ...(database ? { database } : {}), ...(scenarioPath ? { scenarioPath } : {})${live ? ', ...(ingestPort ? { ingestPort } : {})' : ''} });\n process.stdout.write(\`${vendor} twin\${readOnly ? ' [read-only]' : ''} at http://127.0.0.1:\${s.port}\\n\`);\n${served} await keepProcessAlive();\n} else {\n process.stdout.write('Usage: world-${vendor} serve [--port N] [--root DIR] [--read-only] [--database URL] [--scenario FILE]${live ? ' [--ingest-port N]' : ''}\\n');\n}\n`;
364
364
  }
365
365
 
366
366
  /** A vendor id as a name in code: `npm-registry` is `NpmRegistry`. */
@@ -414,7 +414,6 @@ Contributor mechanics belong to the [pack procedure](https://world-docs.volter.a
414
414
 
415
415
  export function initFiles(vendor: string): Record<string, string> {
416
416
  const Name = pascal(vendor);
417
- const semantics = `${Name[0]!.toLowerCase()}${Name.slice(1)}Semantics`;
418
417
  return {
419
418
  'package.json': `${JSON.stringify({
420
419
  name: `@volter/twin-${vendor}`, version: '1.0.0', description: `The ${vendor} twin (Protocol 3).`, author: 'Volter (https://github.com/volter-ai)', license: 'Apache-2.0',
@@ -448,8 +447,6 @@ export function initFiles(vendor: string): Record<string, string> {
448
447
  * surface of its own under `<lane>/src`, keeping the vendor's one store and sharing its `src/semantics/shared.ts`, and the
449
448
  * pack's server routing each lane's host to it. */
450
449
  export function laneInitFiles(vendor: string, lanes: string[]): Record<string, string> {
451
- const Name = pascal(vendor);
452
- const camel = (s: string): string => { const p = pascal(s); return `${p[0]!.toLowerCase()}${p.slice(1)}`; };
453
450
  const single = initFiles(vendor);
454
451
  const files: Record<string, string> = {
455
452
  'package.json': single['package.json']!.replace('"spec",', lanes.map((l) => `"${l}",`).join('\n ')),
package/src/pack-facts.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // Published descriptor compiler. Pack locations are explicit; imports happen only when called by a publisher.
2
2
  import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- import { PACK_ARCHETYPES, PROTOCOL_VERSION, protocolStanding } from '@volter/world-core/runtime';
4
+ import { PACK_ARCHETYPES, PROTOCOL_VERSION, protocolStanding, type TwinPack } from '@volter/world-core/runtime';
5
5
 
6
6
  export type PackFacts = {
7
7
  /** Explicit vendor-server backing; this entry declares no pack protocol or factory. */
@@ -32,14 +32,14 @@ export type PackFacts = {
32
32
  endpointEnv?: { name: string; templates?: Record<string, string> };
33
33
  endpointEnvNone?: string;
34
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> };
35
+ credentialDoor?: TwinPack['credentialDoor'];
36
36
  credentialIssuer?: string;
37
37
  /** The Prisma driver adapter this database twin is reached through (see TwinPack.prismaAdapter). */
38
38
  prismaAdapter?: { adapter: string; export: string; urlEnv: string };
39
39
  /** The managed-infrastructure kind this pack is the World's backing for (architecture.md, "The descriptor"). */
40
40
  managedService?: { kind: 'redis' | 'mongodb' | 'clickhouse'; scheme: string; note: string; configurationArg?: '--configuration'; originalInputs?: { version: 1; manifestArg: '--original-inputs'; rootArg: '--original-root'; digestArg: '--original-inputs-sha256'; paths: string[] } };
41
- /** The World's managed database the runtime binds the twin to (see TwinPack.managedDatabase). */
42
- managedDatabase?: { kind: 'postgres'; arg: string; note: string };
41
+ /** The selected twin's private SQL identity and credential field (see TwinPack.managedDatabase). */
42
+ managedDatabase?: { kind: 'postgres'; arg: string; user: string; database: string; clock: 'wall'; credentialField: string; note: string };
43
43
  /** Listeners the twin opens beside its main port (see TwinPack.sidePorts). */
44
44
  sidePorts?: Array<{ name: string; option: string; flag: string; path?: string; scheme?: string; note: string }>;
45
45
  /** DERIVED, never declared: the pack's `create<Name>TwinServer` factory export — the
@@ -184,9 +184,12 @@ export async function compilePackFacts(dirs: Array<{ vendor: string; dir: string
184
184
  facts.prismaAdapter = pack.prismaAdapter;
185
185
  }
186
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`);
187
+ const md = pack.managedDatabase as { kind?: unknown; arg?: unknown; user?: unknown; database?: unknown; clock?: unknown; credentialField?: unknown; note?: unknown };
188
+ if (md.kind !== 'postgres') throw new Error(`pack-facts: ${vendor} declares managedDatabase.kind "${String(md.kind)}"; private SQL supports postgres`);
189
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
+ for (const key of ['user', 'database'] as const) if (typeof md[key] !== 'string' || !md[key] || /[\r\n\0$]/u.test(md[key]) || Buffer.byteLength(md[key], 'utf8') > 63) throw new Error(`pack-facts: ${vendor} private database ${key} must be a literal PostgreSQL identifier of at most 63 bytes`);
191
+ if (md.clock !== 'wall') throw new Error(`pack-facts: ${vendor} private postgres must declare its wall clock`);
192
+ if (typeof md.credentialField !== 'string' || !Object.values(pack.credentialDoor?.fill ?? {}).includes(md.credentialField) || /^(?:file|directory|side):/.test(md.credentialField)) throw new Error(`pack-facts: ${vendor} private database must name its credential door's scalar URL field`);
190
193
  if (typeof md.note !== 'string' || md.note.trim() === '') throw new Error(`pack-facts: ${vendor} declares managedDatabase with no note`);
191
194
  facts.managedDatabase = pack.managedDatabase;
192
195
  }
@@ -436,8 +436,8 @@ export const isGit = (step: Step): step is GitStep => 'git' in step;
436
436
  export type WaitStep = { name: string; wait: string };
437
437
  export const isWait = (step: Step): step is WaitStep => 'wait' in step;
438
438
 
439
- /** What an application sends the World's managed database over the database's own wire, not through the vendor's API (a
440
- * pack whose data plane is the World's Postgres, the descriptor's `managedDatabase`): its migrations as its deploy applies
439
+ /** What an application sends the selected twin's database over its SQL wire, not through the vendor's HTTP API (a
440
+ * pack whose descriptor declares a private SQL product (`managedDatabase`)): its migrations as its deploy applies
441
441
  * them and the queries it sends, each on the application's one connection to the walked World's database (world-core
442
442
  * `databaseSession`), so a transaction it begins stays open into its next steps, as on its own connection. `sql` alone is
443
443
  * one simple Query (a migration file, a `BEGIN`); with `values` it is one extended-protocol statement, the values bind
@@ -1,7 +1,7 @@
1
1
  // The standard serves a pack with its declared backing, as the runtime binds it in a World.
2
2
  import { join } from 'node:path';
3
- import { worldNow } from '@volter/world-core/runtime';
4
- import { startPgliteHost, type PgliteHost } from './walk-database.ts';
3
+ import type { TwinPack } from '@volter/world-core/runtime';
4
+ import { startTwinDatabase, type TwinDatabase } from './walk-database.ts';
5
5
 
6
6
  export type ServedPack = {
7
7
  port: number;
@@ -10,13 +10,13 @@ export type ServedPack = {
10
10
  managedDatabaseUrl?: () => Promise<string>;
11
11
  };
12
12
 
13
- export async function servePack(pack: string, options: Record<string, unknown>): Promise<ServedPack> {
13
+ export async function servePack(pack: string, options: { root?: string; scenarioPath?: string }): Promise<ServedPack> {
14
14
  const mod = await import(join(pack, 'src', 'server.ts')) as Record<string, unknown>;
15
15
  const create = Object.entries(mod).find(([name, value]) => /^create\w+TwinServer$/.test(name) && typeof value === 'function')?.[1] as ((options: Record<string, unknown>) => Promise<ServedPack>) | undefined;
16
16
  if (!create) throw new Error('src/server.ts exports no create<Vendor>TwinServer');
17
- const descriptor = (await import(join(pack, 'src', 'index.ts'))).pack as { managedDatabase?: { kind: string } } | undefined;
18
- let database: PgliteHost | undefined;
19
- if (descriptor?.managedDatabase?.kind === 'postgres' && options.database === undefined) database = await startPgliteHost();
17
+ const descriptor = (await import(join(pack, 'src', 'index.ts'))).pack as TwinPack | undefined;
18
+ let database: TwinDatabase | undefined;
19
+ if (descriptor?.managedDatabase?.kind === 'postgres') database = await startTwinDatabase(pack, descriptor);
20
20
  let server: ServedPack;
21
21
  try { server = await create({ port: 0, ...options, ...(database ? { database: database.url } : {}) }); }
22
22
  catch (error) { await database?.stop(); throw error; }
@@ -25,7 +25,7 @@ export async function servePack(pack: string, options: Record<string, unknown>):
25
25
  let stopping: Promise<void> | undefined;
26
26
  return {
27
27
  ...server,
28
- managedDatabaseUrl: async () => { backing.clock(worldNow()); return backing.url; },
28
+ managedDatabaseUrl: async () => backing.url,
29
29
  stop: () => stopping ??= (async () => { await server.stop(); await backing.stop(); })(),
30
30
  };
31
31
  }
@@ -2,8 +2,8 @@
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 { type PgliteHost, startPgliteHost } from './walk-database.ts';
6
- import { type TwinStream, worldNow } from '@volter/world-core/runtime';
5
+ import { type TwinDatabase, startTwinDatabase } from './walk-database.ts';
6
+ import { type TwinStream, type TwinPack } from '@volter/world-core/runtime';
7
7
  import type { PackFetchOptions } from '@volter/world-core';
8
8
  import { walkPackDirectory } from './pack-directory.ts';
9
9
 
@@ -22,19 +22,19 @@ export async function vendorFetch(twinDir: string, vendor: string, dependenciesF
22
22
  const name = Object.keys(mod).find((k) => /^create\w+?Fetch$/.test(k) && !/TwinFetch$/.test(k) && typeof mod[k] === 'function');
23
23
  if (!name) return undefined;
24
24
  const factory = mod[name] as (options: { root: string; database?: string; scenarioPath?: string; application?: ApplicationStandIn }) => ((request: Request) => Response | Promise<Response>) & Record<string, unknown>;
25
- if (!(mod.pack as { managedDatabase?: unknown } | undefined)?.managedDatabase) return (root: string, options = {}) => factory({ root, ...options });
26
- // A pack whose data plane is the World's managed Postgres is walked over one, as a World binds it: each World (each
27
- // root) its own fresh database, started on its first request and stopped when the walk's process ends, its clock the
28
- // walked World's (set before each request: the walk moves the World clock between steps).
25
+ const descriptor = mod.pack as TwinPack | undefined;
26
+ if (!descriptor?.managedDatabase) return (root: string, options = {}) => factory({ root, ...options });
27
+ // Each walked twin owns a fresh SQL product through the ordinary selected-twin lifecycle.
28
+ // Native SQL uses the descriptor's explicit wall clock; kernel World time is not claimed for it.
29
29
  return (root: string, options = {}) => {
30
30
  const bare = factory({ root, ...options });
31
31
  let bound: Promise<(request: Request) => Response | Promise<Response>> | undefined;
32
- let host: PgliteHost | undefined;
33
- const bind = () => (bound ??= startPgliteHost().then((started) => { host = started; return factory({ root, database: started.url, ...options }); }));
34
- const served = (request: Request) => bind().then((fetch) => { host!.clock(worldNow()); return fetch(request); });
35
- // the World's database itself, for what an application sends it over its own wire (a SQL step: journey-kit.ts)
36
- const managedDatabaseUrl = () => bind().then(() => { host!.clock(worldNow()); return host!.url; });
37
- // the walk's end stops its World's database, so the next World walked declares it at the same port
32
+ let host: TwinDatabase | undefined;
33
+ const bind = () => (bound ??= startTwinDatabase(directory, descriptor).then((started) => { host = started; return factory({ root, database: started.url, ...options }); }));
34
+ const served = (request: Request) => bind().then((fetch) => { return fetch(request); });
35
+ // The twin's SQL product, for what an application sends over its own wire (journey-kit.ts).
36
+ const managedDatabaseUrl = () => bind().then(() => { return host!.url; });
37
+ // The walk's end retires the selected twin's private engine through its World.
38
38
  const stopDatabase = async () => { if (bound === undefined) return; await bound.catch(() => undefined); await host?.stop(); };
39
39
  return Object.assign(served, bare, { managedDatabaseUrl, stopDatabase });
40
40
  };
@@ -1,106 +1,73 @@
1
- // The walker's Postgres is a declared, task-owned World. The runtime supervises its lifetime and backing;
2
- // the walker owns when to use and release it, never the backend's process or data directory.
1
+ // A managed SQL walk selects the pack in a task-owned World. The same private twin
2
+ // engine lifecycle serves both the World and the walker; no infrastructure is assembled here.
3
3
  import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { createServer } from 'node:net';
5
- import { tmpdir } from 'node:os';
6
4
  import { createRequire } from 'node:module';
7
- import { dirname, extname, join } from 'node:path';
8
- import { worldNow } from '@volter/world-core/runtime';
5
+ import { dirname, join } from 'node:path';
6
+ import type { TwinPack } from '@volter/world-core/runtime';
9
7
 
10
- // The runtime is this package's optional peer: only a walk over a managed database needs it, so it is loaded here, on
11
- // that walk's first database, and every command that starts none runs with the standard alone installed.
12
8
  type Runtime = typeof import('@volter/world-runtime');
13
- let loaded: Promise<{ runtime: Runtime; infrastructureCli: string }> | undefined;
9
+ let loaded: Promise<Runtime> | undefined;
14
10
  const worldRuntime = () => loaded ??= (async () => {
15
- let entry: string;
16
- try { entry = createRequire(import.meta.url).resolve('@volter/world-runtime'); }
17
- catch { throw new Error('A managed database walk needs @volter/world-runtime installed beside @volter/twin-standard: npm i @volter/world-runtime'); }
18
- return { runtime: await import('@volter/world-runtime'), infrastructureCli: join(dirname(entry), `infra-cli${extname(entry)}`) };
11
+ try { createRequire(import.meta.url).resolve('@volter/world-runtime'); }
12
+ catch { throw new Error('A managed SQL walk needs @volter/world-runtime installed beside @volter/twin-standard'); }
13
+ return import('@volter/world-runtime');
19
14
  })();
20
15
 
21
- async function freePort(): Promise<number> {
22
- return new Promise((done, fail) => {
23
- const server = createServer();
24
- server.once('error', fail);
25
- server.listen(0, '127.0.0.1', () => {
26
- const port = (server.address() as { port: number }).port;
27
- server.close(() => done(port));
28
- });
29
- });
30
- }
31
-
32
- export type PgliteHost = { url: string; port: number; clock: (at: string) => void; stop: () => Promise<void> };
33
-
34
- // Sequential replays declare the same database endpoint. A concurrent walk declares its own port. Only a successful
35
- // runtime teardown releases this slot; unknown cleanup never licenses reusing its port or deleting its state.
36
- let declaredPort: number | undefined;
37
- let declaredHeld = false;
16
+ export type TwinDatabase = { url: string; stop: () => Promise<void> };
38
17
 
39
- export async function startPgliteHost(): Promise<PgliteHost> {
40
- if (!process.env.VOLTER_WORLD) throw new Error('A managed database walk requires its enclosing World');
41
- const { runtime: { runWorld, setWorldClock, worldConfigDocument }, infrastructureCli } = await worldRuntime();
42
- const root = mkdtempSync(join(tmpdir(), 'walk-database-world-'));
43
- const name = 'pack-walk-postgres';
44
- const port = declaredPort !== undefined && !declaredHeld ? declaredPort : await freePort();
45
- declaredPort ??= port;
46
- const holdsDeclared = port === declaredPort;
47
- if (holdsDeclared) declaredHeld = true;
48
- const fake = { user: 'world', database: 'world', password: 'walk-only-fake-password' };
49
- const url = `postgresql://${fake.user}:${fake.password}@127.0.0.1:${port}/${fake.database}`;
18
+ export async function startTwinDatabase(pack: string, descriptor: TwinPack): Promise<TwinDatabase> {
19
+ if (!process.env.VOLTER_WORLD) throw new Error('A managed SQL walk requires its enclosing World');
20
+ const definition = descriptor.managedDatabase, door = descriptor.credentialDoor;
21
+ if (!definition || !door || !definition.credentialField) throw new Error('The selected twin must declare its SQL credential door field');
22
+ const { runWorld, worldConfigDocument } = await worldRuntime();
23
+ // Keep the disposable World on the pack's own volume and package-resolution ancestry.
24
+ const root = mkdtempSync(join(dirname(pack), '.walk-database-'));
25
+ const manifest = JSON.parse(readFileSync(join(pack, 'package.json'), 'utf8')) as { name: string };
50
26
  const config = join(root, 'world.json');
51
- const command = [process.execPath, infrastructureCli];
52
- writeFileSync(join(root, 'world.infrastructure.yml'), JSON.stringify({ services: { postgres: {
53
- image: 'postgres:18-alpine', ports: [`127.0.0.1:${port}:5432`],
54
- environment: { POSTGRES_USER: fake.user, POSTGRES_DB: fake.database, POSTGRES_PASSWORD: fake.password },
55
- } } }, null, 2) + '\n');
56
- writeFileSync(config, JSON.stringify(worldConfigDocument({ id: name,
57
- env: { DATABASE_URL: url, VOLTER_WORLD_INFRA_BACKING: 'pglite' }, network: { egress: [] },
58
- services: [{ id: 'infrastructure', type: 'external', controlPlane: true, external: {
59
- up: [...command, 'up'], status: [...command, 'status'], down: [...command, 'down'],
60
- discover: [{ as: 'DATABASE_URL', jsonPath: 'connections.DATABASE_URL', protocol: 'postgres' }],
61
- readyWhen: { command: command[0], args: [...command.slice(1), 'status'], timeoutMs: 120_000, intervalMs: 500 },
62
- } }],
27
+ writeFileSync(config, JSON.stringify(worldConfigDocument({ id: 'pack-walk-sql', stripEnv: ['*'], network: { egress: [] },
28
+ services: [{ id: descriptor.vendor, type: 'twin', package: manifest.name }],
63
29
  }), null, 2) + '\n');
64
- setWorldClock(name, root, worldNow());
65
30
  const readyFile = join(root, 'ready.json'), releaseFile = join(root, 'release');
66
- // This is the registered consumer held by runWorld, not an infrastructure supervisor. It exposes only its own
67
- // connection env, and exits when the caller's sequence is done. Caller loss belongs to the runtime's run guardian.
31
+ // Ask the selected twin's actual product door. The runtime alone allocates and owns its engine.
68
32
  const hold = `
69
- import { existsSync, renameSync, watch, writeFileSync } from 'node:fs';
33
+ import { existsSync, readFileSync, renameSync, watch, writeFileSync } from 'node:fs';
70
34
  import { dirname } from 'node:path';
71
- const [ready, release] = process.argv.slice(1);
72
- if (!process.env.VOLTER_WORLD || !process.env.DATABASE_URL) throw new Error('Missing held World connection');
35
+ const [ready, release, vendor, path, body, field] = process.argv.slice(1);
36
+ if (!process.env.VOLTER_WORLD_INSTANCE) throw new Error('Missing held World instance');
37
+ const instance = JSON.parse(readFileSync(process.env.VOLTER_WORLD_INSTANCE, 'utf8'));
38
+ const service = instance.services[vendor];
39
+ if (!service?.url) throw new Error('Selected twin has no product endpoint');
40
+ const response = await fetch(new URL(path, service.url), {method:'POST',headers:{'content-type':'application/json'},body});
41
+ if (!response.ok) throw new Error('Selected twin SQL credential door refused: ' + response.status);
42
+ const result = await response.json();
43
+ if (typeof result[field] !== 'string' || !result[field]) throw new Error('SQL credential door omitted its declared field');
73
44
  const done = () => { if (existsSync(release)) process.exit(0); };
74
45
  watch(dirname(release), done);
75
- writeFileSync(ready + '.tmp', JSON.stringify({ url: process.env.DATABASE_URL }));
46
+ writeFileSync(ready + '.tmp', JSON.stringify({ url: result[field] }), {mode:0o600});
76
47
  renameSync(ready + '.tmp', ready);
77
48
  done();`;
78
49
  let finished: { result?: Awaited<ReturnType<Runtime['runWorld']>>; error?: unknown; failed: boolean } | undefined;
79
- const task = runWorld(config, [process.execPath, '--input-type=module', '-e', hold, readyFile, releaseFile], {
80
- root, cwd: root, owner: 'pack-walk-managed-database',
50
+ const task = runWorld(config, [process.execPath, '--input-type=module', '-e', hold, readyFile, releaseFile,
51
+ descriptor.vendor, door.path, JSON.stringify(door.body), definition.credentialField], {
52
+ root, cwd: root, owner: 'pack-walk-twin-private-sql',
81
53
  }).then(result => { finished = { result, failed: false }; return result; }, error => {
82
54
  finished = { error, failed: true }; throw error;
83
55
  });
84
- // Startup and normal stop await this promise; keep an early rejection handled while waiting for readiness.
85
56
  void task.catch(() => undefined);
86
57
  while (!existsSync(readyFile)) {
87
58
  if (finished) {
88
- if (!finished.failed && holdsDeclared) declaredHeld = false;
89
59
  if (finished.failed) throw finished.error;
90
- throw new Error(`Managed database World ended before its consumer was ready (exit ${finished.result!.exitCode}); evidence: ${root}`);
60
+ throw new Error(`Twin SQL World ended before its consumer was ready (exit ${finished.result!.exitCode}); evidence: ${root}`);
91
61
  }
92
62
  await new Promise(resolve => setTimeout(resolve, 100));
93
63
  }
94
64
  const ready = JSON.parse(readFileSync(readyFile, 'utf8')) as { url: string };
95
65
  let stopping: Promise<void> | undefined;
96
- return {
97
- url: ready.url, port: Number(new URL(ready.url).port),
98
- clock: at => setWorldClock(name, root, at),
66
+ return { url: ready.url,
99
67
  stop: () => stopping ??= (async () => {
100
68
  writeFileSync(releaseFile, 'released\n');
101
69
  const result = await task;
102
- if (result.exitCode !== 0) throw new Error(`Managed database World consumer exited ${result.exitCode}; evidence: ${root}`);
103
- if (holdsDeclared) declaredHeld = false;
70
+ if (result.exitCode !== 0) throw new Error(`Twin SQL World consumer exited ${result.exitCode}; evidence: ${root}`);
104
71
  })(),
105
72
  };
106
73
  }