@volter/world-runtime 3.0.162 → 3.0.164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/src/ancestry.js +2 -2
  2. package/dist/src/app-url.d.ts +5 -1
  3. package/dist/src/app-url.js +14 -1
  4. package/dist/src/branch.js +28 -8
  5. package/dist/src/browser-files.d.ts +5 -0
  6. package/dist/src/browser-files.js +14 -0
  7. package/dist/src/browsers.d.ts +2 -3
  8. package/dist/src/browsers.js +4 -4
  9. package/dist/src/catalog.d.ts +2 -1
  10. package/dist/src/catalog.js +3 -2
  11. package/dist/src/changeset.js +1 -1
  12. package/dist/src/cli-commands.js +71 -18
  13. package/dist/src/cli-load-clock.d.ts +1 -0
  14. package/dist/src/cli-load-clock.js +2 -0
  15. package/dist/src/cli.js +5 -1
  16. package/dist/src/clickhouse-backing.d.ts +2 -0
  17. package/dist/src/clickhouse-backing.js +145 -0
  18. package/dist/src/command-lifetime.d.mts +1 -1
  19. package/dist/src/command-lifetime.mjs +5 -2
  20. package/dist/src/configs.js +1 -1
  21. package/dist/src/consumers.js +1 -1
  22. package/dist/src/covers.d.ts +4 -2
  23. package/dist/src/covers.js +8 -4
  24. package/dist/src/go-mysql-binding.d.ts +19 -0
  25. package/dist/src/go-mysql-binding.js +51 -0
  26. package/dist/src/guest-processes.d.mts +1 -1
  27. package/dist/src/guest-processes.mjs +22 -14
  28. package/dist/src/host-cli.js +8 -1
  29. package/dist/src/host-ready.d.ts +16 -2
  30. package/dist/src/host-ready.js +66 -39
  31. package/dist/src/host-worker.js +7 -1
  32. package/dist/src/host.d.ts +26 -0
  33. package/dist/src/host.js +93 -3
  34. package/dist/src/infra-definition.d.ts +35 -0
  35. package/dist/src/infra-definition.js +146 -0
  36. package/dist/src/infra.d.ts +3 -0
  37. package/dist/src/infra.js +375 -105
  38. package/dist/src/init.d.ts +46 -0
  39. package/dist/src/init.js +445 -18
  40. package/dist/src/lifecycle-record.d.ts +5 -1
  41. package/dist/src/lifecycle-record.js +27 -4
  42. package/dist/src/local-branches.js +1 -1
  43. package/dist/src/mariadb-bootstrap.d.ts +32 -0
  44. package/dist/src/mariadb-bootstrap.js +90 -0
  45. package/dist/src/mariadb-supplier.d.ts +23 -0
  46. package/dist/src/mariadb-supplier.js +215 -0
  47. package/dist/src/mongodb-backing.d.ts +4 -0
  48. package/dist/src/mongodb-backing.js +71 -1
  49. package/dist/src/native-mariadb-backing.d.ts +13 -0
  50. package/dist/src/native-mariadb-backing.js +512 -0
  51. package/dist/src/native-postgres-backing.d.ts +4 -10
  52. package/dist/src/native-postgres-backing.js +428 -43
  53. package/dist/src/native-server-inputs.d.ts +16 -0
  54. package/dist/src/native-server-inputs.js +204 -0
  55. package/dist/src/origin.js +3 -3
  56. package/dist/src/original-inputs.d.ts +14 -0
  57. package/dist/src/original-inputs.js +84 -0
  58. package/dist/src/pack-facts.d.ts +9 -1
  59. package/dist/src/pglite-backing.d.ts +41 -17
  60. package/dist/src/pglite-backing.js +213 -79
  61. package/dist/src/pglite-host.mjs +482 -10
  62. package/dist/src/process-groups.d.mts +6 -1
  63. package/dist/src/process-groups.d.ts +2 -1
  64. package/dist/src/process-groups.js +1 -1
  65. package/dist/src/process-groups.mjs +82 -15
  66. package/dist/src/process-signal.d.mts +2 -2
  67. package/dist/src/process-signal.mjs +29 -9
  68. package/dist/src/project-inspect.d.ts +12 -0
  69. package/dist/src/project-inspect.js +41 -4
  70. package/dist/src/proxy-env.d.ts +34 -0
  71. package/dist/src/proxy-env.js +114 -0
  72. package/dist/src/redirect-proxy.d.ts +1 -31
  73. package/dist/src/redirect-proxy.js +3 -112
  74. package/dist/src/redis-backing.d.ts +6 -4
  75. package/dist/src/redis-backing.js +20 -168
  76. package/dist/src/runtime.d.ts +1 -0
  77. package/dist/src/runtime.js +503 -218
  78. package/dist/src/schema.d.ts +1 -0
  79. package/dist/src/schema.js +5 -3
  80. package/dist/src/served-world.js +15 -10
  81. package/dist/src/service-exit.d.ts +3 -2
  82. package/dist/src/service-exit.js +7 -5
  83. package/dist/src/twin-backing.d.ts +47 -0
  84. package/dist/src/twin-backing.js +343 -0
  85. package/dist/src/up-cli.js +56 -3
  86. package/package.json +4 -4
  87. package/src/ancestry.ts +2 -2
  88. package/src/app-url.ts +9 -2
  89. package/src/branch.ts +26 -8
  90. package/src/browser-files.ts +15 -0
  91. package/src/browsers.ts +4 -4
  92. package/src/catalog.ts +4 -3
  93. package/src/changeset.ts +1 -1
  94. package/src/cli-commands.ts +85 -18
  95. package/src/cli-load-clock.ts +2 -0
  96. package/src/cli.ts +5 -1
  97. package/src/clickhouse-backing.ts +70 -0
  98. package/src/command-lifetime.d.mts +1 -1
  99. package/src/command-lifetime.mjs +5 -2
  100. package/src/configs.ts +1 -1
  101. package/src/consumers.ts +1 -1
  102. package/src/covers.ts +9 -4
  103. package/src/go-mysql-binding.ts +49 -0
  104. package/src/guest-processes.d.mts +1 -1
  105. package/src/guest-processes.mjs +22 -14
  106. package/src/host-cli.ts +7 -1
  107. package/src/host-ready.ts +62 -31
  108. package/src/host-worker.ts +6 -2
  109. package/src/host.ts +88 -6
  110. package/src/infra-definition.ts +135 -0
  111. package/src/infra.ts +250 -45
  112. package/src/init.ts +416 -17
  113. package/src/lifecycle-record.ts +18 -4
  114. package/src/local-branches.ts +1 -1
  115. package/src/mariadb-bootstrap.ts +107 -0
  116. package/src/mariadb-supplier.ts +199 -0
  117. package/src/mongodb-backing.ts +59 -1
  118. package/src/native-mariadb-backing.ts +418 -0
  119. package/src/native-postgres-backing.ts +353 -41
  120. package/src/native-server-inputs.ts +169 -0
  121. package/src/origin.ts +3 -3
  122. package/src/original-inputs.ts +72 -0
  123. package/src/pack-facts.ts +1 -1
  124. package/src/pglite-backing.ts +192 -80
  125. package/src/pglite-host.mjs +482 -10
  126. package/src/process-groups.d.mts +6 -1
  127. package/src/process-groups.mjs +82 -15
  128. package/src/process-groups.ts +2 -1
  129. package/src/process-signal.d.mts +2 -2
  130. package/src/process-signal.mjs +29 -9
  131. package/src/project-inspect.ts +37 -4
  132. package/src/proxy-env.ts +128 -0
  133. package/src/redirect-proxy.ts +3 -125
  134. package/src/redis-backing.ts +19 -136
  135. package/src/runtime.ts +342 -107
  136. package/src/schema.ts +5 -4
  137. package/src/served-world.ts +14 -10
  138. package/src/service-exit.ts +5 -3
  139. package/src/twin-backing.ts +300 -0
  140. package/src/up-cli.ts +52 -3
@@ -19,7 +19,7 @@ export declare function assertLifecycleStartable(root: string, world: string): v
19
19
  /** A missing process is not successful teardown; records persist until explicit release. */
20
20
  export declare function beginWorldLifecycle(root: string, world: string, instanceCreatedAt: string, owner?: string): void;
21
21
  export declare function handoffWorldLifecycle(root: string, world: string, pids: number[]): void;
22
- export declare function retainWorldLifecycleForCleanup(root: string, world: string): void;
22
+ export declare function retainWorldLifecycleForCleanup(root: string, world: string, onError?: (error: unknown) => void): void;
23
23
  export declare function recordWorldLifecycleEvent(root: string, world: string, message: string): void;
24
24
  /** Called only after verified cleanup. Never touches legacy records. */
25
25
  export declare function releaseWorldLifecycle(root: string, world: string, receipt?: string): void;
@@ -46,4 +46,8 @@ export declare function inspectWorldLifecycles(): {
46
46
  warnings: string[];
47
47
  };
48
48
  /** Runtime phase is meaningful only for the same instance generation; it is not a health probe. */
49
+ /** An instrument: what `isWorldLifecycleRunning` read, for a refusal that says only "is not running": where the
50
+ * record is looked for (under VOLTER_HOME, else the home), whether it is there, and its phase and instance beside
51
+ * the instance asked for. It changes no answer. */
52
+ export declare function describeWorldLifecycle(root: string, world: string, instanceCreatedAt: string): string;
49
53
  export declare function isWorldLifecycleRunning(root: string, world: string, instanceCreatedAt: string): boolean;
@@ -1,10 +1,10 @@
1
1
  // Durable ownership of one World. This inventory does not allocate or reserve capacity.
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
- import { volterHome } from '@volter/world-core/runtime';
4
+ import { volterHome } from '@volter/world-core/runtime-state';
5
5
  import { hostname } from 'node:os';
6
6
  import { dirname, join, resolve } from 'node:path';
7
- import { stateDirName, withFileLock } from '@volter/world-core/runtime';
7
+ import { stateDirName, withFileLock } from '@volter/world-core/runtime-state';
8
8
  import { checkedOwner } from "./consumers.js";
9
9
  const directory = () => join(volterHome(), 'world-lifecycles');
10
10
  /** One directory is one root however it is spelled: macOS's /tmp is /private/tmp, and a caller's cwd is the real path
@@ -77,12 +77,19 @@ function update(root, world, change) {
77
77
  export function handoffWorldLifecycle(root, world, pids) {
78
78
  update(root, world, record => ({ ...record, phase: 'running', pids: [...new Set(pids.filter(pid => pid > 0))] }));
79
79
  }
80
- export function retainWorldLifecycleForCleanup(root, world) {
80
+ export function retainWorldLifecycleForCleanup(root, world, onError) {
81
81
  if (existsSync(legacyPath(root, world)))
82
82
  throw new Error(`World "${world}" has legacy ownership; use its pinned runtime for teardown`);
83
83
  if (!existsSync(recordPath(root, world)))
84
84
  return;
85
- update(root, world, record => ({ ...record, phase: 'cleanup-pending' }));
85
+ try {
86
+ update(root, world, record => ({ ...record, phase: 'cleanup-pending' }));
87
+ }
88
+ catch (error) {
89
+ if (!onError)
90
+ throw error;
91
+ onError(error);
92
+ }
86
93
  }
87
94
  export function recordWorldLifecycleEvent(root, world, message) {
88
95
  const path = lifecycleLogPath(root, world);
@@ -208,6 +215,22 @@ export function inspectWorldLifecycles() {
208
215
  return { worlds, legacyRecords, warnings };
209
216
  }
210
217
  /** Runtime phase is meaningful only for the same instance generation; it is not a health probe. */
218
+ /** An instrument: what `isWorldLifecycleRunning` read, for a refusal that says only "is not running": where the
219
+ * record is looked for (under VOLTER_HOME, else the home), whether it is there, and its phase and instance beside
220
+ * the instance asked for. It changes no answer. */
221
+ export function describeWorldLifecycle(root, world, instanceCreatedAt) {
222
+ const path = recordPath(root, world);
223
+ const home = `VOLTER_HOME ${process.env.VOLTER_HOME === undefined || process.env.VOLTER_HOME === '' ? 'unset' : process.env.VOLTER_HOME}`;
224
+ if (!existsSync(path))
225
+ return `its lifecycle record ${path} is not there (${home})`;
226
+ try {
227
+ const record = read(path);
228
+ return `its lifecycle record ${path} (${home}) says phase ${record.phase}, instance created ${record.instanceCreatedAt}${record.instanceCreatedAt === instanceCreatedAt ? '' : `, where ${instanceCreatedAt} is asked for`}${record.identity === identity(root, world) ? '' : ', under another identity'}`;
229
+ }
230
+ catch (error) {
231
+ return `its lifecycle record ${path} (${home}) could not be read: ${error instanceof Error ? error.message : String(error)}`;
232
+ }
233
+ }
211
234
  export function isWorldLifecycleRunning(root, world, instanceCreatedAt) {
212
235
  try {
213
236
  const record = read(recordPath(root, world));
@@ -185,7 +185,7 @@ export class LocalBranches {
185
185
  if (!meta?.parent)
186
186
  throw new Error(`cloning ${parent}: ${service}/${state.name} has no parent`);
187
187
  writeBranchMeta(state.name, { ...meta, parent: { ...meta.parent, at: join(base.dirs.data, service), viewDirectory: directory } }, control);
188
- });
188
+ }, { operation: 'branch-pointer adjustment' });
189
189
  }
190
190
  }
191
191
  inheritWorldBrowsers(base.dirs.data, child.dirs.data);
@@ -0,0 +1,32 @@
1
+ export interface MariaDBCredentials {
2
+ database: string;
3
+ user: string;
4
+ password: string;
5
+ rootPassword: string;
6
+ }
7
+ export interface MariaDBPrograms {
8
+ server: string;
9
+ client: string;
10
+ admin: string;
11
+ installDb: string;
12
+ basedir: string;
13
+ }
14
+ /** Only for a fresh owned datadir. Resume compares the private issued policy
15
+ * before invoking anything; it never runs install-db or overwrites users. */
16
+ export declare function mariaDBBootstrap(programs: MariaDBPrograms, credentials: MariaDBCredentials, datadir: string, extraSql: string): {
17
+ command: string;
18
+ args: string[];
19
+ privateSql: string;
20
+ };
21
+ /** Write as 0600, never in receipts/logs; readiness uses the application actor.
22
+ * It does not enable root over TCP or grant the application shutdown rights. */
23
+ export declare function mariaDBClientOptions(credentials: MariaDBCredentials, host: string, port: number): string;
24
+ /** The actual client proves version, port and datadir over an authenticated wire.
25
+ * The lifecycle joins this answer to its child identity and the server pidfile. */
26
+ export declare function mariaDBReadinessCommand(programs: MariaDBPrograms, optionsFile: string): {
27
+ command: string;
28
+ args: string[];
29
+ };
30
+ /** Provider aliases must issue one policy. Unconsumed vendor entrypoint flags
31
+ * remain gaps, including localhost health-user/grant generation. */
32
+ export declare function mariaDBCredentials(environment: Record<string, unknown>): MariaDBCredentials;
@@ -0,0 +1,90 @@
1
+ function text(value, name) {
2
+ if (typeof value !== 'string' || !value || value.includes('\0'))
3
+ throw new Error(`native MariaDB requires a nonempty ${name} without NUL`);
4
+ return value;
5
+ }
6
+ // NO_BACKSLASH_ESCAPES is set before these literals; quotes are doubled.
7
+ const literal = (value) => "'" + value.replaceAll("'", "''") + "'";
8
+ const databaseIdentifier = (value) => '`' + value.replaceAll('`', '``') + '`';
9
+ const option = (value) => '"' + value.replaceAll('\\', '\\\\')
10
+ .replaceAll('"', '\\"').replaceAll('\n', '\\n').replaceAll('\r', '\\r').replaceAll('\t', '\\t') + '"';
11
+ /** Only for a fresh owned datadir. Resume compares the private issued policy
12
+ * before invoking anything; it never runs install-db or overwrites users. */
13
+ export function mariaDBBootstrap(programs, credentials, datadir, extraSql) {
14
+ // Identifier length/charset belongs to the actual selected server's parser.
15
+ const database = text(credentials.database, 'database');
16
+ if (['mysql', 'sys', 'information_schema', 'performance_schema'].includes(database.toLowerCase()))
17
+ throw new Error('native MariaDB application policy requires its own database, not a system schema');
18
+ const user = text(credentials.user, 'user');
19
+ const password = text(credentials.password, 'password');
20
+ const rootPassword = text(credentials.rootPassword, 'root password');
21
+ if ([database, user, password, rootPassword].some(value => value.includes('@current_hostname')))
22
+ throw new Error('native MariaDB cross-bootstrap SQL filter cannot carry this issued policy');
23
+ if (user === 'root')
24
+ throw new Error('native MariaDB application user must differ from the provider-only root');
25
+ const account = literal(user) + "@'%'";
26
+ // The vendor script consumes these statements after its genuine system SQL.
27
+ // A root policy is installed while --bootstrap has no network listener.
28
+ const privateSql = [
29
+ // 12.3.3's mariadb_system_tables_data.sql:20 selects latin1. Explicitly
30
+ // interpret this UTF-8 extra file before reading any issued actor/value.
31
+ 'SET NAMES utf8mb4;',
32
+ "SET SESSION sql_mode = 'NO_BACKSLASH_ESCAPES';",
33
+ // --bootstrap starts with opt_noacl. Load the genuine system tables the
34
+ // vendor script just created before its account APIs become available.
35
+ 'FLUSH PRIVILEGES;',
36
+ "ALTER USER 'root'@'localhost' IDENTIFIED BY " + literal(rootPassword) + ';',
37
+ // 10.11.19 mysql_system_tables_data.sql:35-39 and 12.3.3's
38
+ // mariadb_system_tables_data.sql:37-41 create three normal root accounts.
39
+ // Retire the two passwordless TCP actors through the genuine account API.
40
+ "DROP USER IF EXISTS 'root'@'127.0.0.1', 'root'@'::1';",
41
+ 'CREATE DATABASE IF NOT EXISTS ' + databaseIdentifier(database) + ';',
42
+ 'CREATE USER ' + account + ' IDENTIFIED BY ' + literal(password) + ';',
43
+ 'GRANT ALL PRIVILEGES ON ' + databaseIdentifier(database) + '.* TO ' + account + ';',
44
+ 'FLUSH PRIVILEGES;', '',
45
+ ].join('\n');
46
+ return { command: programs.installDb, args: ['--no-defaults', '--basedir=' + programs.basedir,
47
+ '--datadir=' + datadir, '--auth-root-authentication-method=normal', '--skip-test-db',
48
+ // This is the vendor's source-defined cross-bootstrap branch: host-specific
49
+ // grant rows are excluded. @HOSTNAME@ is the runtime command "uname -n",
50
+ // not a builder hostname literal (scripts/CMakeLists.txt:180).
51
+ '--cross-bootstrap', '--extra-file=' + extraSql], privateSql };
52
+ }
53
+ /** Write as 0600, never in receipts/logs; readiness uses the application actor.
54
+ * It does not enable root over TCP or grant the application shutdown rights. */
55
+ export function mariaDBClientOptions(credentials, host, port) {
56
+ text(credentials.user, 'user');
57
+ text(credentials.password, 'password');
58
+ if (!/^127(?:\.\d{1,3}){3}$/u.test(host) || host.split('.').some(piece => Number(piece) > 255)
59
+ || !Number.isInteger(port) || port < 1 || port > 65535)
60
+ throw new Error('native MariaDB client requires its allocated loopback endpoint');
61
+ return ['[client]', 'user=' + option(credentials.user), 'password=' + option(credentials.password),
62
+ 'default-character-set=utf8mb4',
63
+ 'database=' + option(text(credentials.database, 'database')),
64
+ 'host=' + host, 'port=' + port, 'protocol=tcp', ''].join('\n');
65
+ }
66
+ /** The actual client proves version, port and datadir over an authenticated wire.
67
+ * The lifecycle joins this answer to its child identity and the server pidfile. */
68
+ export function mariaDBReadinessCommand(programs, optionsFile) {
69
+ return { command: programs.client, args: ['--defaults-file=' + optionsFile, '--batch', '--skip-column-names',
70
+ '--execute=SELECT VERSION(), @@port, @@datadir, HEX(CURRENT_USER())'] };
71
+ }
72
+ /** Provider aliases must issue one policy. Unconsumed vendor entrypoint flags
73
+ * remain gaps, including localhost health-user/grant generation. */
74
+ export function mariaDBCredentials(environment) {
75
+ const names = ['DATABASE', 'USER', 'PASSWORD', 'ROOT_PASSWORD'];
76
+ for (const name of Object.keys(environment))
77
+ if (/^(?:MYSQL|MARIADB)_/u.test(name)
78
+ && !names.some(suffix => name === 'MYSQL_' + suffix || name === 'MARIADB_' + suffix))
79
+ throw new Error('native MariaDB provider-start-input gap: ' + name);
80
+ const issued = (suffix) => {
81
+ const first = environment['MYSQL_' + suffix], second = environment['MARIADB_' + suffix];
82
+ if (first !== undefined && second !== undefined && first !== second)
83
+ throw new Error('native MariaDB provider aliases disagree for ' + suffix);
84
+ const value = first ?? second;
85
+ if (typeof value !== 'string' || value.includes('${'))
86
+ throw new Error('native MariaDB requires a literal issued ' + suffix);
87
+ return text(value, suffix);
88
+ };
89
+ return { database: issued('DATABASE'), user: issued('USER'), password: issued('PASSWORD'), rootPassword: issued('ROOT_PASSWORD') };
90
+ }
@@ -0,0 +1,23 @@
1
+ import type { MariaDBPrograms } from './mariadb-bootstrap.js';
2
+ export interface NativeMariaDBReference {
3
+ version: 1;
4
+ implementation: 'mariadb';
5
+ directory: string;
6
+ release: string;
7
+ receiptSha256: string;
8
+ sourceSha256: string;
9
+ recipe: {
10
+ path: string;
11
+ sha256: string;
12
+ };
13
+ }
14
+ export declare function nativeMariaDBReference(value: unknown): NativeMariaDBReference;
15
+ /** These are the external commands in the selected install-db branch:
16
+ * mysql_install_db.sh:47-48,136-138,436,538-542,601-609 (12.3.3).
17
+ * The pre-existing datadir and absent --user avoid mkdir/chmod/chown branches.
18
+ * Presence and WALI bytes do not grant admission: every actual exec still
19
+ * passes the attached kernel's selected program/import/profile checks. */
20
+ export declare function nativeMariaDBShellEnvironment(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
21
+ /** Only the made World's measured supply is eligible. This performs no
22
+ * compiler lookup, PATH search, initialization or readiness claim. */
23
+ export declare function nativeMariaDBSupplier(worldDirectory: string, image: string, reference: NativeMariaDBReference): MariaDBPrograms;
@@ -0,0 +1,215 @@
1
+ // Supply identity is separate from execution admission. The attached kernel
2
+ // owns its WALI profiles/import/memory checks on every actual program launch.
3
+ import { createHash } from 'node:crypto';
4
+ import { lstatSync, readFileSync, readlinkSync, readdirSync, realpathSync } from 'node:fs';
5
+ import { delimiter, isAbsolute, join, posix, relative } from 'node:path';
6
+ const digest = (value) => typeof value === 'string' && /^[a-f0-9]{64}$/u.test(value);
7
+ const sha = (value) => createHash('sha256').update(value).digest('hex');
8
+ const relativeName = (value) => typeof value === 'string' && !!value
9
+ && !isAbsolute(value) && !value.includes('\\') && !value.includes('\0')
10
+ && posix.normalize(value) === value && !value.split('/').some(part => !part || part === '.' || part === '..');
11
+ // Exact vendor initialization policies read for these archives. Supply for any
12
+ // later release remains separate from this lifecycle's bootstrap coverage.
13
+ const POLICY_SOURCE = {
14
+ '10.11.19': 'b8e543ee69d380fb1cfd563226f49e0fe96e4d67e7b7a9045ee514a168ed2066',
15
+ '12.3.3': 'e99d739fd4a55f9a11dea7bd2287a262673e287550af3071c8469dd2bec0c163',
16
+ };
17
+ export function nativeMariaDBReference(value) {
18
+ if (!value || typeof value !== 'object' || Array.isArray(value))
19
+ throw new Error('native MariaDB needs a typed supplier reference');
20
+ const row = value;
21
+ if (row.version !== 1 || row.implementation !== 'mariadb' || !relativeName(row.directory) || typeof row.release !== 'string'
22
+ || !/^\d+\.\d+\.\d+$/u.test(row.release) || !digest(row.receiptSha256) || !digest(row.sourceSha256)
23
+ || !relativeName(row.recipe?.path) || !digest(row.recipe?.sha256))
24
+ throw new Error('native MariaDB supplier reference is incomplete or invalid');
25
+ const known = ['version', 'implementation', 'directory', 'release', 'receiptSha256', 'sourceSha256', 'recipe'];
26
+ if (Object.keys(row).some(key => !known.includes(key)))
27
+ throw new Error('native MariaDB supplier reference has unknown fields');
28
+ if (Object.keys(row.recipe).some(key => !['path', 'sha256'].includes(key)))
29
+ throw new Error('native MariaDB recipe reference has unknown fields');
30
+ return row;
31
+ }
32
+ /** These are the external commands in the selected install-db branch:
33
+ * mysql_install_db.sh:47-48,136-138,436,538-542,601-609 (12.3.3).
34
+ * The pre-existing datadir and absent --user avoid mkdir/chmod/chown branches.
35
+ * Presence and WALI bytes do not grant admission: every actual exec still
36
+ * passes the attached kernel's selected program/import/profile checks. */
37
+ export function nativeMariaDBShellEnvironment(env) {
38
+ if (env.MYSQLD_BOOTSTRAP !== undefined)
39
+ throw new Error('native MariaDB provider-start-input gap: MYSQLD_BOOTSTRAP overrides the selected server');
40
+ const executable = (path) => {
41
+ try {
42
+ const file = realpathSync(path), stat = lstatSync(file);
43
+ return stat.isFile() && (stat.mode & 0o111) !== 0
44
+ && readFileSync(file).subarray(0, 8).equals(Buffer.from([0, 97, 115, 109, 1, 0, 0, 0]));
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ };
50
+ if (!executable('/bin/sh'))
51
+ throw new Error('native MariaDB initialization needs the installed real WALI /bin/sh');
52
+ const directories = (env.PATH ?? '').split(delimiter);
53
+ if (directories.some(directory => !isAbsolute(directory)))
54
+ throw new Error('native MariaDB initialization requires an explicit absolute helper PATH');
55
+ for (const name of ['dirname', 'sed', 'uname', 'cat']) {
56
+ // Shell PATH stops at the first executable, even if that file cannot be
57
+ // admitted. Never look past a conflicting earlier non-WALI command.
58
+ const directory = directories.find(directory => {
59
+ try {
60
+ const stat = lstatSync(realpathSync(join(directory, name)));
61
+ return stat.isFile() && (stat.mode & 0o111) !== 0;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ });
67
+ if (!directory || !executable(join(directory, name)))
68
+ throw new Error('native MariaDB initialization needs a selected real WALI helper: ' + name);
69
+ }
70
+ return { ...env }; // Keep the exact first-match order the shell will use.
71
+ }
72
+ /** Only the made World's measured supply is eligible. This performs no
73
+ * compiler lookup, PATH search, initialization or readiness claim. */
74
+ export function nativeMariaDBSupplier(worldDirectory, image, reference) {
75
+ if (POLICY_SOURCE[reference.release] !== reference.sourceSha256)
76
+ throw new Error('native MariaDB exact-release initialization policy is not covered');
77
+ const match = image.match(/^(?:docker\.io\/)?(?:library\/)?mariadb(?::[^@]+)?@sha256:([a-f0-9]{64})$/u);
78
+ if (!match)
79
+ throw new Error('native MariaDB requires its exact digest-bound declared image');
80
+ const world = realpathSync(worldDirectory), directory = join(world, reference.directory);
81
+ if (!lstatSync(directory).isDirectory())
82
+ throw new Error('native MariaDB supplier root must be a regular directory');
83
+ const root = realpathSync(directory), within = relative(world, root);
84
+ if (!within || within === '..' || within.startsWith('../') || isAbsolute(within))
85
+ throw new Error('native MariaDB supplier leaves its made World');
86
+ const file = (name) => {
87
+ if (!relativeName(name))
88
+ throw new Error('native MariaDB supplier has an invalid file path');
89
+ const path = join(root, name), rel = relative(root, realpathSync(path));
90
+ if (!rel || rel === '..' || rel.startsWith('../') || isAbsolute(rel))
91
+ throw new Error('native MariaDB supplier file leaves its root');
92
+ return path;
93
+ };
94
+ const body = (name) => {
95
+ const path = file(name);
96
+ if (!lstatSync(path).isFile())
97
+ throw new Error('native MariaDB evidence must be a regular file');
98
+ return readFileSync(path);
99
+ };
100
+ const bytes = body('artifact.json');
101
+ if (sha(bytes) !== reference.receiptSha256)
102
+ throw new Error('native MariaDB supplier receipt changed');
103
+ const receipt = JSON.parse(bytes.toString('utf8')), service = receipt.managedService, binding = receipt.imageBinding;
104
+ if (receipt.kind !== 'sweep.wali-native-managed-server' || receipt.version !== 1 || receipt.complete !== true
105
+ || receipt.sourceUnchanged !== true || receipt.target !== 'wasm32-linux-muslwali'
106
+ || receipt.qualified !== false || receipt.admitted !== false
107
+ || service?.kind !== 'mysql' || service.implementation !== 'mariadb' || service.version !== reference.release
108
+ || receipt.source?.sha256 !== reference.sourceSha256 || receipt.source.version !== reference.release
109
+ || receipt.recipe?.path !== reference.recipe.path || receipt.recipe.sha256 !== reference.recipe.sha256
110
+ || !binding || ![binding.indexDigest, binding.manifestDigest].includes('sha256:' + match[1]))
111
+ throw new Error('native MariaDB supplier differs from the declared provider/source/recipe');
112
+ const prefix = 'prefix/opt/programs/mariadb/' + reference.release;
113
+ if (receipt.prefix !== prefix || !receipt.files || typeof receipt.files !== 'object' || Array.isArray(receipt.files))
114
+ throw new Error('native MariaDB supplier has no measured install');
115
+ if (receipt.buildReceipt?.complete !== true || receipt.buildReceipt.sourceUnchanged !== true
116
+ || receipt.buildReceipt.source?.sha256 !== reference.sourceSha256
117
+ || !/^[a-f0-9]{40}$/u.test(receipt.buildReceipt.sourceHead ?? '')
118
+ || JSON.stringify(receipt.buildReceipt.phases) !== JSON.stringify(['source', 'inputs', 'generators', 'configure', 'build', 'install', 'return']))
119
+ throw new Error('native MariaDB supplier lacks a complete unchanged-source preparation record');
120
+ // The common catalog gate authenticates the complete producer return. The
121
+ // made World carries its pinned source/recipe evidence; execution checks the
122
+ // actual installed catalog prefix here, never a build-root executable.
123
+ const installed = '/opt/programs/mariadb/' + reference.release;
124
+ if (!lstatSync(installed).isDirectory() || realpathSync(installed) !== installed)
125
+ throw new Error('native MariaDB has no installed regular catalog prefix');
126
+ const runtimeFile = (name) => {
127
+ if (!relativeName(name))
128
+ throw new Error('native MariaDB install has an invalid relative path');
129
+ const path = join(installed, name), rel = relative(installed, realpathSync(path));
130
+ if (!rel || rel === '..' || rel.startsWith('../') || isAbsolute(rel))
131
+ throw new Error('native MariaDB runtime resource leaves its admitted prefix');
132
+ return path;
133
+ };
134
+ const runtimeBody = (name) => {
135
+ const path = runtimeFile(name);
136
+ if (!lstatSync(path).isFile())
137
+ throw new Error('native MariaDB runtime body is not a regular file');
138
+ const pin = receipt.files[prefix + '/' + name], data = readFileSync(path);
139
+ if (!pin || typeof pin !== 'object' || 'symlink' in pin || !Number.isSafeInteger(pin.bytes)
140
+ || pin.bytes < 0 || !digest(pin.sha256) || data.length !== pin.bytes || sha(data) !== pin.sha256)
141
+ throw new Error('native MariaDB installed program/resource differs: ' + name);
142
+ return data;
143
+ };
144
+ for (const [name, value] of Object.entries(receipt.files)) {
145
+ if (!name.startsWith(prefix + '/'))
146
+ continue;
147
+ const relative = name.slice(prefix.length + 1), pin = value;
148
+ if (!pin || typeof pin !== 'object')
149
+ throw new Error('native MariaDB runtime file has no measurement');
150
+ if ('symlink' in pin) {
151
+ const path = runtimeFile(relative);
152
+ if (typeof pin.symlink !== 'string' || isAbsolute(pin.symlink)
153
+ || !lstatSync(path).isSymbolicLink() || readlinkSync(path) !== pin.symlink)
154
+ throw new Error('native MariaDB installed alias changed');
155
+ }
156
+ else
157
+ runtimeBody(relative);
158
+ }
159
+ const inspect = (name) => {
160
+ const path = join(installed, name), stat = lstatSync(path);
161
+ if (stat.isDirectory()) {
162
+ for (const child of readdirSync(path))
163
+ inspect(name ? name + '/' + child : child);
164
+ }
165
+ else if (!receipt.files[prefix + '/' + name]) {
166
+ throw new Error('native MariaDB install contains an unmeasured file: ' + name);
167
+ }
168
+ };
169
+ inspect('');
170
+ const measured = (name) => {
171
+ const pin = receipt.files[name], data = body(name);
172
+ if (!pin || !Number.isSafeInteger(pin.bytes) || data.length !== pin.bytes || sha(data) !== pin.sha256)
173
+ throw new Error('native MariaDB supplier measurement differs: ' + name);
174
+ return data;
175
+ };
176
+ const indexBytes = measured('recipe-inputs/image-index.json'), manifestBytes = measured('recipe-inputs/image-manifest.json');
177
+ const configBytes = measured('recipe-inputs/image-config.json');
178
+ const config = JSON.parse(configBytes.toString('utf8')), index = JSON.parse(indexBytes.toString('utf8'));
179
+ const manifest = JSON.parse(manifestBytes.toString('utf8'));
180
+ if ('sha256:' + sha(indexBytes) !== binding.indexDigest || 'sha256:' + sha(manifestBytes) !== binding.manifestDigest
181
+ || 'sha256:' + sha(configBytes) !== binding.configDigest || manifest.config?.digest !== binding.configDigest
182
+ || !index.manifests?.some((row) => row.digest === binding.manifestDigest && row.platform?.os === 'linux' && row.platform.architecture === 'amd64')
183
+ || config.config?.Labels?.['org.opencontainers.image.version'] !== reference.release
184
+ || !config.config?.Env?.some((value) => typeof value === 'string' && value.startsWith('MARIADB_VERSION=1:' + reference.release + '+')))
185
+ throw new Error('native MariaDB image release reading differs');
186
+ const recipeBytes = measured('recipe-inputs/recipe.json');
187
+ if (sha(recipeBytes) !== reference.recipe.sha256)
188
+ throw new Error('native MariaDB recipe reading differs');
189
+ const recipe = JSON.parse(recipeBytes.toString('utf8')), layoutBytes = measured('recipe-inputs/release.json');
190
+ if (recipe.preparation?.files?.filter((row) => row.path === 'release.json' && row.sha256 === sha(layoutBytes)).length !== 1)
191
+ throw new Error('native MariaDB installed resources are not declared by the selected recipe');
192
+ const layout = JSON.parse(layoutBytes.toString('utf8'));
193
+ const expectedPrograms = ['sbin/mariadbd', 'bin/mariadb', 'bin/mariadb-admin', 'bin/my_print_defaults', 'bin/resolveip'];
194
+ if (layout.release !== reference.release || recipe.version !== reference.release
195
+ || !Array.isArray(layout.programs) || layout.programs.length !== expectedPrograms.length
196
+ || !expectedPrograms.every(name => layout.programs.includes(name))
197
+ || !Array.isArray(layout.requiredResources) || !layout.requiredResources.length)
198
+ throw new Error('native MariaDB resource release differs');
199
+ for (const name of layout.requiredResources)
200
+ runtimeBody(name);
201
+ const executable = (name) => {
202
+ const data = runtimeBody(name), path = runtimeFile(name);
203
+ if (!data.subarray(0, 8).equals(Buffer.from([0, 97, 115, 109, 1, 0, 0, 0])) || !(lstatSync(path).mode & 0o111))
204
+ throw new Error('native MariaDB requires an executable measured WALI program: ' + name);
205
+ return path;
206
+ };
207
+ const installDb = runtimeFile('scripts/mariadb-install-db');
208
+ if (!runtimeBody('scripts/mariadb-install-db').subarray(0, 2).equals(Buffer.from('#!'))
209
+ || !(lstatSync(installDb).mode & 0o111))
210
+ throw new Error('native MariaDB install-db is not its executable script');
211
+ for (const name of expectedPrograms)
212
+ executable(name);
213
+ return { server: executable('sbin/mariadbd'), client: executable('bin/mariadb'), admin: executable('bin/mariadb-admin'),
214
+ installDb, basedir: installed };
215
+ }
@@ -1,8 +1,12 @@
1
1
  import type { HostLaunch, InfraService } from './pglite-backing.js';
2
+ import type { ManagedProtocol } from './twin-backing.js';
2
3
  /** PATH is the binary contract, as for the native PostgreSQL example and LiveKit. No acquisition at boot. */
3
4
  export declare function mongodbArgv(service: InfraService, data: string): string[];
4
5
  /** Whether the server answering MongoDB's protocol on the port is this child: its own serverStatus names its pid. A
5
6
  * listener that is not answering yet, or another process's server on the port, is not. */
6
7
  export declare function mongodbServes(service: InfraService, pid: number): Promise<boolean>;
8
+ /** MongoDB's two questions for a twin that serves the kind (twin-backing.ts): the same ownership question the
9
+ * vendor's own server is asked above, so a pack that serves the kind answers serverStatus with its process's pid. */
10
+ export declare const MONGODB_PROTOCOL: ManagedProtocol;
7
11
  /** Ping the server; initialize only an explicitly declared one-member set and await its primary. */
8
12
  export declare function mongodbReady(service: InfraService, launch: HostLaunch, deadline: number, initialize?: boolean): Promise<void>;
@@ -1,3 +1,25 @@
1
+ import { connect } from 'node:net';
2
+ const PROTOCOL_QUESTION_MS = 1000;
3
+ /** A refused connection cannot answer MongoDB. Ask that negative question before loading the driver,
4
+ * whose server selection retries an absent endpoint for the whole protocol question budget.
5
+ * A TCP acceptance is only permission to ask the original protocol question below. */
6
+ function portConnects(port) {
7
+ return new Promise((resolve) => {
8
+ const socket = connect({ host: '127.0.0.1', port, timeout: PROTOCOL_QUESTION_MS });
9
+ let settled = false;
10
+ const finish = (connected) => {
11
+ if (settled)
12
+ return;
13
+ settled = true;
14
+ socket.destroy();
15
+ resolve(connected);
16
+ };
17
+ socket.once('connect', () => finish(true));
18
+ socket.on('error', () => finish(false));
19
+ socket.once('timeout', () => finish(false));
20
+ socket.once('close', () => finish(false));
21
+ });
22
+ }
1
23
  /** PATH is the binary contract, as for the native PostgreSQL example and LiveKit. No acquisition at boot. */
2
24
  export function mongodbArgv(service, data) {
3
25
  return ['mongod', '--bind_ip', '127.0.0.1', '--port', String(service.hostPort), '--dbpath', data,
@@ -6,8 +28,10 @@ export function mongodbArgv(service, data) {
6
28
  /** Whether the server answering MongoDB's protocol on the port is this child: its own serverStatus names its pid. A
7
29
  * listener that is not answering yet, or another process's server on the port, is not. */
8
30
  export async function mongodbServes(service, pid) {
31
+ if (!(await portConnects(service.hostPort)))
32
+ return false;
9
33
  const { MongoClient } = await import('mongodb');
10
- const client = new MongoClient(`mongodb://127.0.0.1:${service.hostPort}`, { directConnection: true, timeoutMS: 1000, serverSelectionTimeoutMS: 1000 });
34
+ const client = new MongoClient(`mongodb://127.0.0.1:${service.hostPort}`, { directConnection: true, timeoutMS: PROTOCOL_QUESTION_MS, serverSelectionTimeoutMS: PROTOCOL_QUESTION_MS });
11
35
  try {
12
36
  await client.connect();
13
37
  const status = await client.db('admin').command({ serverStatus: 1 });
@@ -20,6 +44,52 @@ export async function mongodbServes(service, pid) {
20
44
  await client.close().catch(() => undefined);
21
45
  }
22
46
  }
47
+ /** Whether anything speaking MongoDB's protocol answers on the port at all: a ping answered. */
48
+ async function mongodbAnswers(port) {
49
+ if (!(await portConnects(port)))
50
+ return false;
51
+ const { MongoClient } = await import('mongodb');
52
+ const client = new MongoClient(`mongodb://127.0.0.1:${port}`, { directConnection: true, timeoutMS: PROTOCOL_QUESTION_MS, serverSelectionTimeoutMS: PROTOCOL_QUESTION_MS });
53
+ try {
54
+ await client.connect();
55
+ await client.db('admin').command({ ping: 1 });
56
+ return true;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ finally {
62
+ await client.close().catch(() => undefined);
63
+ }
64
+ }
65
+ /** MongoDB's two questions for a twin that serves the kind (twin-backing.ts): the same ownership question the
66
+ * vendor's own server is asked above, so a pack that serves the kind answers serverStatus with its process's pid. */
67
+ export const MONGODB_PROTOCOL = {
68
+ name: 'MongoDB', answers: mongodbAnswers,
69
+ async serves(port, pid, service) {
70
+ if (!(await mongodbServes({ kind: 'mongodb', hostPort: port }, pid)))
71
+ return false;
72
+ if (!service?.replicaSet)
73
+ return true;
74
+ // PID ownership alone cannot make a standalone listener ready for a replica-set URI. An older pack that
75
+ // ignores the new CLI option must fail this protocol question before the application is given its address.
76
+ const { MongoClient } = await import('mongodb');
77
+ const client = new MongoClient(`mongodb://127.0.0.1:${port}`, { directConnection: true,
78
+ timeoutMS: PROTOCOL_QUESTION_MS, serverSelectionTimeoutMS: PROTOCOL_QUESTION_MS });
79
+ try {
80
+ const hello = await client.db('admin').command({ hello: 1 });
81
+ const self = `127.0.0.1:${port}`;
82
+ return hello.setName === service.replicaSet && hello.isWritablePrimary === true && hello.primary === self
83
+ && Array.isArray(hello.hosts) && hello.hosts.length === 1 && hello.hosts[0] === self;
84
+ }
85
+ catch {
86
+ return false;
87
+ }
88
+ finally {
89
+ await client.close().catch(() => undefined);
90
+ }
91
+ },
92
+ };
23
93
  /** Ping the server; initialize only an explicitly declared one-member set and await its primary. */
24
94
  export async function mongodbReady(service, launch, deadline, initialize = true) {
25
95
  const { MongoClient } = await import('mongodb');
@@ -0,0 +1,13 @@
1
+ import { type MariaDBCredentials } from './mariadb-bootstrap.js';
2
+ import { type NativeMariaDBReference } from './mariadb-supplier.js';
3
+ import type { HostLaunch } from './pglite-backing.js';
4
+ export interface NativeMariaDBService {
5
+ hostPort: number;
6
+ image: string;
7
+ reference: NativeMariaDBReference;
8
+ credentials: MariaDBCredentials;
9
+ }
10
+ export declare function nativeMariaDBUp(service: NativeMariaDBService, root: string, config: string, launch: HostLaunch): Promise<void>;
11
+ export declare function nativeMariaDBStatus(service: NativeMariaDBService, root: string, config: string, launch: HostLaunch): Promise<boolean>;
12
+ /** The held identity is enough to retire even if the supplier/config is gone. */
13
+ export declare function nativeMariaDBDown(root: string): Promise<void>;