@volter/world-runtime 3.0.163 → 3.0.165
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.
- package/dist/src/ancestry.js +2 -2
- package/dist/src/app-url.d.ts +5 -1
- package/dist/src/app-url.js +14 -1
- package/dist/src/branch.js +28 -8
- package/dist/src/browser-files.d.ts +5 -0
- package/dist/src/browser-files.js +14 -0
- package/dist/src/browsers.d.ts +2 -3
- package/dist/src/browsers.js +4 -4
- package/dist/src/catalog.d.ts +2 -1
- package/dist/src/catalog.js +9 -3
- package/dist/src/changeset.js +1 -1
- package/dist/src/cli-commands.js +71 -18
- package/dist/src/cli-load-clock.d.ts +1 -0
- package/dist/src/cli-load-clock.js +2 -0
- package/dist/src/cli.js +5 -1
- package/dist/src/clickhouse-backing.d.ts +2 -0
- package/dist/src/clickhouse-backing.js +145 -0
- package/dist/src/command-lifetime.d.mts +1 -1
- package/dist/src/command-lifetime.mjs +5 -2
- package/dist/src/configs.js +1 -1
- package/dist/src/consumers.js +1 -1
- package/dist/src/covers.d.ts +4 -2
- package/dist/src/covers.js +8 -4
- package/dist/src/go-mysql-binding.d.ts +19 -0
- package/dist/src/go-mysql-binding.js +51 -0
- package/dist/src/guest-processes.d.mts +1 -1
- package/dist/src/guest-processes.mjs +22 -14
- package/dist/src/host-cli.js +8 -1
- package/dist/src/host-ready.d.ts +16 -2
- package/dist/src/host-ready.js +66 -39
- package/dist/src/host-worker.js +7 -1
- package/dist/src/host.d.ts +26 -0
- package/dist/src/host.js +93 -3
- package/dist/src/infra-definition.d.ts +35 -0
- package/dist/src/infra-definition.js +146 -0
- package/dist/src/infra.d.ts +3 -0
- package/dist/src/infra.js +375 -105
- package/dist/src/init.d.ts +46 -0
- package/dist/src/init.js +446 -19
- package/dist/src/lifecycle-record.d.ts +5 -1
- package/dist/src/lifecycle-record.js +27 -4
- package/dist/src/local-branches.js +1 -1
- package/dist/src/mariadb-bootstrap.d.ts +32 -0
- package/dist/src/mariadb-bootstrap.js +90 -0
- package/dist/src/mariadb-supplier.d.ts +23 -0
- package/dist/src/mariadb-supplier.js +215 -0
- package/dist/src/mongodb-backing.d.ts +4 -0
- package/dist/src/mongodb-backing.js +71 -1
- package/dist/src/native-mariadb-backing.d.ts +13 -0
- package/dist/src/native-mariadb-backing.js +512 -0
- package/dist/src/native-postgres-backing.d.ts +4 -10
- package/dist/src/native-postgres-backing.js +428 -43
- package/dist/src/native-server-inputs.d.ts +16 -0
- package/dist/src/native-server-inputs.js +204 -0
- package/dist/src/origin.js +3 -3
- package/dist/src/original-inputs.d.ts +14 -0
- package/dist/src/original-inputs.js +84 -0
- package/dist/src/pack-facts.d.ts +9 -1
- package/dist/src/pglite-backing.d.ts +41 -17
- package/dist/src/pglite-backing.js +213 -79
- package/dist/src/pglite-host.mjs +482 -10
- package/dist/src/process-groups.d.mts +6 -1
- package/dist/src/process-groups.d.ts +2 -1
- package/dist/src/process-groups.js +1 -1
- package/dist/src/process-groups.mjs +82 -15
- package/dist/src/process-signal.d.mts +2 -2
- package/dist/src/process-signal.mjs +29 -9
- package/dist/src/project-inspect.d.ts +12 -0
- package/dist/src/project-inspect.js +41 -4
- package/dist/src/proxy-env.d.ts +34 -0
- package/dist/src/proxy-env.js +114 -0
- package/dist/src/redirect-proxy.d.ts +1 -31
- package/dist/src/redirect-proxy.js +3 -112
- package/dist/src/redis-backing.d.ts +6 -4
- package/dist/src/redis-backing.js +20 -168
- package/dist/src/runtime.d.ts +1 -0
- package/dist/src/runtime.js +503 -218
- package/dist/src/schema.d.ts +1 -0
- package/dist/src/schema.js +5 -3
- package/dist/src/served-world.js +3 -3
- package/dist/src/twin-backing.d.ts +47 -0
- package/dist/src/twin-backing.js +343 -0
- package/dist/src/up-cli.js +56 -3
- package/package.json +3 -3
- package/src/ancestry.ts +2 -2
- package/src/app-url.ts +9 -2
- package/src/branch.ts +26 -8
- package/src/browser-files.ts +15 -0
- package/src/browsers.ts +4 -4
- package/src/catalog.ts +9 -4
- package/src/changeset.ts +1 -1
- package/src/cli-commands.ts +85 -18
- package/src/cli-load-clock.ts +2 -0
- package/src/cli.ts +5 -1
- package/src/clickhouse-backing.ts +70 -0
- package/src/command-lifetime.d.mts +1 -1
- package/src/command-lifetime.mjs +5 -2
- package/src/configs.ts +1 -1
- package/src/consumers.ts +1 -1
- package/src/covers.ts +9 -4
- package/src/go-mysql-binding.ts +49 -0
- package/src/guest-processes.d.mts +1 -1
- package/src/guest-processes.mjs +22 -14
- package/src/host-cli.ts +7 -1
- package/src/host-ready.ts +62 -31
- package/src/host-worker.ts +6 -2
- package/src/host.ts +88 -6
- package/src/infra-definition.ts +135 -0
- package/src/infra.ts +250 -45
- package/src/init.ts +417 -18
- package/src/lifecycle-record.ts +18 -4
- package/src/local-branches.ts +1 -1
- package/src/mariadb-bootstrap.ts +107 -0
- package/src/mariadb-supplier.ts +199 -0
- package/src/mongodb-backing.ts +59 -1
- package/src/native-mariadb-backing.ts +418 -0
- package/src/native-postgres-backing.ts +353 -41
- package/src/native-server-inputs.ts +169 -0
- package/src/origin.ts +3 -3
- package/src/original-inputs.ts +72 -0
- package/src/pack-facts.ts +1 -1
- package/src/pglite-backing.ts +192 -80
- package/src/pglite-host.mjs +482 -10
- package/src/process-groups.d.mts +6 -1
- package/src/process-groups.mjs +82 -15
- package/src/process-groups.ts +2 -1
- package/src/process-signal.d.mts +2 -2
- package/src/process-signal.mjs +29 -9
- package/src/project-inspect.ts +37 -4
- package/src/proxy-env.ts +128 -0
- package/src/redirect-proxy.ts +3 -125
- package/src/redis-backing.ts +19 -136
- package/src/runtime.ts +342 -107
- package/src/schema.ts +5 -4
- package/src/served-world.ts +3 -3
- package/src/twin-backing.ts +300 -0
- package/src/up-cli.ts +52 -3
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type NativeMariaDBReference } from './mariadb-supplier.js';
|
|
2
|
+
export interface NativeServerInputs extends NativeMariaDBReference {
|
|
3
|
+
image: string;
|
|
4
|
+
declarationSha256: string;
|
|
5
|
+
snapshotSha256: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function nativeServerReference(value: unknown): NativeServerInputs;
|
|
8
|
+
export declare function nativeServerRuntimeReference(reference: NativeServerInputs): NativeMariaDBReference;
|
|
9
|
+
/** Validate metadata and use these verified owned byte copies at the later write boundary. */
|
|
10
|
+
export declare function readNativeServerInputs(parent: string, value: unknown): {
|
|
11
|
+
reference: NativeServerInputs;
|
|
12
|
+
files: ReadonlyMap<string, Buffer>;
|
|
13
|
+
aliases: string[];
|
|
14
|
+
};
|
|
15
|
+
/** Only World init writes its configuration tree, using the exact bytes it reverified itself. */
|
|
16
|
+
export declare function copyNativeServerInputs(parent: string, reference: NativeServerInputs, world: string): NativeMariaDBReference;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// World init owns this copy. Custody never initializes a server or admits a program.
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, isAbsolute, join, posix, relative } from 'node:path';
|
|
5
|
+
import { nativeMariaDBReference } from "./mariadb-supplier.js";
|
|
6
|
+
const sha = (value) => createHash('sha256').update(value).digest('hex');
|
|
7
|
+
const hex = (value) => typeof value === 'string' && /^[a-f0-9]{64}$/u.test(value);
|
|
8
|
+
const record = (value) => {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10
|
+
throw new Error('Native managed inputs need object metadata');
|
|
11
|
+
return value;
|
|
12
|
+
};
|
|
13
|
+
const regular = (path) => {
|
|
14
|
+
const stat = lstatSync(path);
|
|
15
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
16
|
+
throw new Error('Native input must be a regular owned file');
|
|
17
|
+
return readFileSync(path);
|
|
18
|
+
};
|
|
19
|
+
const confined = (root, name) => {
|
|
20
|
+
if (!name || isAbsolute(name) || name.includes('\\') || name.includes('\0') || posix.normalize(name) !== name
|
|
21
|
+
|| name.split('/').some(part => !part || part === '.' || part === '..'))
|
|
22
|
+
throw new Error('Native input path is not confined');
|
|
23
|
+
const path = join(root, name), rel = relative(root, realpathSync(path));
|
|
24
|
+
if (!rel || rel === '..' || rel.startsWith('../') || isAbsolute(rel))
|
|
25
|
+
throw new Error('Native input path leaves its owned root');
|
|
26
|
+
return path;
|
|
27
|
+
};
|
|
28
|
+
export function nativeServerReference(value) {
|
|
29
|
+
const row = record(value);
|
|
30
|
+
const { image, declarationSha256, snapshotSha256, ...provider } = row;
|
|
31
|
+
const reference = nativeMariaDBReference(provider);
|
|
32
|
+
if (typeof image !== 'string' || !/^docker\.io\/library\/mariadb@sha256:[a-f0-9]{64}$/u.test(image)
|
|
33
|
+
|| !hex(declarationSha256) || !hex(snapshotSha256))
|
|
34
|
+
throw new Error('Native managed source requires a resolved image and exact snapshot/declaration');
|
|
35
|
+
return { ...reference, image, declarationSha256, snapshotSha256 };
|
|
36
|
+
}
|
|
37
|
+
export function nativeServerRuntimeReference(reference) {
|
|
38
|
+
const { image: _image, declarationSha256: _declaration, snapshotSha256: _snapshot, ...provider } = reference;
|
|
39
|
+
return { ...provider, directory: `native-inputs/${reference.receiptSha256}/${reference.declarationSha256}` };
|
|
40
|
+
}
|
|
41
|
+
/** Validate metadata and use these verified owned byte copies at the later write boundary. */
|
|
42
|
+
export function readNativeServerInputs(parent, value) {
|
|
43
|
+
const reference = nativeServerReference(value), base = realpathSync(parent);
|
|
44
|
+
const root = confined(base, reference.directory), stat = lstatSync(root);
|
|
45
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
46
|
+
throw new Error('Native managed snapshot must be a regular directory');
|
|
47
|
+
const manifestBytes = regular(confined(root, 'manifest.json'));
|
|
48
|
+
if (sha(manifestBytes) !== reference.snapshotSha256)
|
|
49
|
+
throw new Error('Native managed snapshot changed');
|
|
50
|
+
const manifest = record(JSON.parse(manifestBytes.toString('utf8'))), measured = record(manifest.files);
|
|
51
|
+
if (manifest.kind !== 'volter.native-server-inputs' || manifest.version !== 1 || manifest.receiptSha256 !== reference.receiptSha256
|
|
52
|
+
|| Object.keys(manifest).some(key => !['kind', 'version', 'receiptSha256', 'files'].includes(key)))
|
|
53
|
+
throw new Error('Invalid native managed snapshot');
|
|
54
|
+
const required = ['artifact.json', 'source-receipt.json', 'recipe-inputs/recipe.json', 'recipe-inputs/release.json',
|
|
55
|
+
'recipe-inputs/image-index.json', 'recipe-inputs/image-manifest.json', 'recipe-inputs/image-config.json', 'declaration.json'];
|
|
56
|
+
if (Object.keys(measured).sort().join('\0') !== required.sort().join('\0'))
|
|
57
|
+
throw new Error('Native runtime evidence subset differs');
|
|
58
|
+
const files = new Map();
|
|
59
|
+
for (const [name, value] of Object.entries(measured)) {
|
|
60
|
+
const pin = record(value), bytes = regular(confined(root, name));
|
|
61
|
+
if (Object.keys(pin).some(key => !['bytes', 'sha256'].includes(key)) || !Number.isSafeInteger(pin.bytes)
|
|
62
|
+
|| pin.bytes !== bytes.length || !hex(pin.sha256) || sha(bytes) !== pin.sha256)
|
|
63
|
+
throw new Error('Native managed input bytes changed: ' + name);
|
|
64
|
+
files.set(name, bytes);
|
|
65
|
+
}
|
|
66
|
+
const inventory = [];
|
|
67
|
+
const walk = (directory) => {
|
|
68
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
69
|
+
const path = join(directory, entry.name);
|
|
70
|
+
if (entry.isDirectory())
|
|
71
|
+
walk(path);
|
|
72
|
+
else
|
|
73
|
+
inventory.push(relative(root, path).split(posix.sep).join('/'));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
walk(root);
|
|
77
|
+
if (inventory.sort().join('\0') !== [...required, 'manifest.json'].sort().join('\0'))
|
|
78
|
+
throw new Error('Native managed snapshot contains unmeasured files');
|
|
79
|
+
const receiptBytes = files.get('artifact.json'), receipt = record(JSON.parse(receiptBytes.toString('utf8')));
|
|
80
|
+
const source = record(receipt.source), recipe = record(receipt.recipe), image = record(receipt.imageBinding), originalMap = record(receipt.files);
|
|
81
|
+
if (sha(receiptBytes) !== reference.receiptSha256 || receipt.kind !== 'sweep.wali-native-managed-server' || receipt.version !== 1
|
|
82
|
+
|| receipt.complete !== true || receipt.sourceUnchanged !== true || receipt.qualified !== false || receipt.admitted !== false
|
|
83
|
+
|| receipt.target !== 'wasm32-linux-muslwali' || receipt.prefix !== 'prefix/opt/programs/mariadb/' + reference.release
|
|
84
|
+
|| source.version !== reference.release || source.sha256 !== reference.sourceSha256
|
|
85
|
+
|| recipe.path !== reference.recipe.path || recipe.sha256 !== reference.recipe.sha256
|
|
86
|
+
|| sha(files.get('recipe-inputs/recipe.json')) !== reference.recipe.sha256
|
|
87
|
+
|| ![image.indexDigest, image.manifestDigest].includes(reference.image.slice(reference.image.indexOf('@') + 1)))
|
|
88
|
+
throw new Error('Native managed snapshot differs from its actual original producer');
|
|
89
|
+
for (const [name, bytes] of files)
|
|
90
|
+
if (name !== 'artifact.json' && name !== 'declaration.json') {
|
|
91
|
+
const original = record(originalMap[name]);
|
|
92
|
+
if (original.bytes !== bytes.length || original.sha256 !== sha(bytes))
|
|
93
|
+
throw new Error('Native runtime subset is absent from its original receipt map');
|
|
94
|
+
}
|
|
95
|
+
files.set('manifest.json', manifestBytes);
|
|
96
|
+
const declaration = files.get('declaration.json');
|
|
97
|
+
if (sha(declaration) !== reference.declarationSha256)
|
|
98
|
+
throw new Error('Native source declaration bytes changed');
|
|
99
|
+
return { reference, files, aliases: sourceAliases(new TextDecoder('utf-8', { fatal: true }).decode(declaration), reference.release) };
|
|
100
|
+
}
|
|
101
|
+
/** Every parsed source requirement has a disposition; unsupported start behavior refuses before World writes. */
|
|
102
|
+
function sourceAliases(declaration, release) {
|
|
103
|
+
const service = record(JSON.parse(declaration));
|
|
104
|
+
// annotated on the name, so each refuse() narrows what follows it (TypeScript's assertion-call rule)
|
|
105
|
+
const refuse = (field) => { throw new Error(`native MariaDB ${release} provider-start-input gap: ${field}`); };
|
|
106
|
+
const allowed = ['name', 'image', 'build', 'command', 'entrypoint', 'environment', 'envFiles', 'volumes', 'healthcheck', 'workingDir', 'hostname',
|
|
107
|
+
'capAdd', 'capDrop', 'privileged', 'user', 'secrets', 'networkMode', 'limits', 'develop', 'ports', 'expose', 'dependsOn', 'hostNames', 'profiles',
|
|
108
|
+
'ignored', 'connectionBindings', 'environmentInherited'];
|
|
109
|
+
for (const key of Object.keys(service))
|
|
110
|
+
if (!allowed.includes(key))
|
|
111
|
+
refuse(key);
|
|
112
|
+
for (const key of ['build', 'command', 'entrypoint', 'healthcheck', 'workingDir', 'hostname', 'user', 'networkMode', 'develop', 'connectionBindings'])
|
|
113
|
+
if (service[key] !== undefined)
|
|
114
|
+
refuse(key);
|
|
115
|
+
if (service.privileged === true)
|
|
116
|
+
refuse('privileged');
|
|
117
|
+
for (const key of ['envFiles', 'capAdd', 'capDrop', 'secrets', 'dependsOn', 'profiles', 'ignored', 'environmentInherited']) {
|
|
118
|
+
if (!Array.isArray(service[key]) && service[key] !== undefined)
|
|
119
|
+
refuse(key);
|
|
120
|
+
if (Array.isArray(service[key]) && service[key].length)
|
|
121
|
+
refuse(key);
|
|
122
|
+
}
|
|
123
|
+
if (Object.keys(record(service.limits)).length)
|
|
124
|
+
refuse('limits');
|
|
125
|
+
if (!Array.isArray(service.volumes) || service.volumes.length > 1)
|
|
126
|
+
refuse('volumes');
|
|
127
|
+
for (const item of service.volumes) {
|
|
128
|
+
const mount = record(item);
|
|
129
|
+
if (!['named', 'anonymous'].includes(String(mount.kind)) || mount.target !== '/var/lib/mysql' || mount.readOnly !== false)
|
|
130
|
+
refuse('volume');
|
|
131
|
+
}
|
|
132
|
+
for (const key of ['ports', 'expose']) {
|
|
133
|
+
if (!Array.isArray(service[key]))
|
|
134
|
+
refuse(key);
|
|
135
|
+
for (const value of service[key])
|
|
136
|
+
if (key === 'expose' ? value !== 3306 : record(value).container !== 3306 || record(value).protocol !== 'tcp')
|
|
137
|
+
refuse(key);
|
|
138
|
+
}
|
|
139
|
+
const env = record(service.environment), aliases = [];
|
|
140
|
+
const values = new Map();
|
|
141
|
+
for (const [name, value] of Object.entries(env)) {
|
|
142
|
+
const match = /^(MYSQL|MARIADB)_(DATABASE|USER|PASSWORD|ROOT_PASSWORD)$/u.exec(name);
|
|
143
|
+
if (!match || typeof value !== 'string' || !value || value.includes('\0'))
|
|
144
|
+
refuse(name);
|
|
145
|
+
const field = match[2];
|
|
146
|
+
if (values.has(field) && values.get(field) !== value)
|
|
147
|
+
refuse('conflicting credential aliases: ' + field);
|
|
148
|
+
values.set(field, value);
|
|
149
|
+
if (match[1] === 'MARIADB')
|
|
150
|
+
aliases.push(name);
|
|
151
|
+
}
|
|
152
|
+
return aliases;
|
|
153
|
+
}
|
|
154
|
+
/** Only World init writes its configuration tree, using the exact bytes it reverified itself. */
|
|
155
|
+
export function copyNativeServerInputs(parent, reference, world) {
|
|
156
|
+
const snapshot = readNativeServerInputs(parent, reference);
|
|
157
|
+
const directory = `native-inputs/${reference.receiptSha256}/${reference.declarationSha256}`;
|
|
158
|
+
const root = join(world, directory);
|
|
159
|
+
const collection = join(world, 'native-inputs');
|
|
160
|
+
try {
|
|
161
|
+
const stat = lstatSync(collection);
|
|
162
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
163
|
+
throw new Error('Native World input collection is not owned');
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (error.code !== 'ENOENT')
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
let exists = true;
|
|
170
|
+
try {
|
|
171
|
+
lstatSync(root);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error.code !== 'ENOENT')
|
|
175
|
+
throw error;
|
|
176
|
+
exists = false;
|
|
177
|
+
}
|
|
178
|
+
if (exists)
|
|
179
|
+
readNativeServerInputs(world, { ...reference, directory });
|
|
180
|
+
else {
|
|
181
|
+
mkdirSync(collection, { recursive: true, mode: 0o700 });
|
|
182
|
+
const producerDirectory = dirname(root);
|
|
183
|
+
try {
|
|
184
|
+
const stat = lstatSync(producerDirectory);
|
|
185
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
186
|
+
throw new Error('Native World producer collection is not owned');
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
if (error.code !== 'ENOENT')
|
|
190
|
+
throw error;
|
|
191
|
+
mkdirSync(producerDirectory, { mode: 0o700 });
|
|
192
|
+
}
|
|
193
|
+
const staging = root + '.' + randomUUID() + '.partial';
|
|
194
|
+
mkdirSync(staging, { mode: 0o700 });
|
|
195
|
+
for (const [name, bytes] of snapshot.files) {
|
|
196
|
+
const file = join(staging, name);
|
|
197
|
+
mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
|
|
198
|
+
writeFileSync(file, bytes, { flag: 'wx', mode: 0o400 });
|
|
199
|
+
}
|
|
200
|
+
renameSync(staging, root);
|
|
201
|
+
}
|
|
202
|
+
// This subset does not pretend the original map's archives/dependency outputs were copied into the World.
|
|
203
|
+
return nativeServerRuntimeReference(reference);
|
|
204
|
+
}
|
package/dist/src/origin.js
CHANGED
|
@@ -55,7 +55,7 @@ export async function fetchFromOrigin(name, options) {
|
|
|
55
55
|
const meta = readBranchMeta(state, controlRoot);
|
|
56
56
|
if (meta?.parent && !meta.origin)
|
|
57
57
|
throw new Error('Cannot attach a first origin to an existing local fork; clone the origin before branching');
|
|
58
|
-
});
|
|
58
|
+
}, { operation: 'origin fetch' });
|
|
59
59
|
}
|
|
60
60
|
for (const service of services) {
|
|
61
61
|
const controlRoot = join(instance.dirs.data, service);
|
|
@@ -90,7 +90,7 @@ export async function fetchFromOrigin(name, options) {
|
|
|
90
90
|
const pointer = selected?.origin ?? selected?.parent;
|
|
91
91
|
appended[service] = Math.max(0, parentLength - before);
|
|
92
92
|
behind[service] = pointer ? Math.max(0, parentLength - pointer.position) || (pointer.view === head.view ? 0 : 1) : 0;
|
|
93
|
-
});
|
|
93
|
+
}, { operation: 'origin fetch' });
|
|
94
94
|
continue;
|
|
95
95
|
}
|
|
96
96
|
// Download one immutable view, then publish its complete cache head. No mixed pages.
|
|
@@ -133,7 +133,7 @@ export async function fetchFromOrigin(name, options) {
|
|
|
133
133
|
const pointer = selected?.origin ?? selected?.parent;
|
|
134
134
|
// Changed same-length snapshots are still drift; offsets are scoped to a view.
|
|
135
135
|
behind[service] = pointer ? Math.max(0, entries.length - pointer.position) || (pointer.remoteView === first.view ? 0 : 1) : 0;
|
|
136
|
-
});
|
|
136
|
+
}, { operation: 'origin fetch' });
|
|
137
137
|
}
|
|
138
138
|
saveWorldInstance({ ...instance, origin: { ...origin, ...(options.full ? { clonedAt: at_ } : instance.origin?.clonedAt ? { clonedAt: instance.origin.clonedAt } : {}), fetchedAt: at_ } });
|
|
139
139
|
// a clone names its remote where `volter remote add` does (world.json, as git config holds a remote): one record of
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Retained original-input custody, separate from the managed service's mutable data. */
|
|
2
|
+
import * as managedOriginalInputs from '@volter/world-core/managed-original-inputs';
|
|
3
|
+
export type OriginalInputReference = managedOriginalInputs.OriginalInputReference;
|
|
4
|
+
export declare function readOriginalInputSnapshot(parent: string, value: unknown): {
|
|
5
|
+
reference: OriginalInputReference;
|
|
6
|
+
root: string;
|
|
7
|
+
bytes: Uint8Array;
|
|
8
|
+
snapshot: ReturnType<typeof managedOriginalInputs.readSnapshot>;
|
|
9
|
+
};
|
|
10
|
+
export declare function copyOriginalInputSnapshot(from: string, reference: OriginalInputReference, out: string): OriginalInputReference;
|
|
11
|
+
/** A retained store cannot silently acquire another source declaration on resume. */
|
|
12
|
+
export declare function verifyOriginalInputState(root: string, reference: OriginalInputReference): void;
|
|
13
|
+
export declare function bindOriginalInputState(root: string, reference: OriginalInputReference): void;
|
|
14
|
+
export declare function preflightOriginalInputState(root: string, reference: OriginalInputReference): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Retained original-input custody, separate from the managed service's mutable data. */
|
|
2
|
+
import * as managedOriginalInputs from '@volter/world-core/managed-original-inputs';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
export function readOriginalInputSnapshot(parent, value) {
|
|
7
|
+
const reference = managedOriginalInputs.referenceOf(value);
|
|
8
|
+
let root = resolve(parent);
|
|
9
|
+
for (const part of reference.root.split('/')) {
|
|
10
|
+
root = join(root, part);
|
|
11
|
+
const stat = lstatSync(root);
|
|
12
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
13
|
+
throw new Error('Original-input root traverses a host symlink');
|
|
14
|
+
}
|
|
15
|
+
const file = join(root, reference.manifest), stat = lstatSync(file);
|
|
16
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 64 * 1024 * 1024)
|
|
17
|
+
throw new Error('Invalid original-input manifest file');
|
|
18
|
+
const bytes = readFileSync(file), objectsDir = join(root, 'objects'), directory = lstatSync(objectsDir);
|
|
19
|
+
if (!directory.isDirectory() || directory.isSymbolicLink())
|
|
20
|
+
throw new Error('Invalid original-input objects directory');
|
|
21
|
+
const snapshot = managedOriginalInputs.readSnapshot(bytes, reference.sha256, (path, size) => {
|
|
22
|
+
const file = join(root, path), stat = lstatSync(file);
|
|
23
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== size)
|
|
24
|
+
throw new Error('Original-input object differs');
|
|
25
|
+
return readFileSync(file);
|
|
26
|
+
});
|
|
27
|
+
if (snapshot.manifest.inventoryDigest !== reference.inventoryDigest || readdirSync(root).sort().join('\0') !== 'manifest.json\0objects'
|
|
28
|
+
|| readdirSync(objectsDir).sort().join('\0') !== [...snapshot.objects.keys()].sort().join('\0'))
|
|
29
|
+
throw new Error('Original-input closed inventory differs');
|
|
30
|
+
return { reference, root, bytes, snapshot };
|
|
31
|
+
}
|
|
32
|
+
export function copyOriginalInputSnapshot(from, reference, out) {
|
|
33
|
+
const input = readOriginalInputSnapshot(from, reference);
|
|
34
|
+
const next = { ...reference, root: `original-inputs/${reference.sha256}` };
|
|
35
|
+
const parent = join(out, 'original-inputs');
|
|
36
|
+
if (existsSync(parent) && (!lstatSync(parent).isDirectory() || lstatSync(parent).isSymbolicLink()))
|
|
37
|
+
throw new Error('Original-input output parent is not an owned directory');
|
|
38
|
+
mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
39
|
+
const destination = join(out, next.root);
|
|
40
|
+
if (existsSync(destination)) {
|
|
41
|
+
readOriginalInputSnapshot(out, next);
|
|
42
|
+
return next;
|
|
43
|
+
}
|
|
44
|
+
const staging = destination + '.' + randomUUID() + '.partial';
|
|
45
|
+
mkdirSync(join(staging, 'objects'), { recursive: true, mode: 0o700 });
|
|
46
|
+
for (const [sha256, bytes] of input.snapshot.objects)
|
|
47
|
+
writeFileSync(join(staging, 'objects', sha256), bytes, { mode: 0o400, flag: 'wx' });
|
|
48
|
+
writeFileSync(join(staging, 'manifest.json'), input.bytes, { mode: 0o400, flag: 'wx' });
|
|
49
|
+
renameSync(staging, destination);
|
|
50
|
+
chmodSync(join(destination, "objects"), 0o500);
|
|
51
|
+
chmodSync(destination, 0o500);
|
|
52
|
+
return next;
|
|
53
|
+
}
|
|
54
|
+
/** A retained store cannot silently acquire another source declaration on resume. */
|
|
55
|
+
export function verifyOriginalInputState(root, reference) {
|
|
56
|
+
const marker = join(root, '.original-inputs.json');
|
|
57
|
+
const stat = lstatSync(marker);
|
|
58
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4096)
|
|
59
|
+
throw new Error('Invalid retained original-input state identity');
|
|
60
|
+
const value = JSON.parse(readFileSync(marker, 'utf8'));
|
|
61
|
+
if (value.kind !== 'volter.managed-original-input-state' || value.version !== 1 || value.sha256 !== reference.sha256)
|
|
62
|
+
throw new Error('Retained managed state belongs to different original inputs; use an explicit fresh World or reset');
|
|
63
|
+
}
|
|
64
|
+
export function bindOriginalInputState(root, reference) {
|
|
65
|
+
const marker = join(root, '.original-inputs.json');
|
|
66
|
+
if (existsSync(marker)) {
|
|
67
|
+
verifyOriginalInputState(root, reference);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (readdirSync(root).length)
|
|
71
|
+
throw new Error('Retained managed state has no original-input identity; a fresh source-bound state is required');
|
|
72
|
+
writeFileSync(marker, JSON.stringify({ kind: 'volter.managed-original-input-state', version: 1, sha256: reference.sha256 }) + '\n', { flag: 'wx', mode: 0o600 });
|
|
73
|
+
}
|
|
74
|
+
export function preflightOriginalInputState(root, reference) {
|
|
75
|
+
if (!existsSync(root))
|
|
76
|
+
return;
|
|
77
|
+
const directory = lstatSync(root);
|
|
78
|
+
if (!directory.isDirectory() || directory.isSymbolicLink())
|
|
79
|
+
throw new Error('Managed original-input state root is not an owned directory');
|
|
80
|
+
if (existsSync(join(root, '.original-inputs.json')))
|
|
81
|
+
verifyOriginalInputState(root, reference);
|
|
82
|
+
else if (readdirSync(root).length)
|
|
83
|
+
throw new Error('Retained managed state has no original-input identity; a fresh source-bound state is required');
|
|
84
|
+
}
|
package/dist/src/pack-facts.d.ts
CHANGED
|
@@ -58,9 +58,17 @@ export type PackFacts = {
|
|
|
58
58
|
serveExport?: string;
|
|
59
59
|
/** The managed-infrastructure kind this pack is the World's backing for (architecture.md, "The descriptor"). */
|
|
60
60
|
managedService?: {
|
|
61
|
-
kind: 'redis' | 'mongodb';
|
|
61
|
+
kind: 'redis' | 'mongodb' | 'clickhouse';
|
|
62
62
|
scheme: string;
|
|
63
63
|
note: string;
|
|
64
|
+
configurationArg?: '--configuration';
|
|
65
|
+
originalInputs?: {
|
|
66
|
+
version: 1;
|
|
67
|
+
manifestArg: '--original-inputs';
|
|
68
|
+
rootArg: '--original-root';
|
|
69
|
+
digestArg: '--original-inputs-sha256';
|
|
70
|
+
paths: string[];
|
|
71
|
+
};
|
|
64
72
|
};
|
|
65
73
|
/** The World's managed database the runtime binds the twin to at boot (TwinPack.managedDatabase). */
|
|
66
74
|
managedDatabase?: {
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
import { type WorldDatabasePrepare } from './schema.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
replicaSet?: string;
|
|
6
|
-
postgres?: {
|
|
7
|
-
user: string;
|
|
8
|
-
database: string;
|
|
9
|
-
password?: string;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
/** Read the generated definition's loopback port mappings. MongoDB may explicitly request
|
|
13
|
-
* one replica-set member through its Compose-compatible command; other mongod options are refused. */
|
|
14
|
-
export declare function parseInfraDefinition(text: string): InfraService[];
|
|
15
|
-
/** Endpoints of the declared infrastructure, published through external.discover. Only URLs
|
|
16
|
-
* whose protocol and loopback port match a declared service belong to this lifecycle. */
|
|
17
|
-
export declare function infraConnections(services: InfraService[], env: Record<string, string | undefined>): Record<string, string>;
|
|
2
|
+
import type { InfraService } from './infra-definition.js';
|
|
3
|
+
export { parseInfraDefinition, infraConnections } from './infra-definition.js';
|
|
4
|
+
export type { InfraService } from './infra-definition.js';
|
|
18
5
|
export declare function containerlessDataDirectory(kind: string): string | undefined;
|
|
19
6
|
/** How a backing starts its hosts: the environment they run in (the World's service environment, which
|
|
20
7
|
* the runtime computes for the infrastructure service: admitted host env, the World's env, the service's own, less
|
|
@@ -25,6 +12,8 @@ export interface HostLaunch {
|
|
|
25
12
|
env?: NodeJS.ProcessEnv;
|
|
26
13
|
signal?: AbortSignal;
|
|
27
14
|
note?: (what: string) => void;
|
|
15
|
+
/** Said once the database's cluster is chosen, placed and its server spawned, before its readiness is waited on (S2 R79). */
|
|
16
|
+
onPlaced?: () => void;
|
|
28
17
|
}
|
|
29
18
|
/** The kinds this backing cannot serve, or [] when it can serve the world. */
|
|
30
19
|
export declare function unsupportedKinds(services: InfraService[]): string[];
|
|
@@ -51,11 +40,41 @@ export declare function preparedCluster(worldConfig: string): {
|
|
|
51
40
|
* another password takes the same cluster and authenticates against its own. Extensions are loaded into the module
|
|
52
41
|
* at each open, never created in the cluster by the open path.
|
|
53
42
|
*/
|
|
43
|
+
/**
|
|
44
|
+
* A managed store other than the cluster that a step run with the World up may have written (architecture, "A step
|
|
45
|
+
* may need the World"): where the preparation stages it, beside the cluster, and its stamp.
|
|
46
|
+
*/
|
|
47
|
+
export declare function preparedStore(worldConfig: string, name: string): {
|
|
48
|
+
directory: string;
|
|
49
|
+
stamp: string;
|
|
50
|
+
};
|
|
51
|
+
/** The state directory a preparation with the World up runs in, beside the World's own (native-postgres-backing.ts). */
|
|
52
|
+
export declare const PREPARE_STATE_DIRECTORY = ".volter-prepare";
|
|
53
|
+
/** The stamp of a staged store: the migration it was written by. */
|
|
54
|
+
export declare function preparedStoreStamp(migration: PreparedMigration): string;
|
|
55
|
+
/**
|
|
56
|
+
* Place a staged store where its service serves from, at `up`: only into a place that holds nothing, and only when
|
|
57
|
+
* its stamp is the migration this World expects (the rule a prepared cluster is taken by). Anything else leaves the
|
|
58
|
+
* service to start empty, and the note says why.
|
|
59
|
+
*/
|
|
60
|
+
export declare function takePreparedStore(worldConfig: string | undefined, env: NodeJS.ProcessEnv, name: string, target: string): {
|
|
61
|
+
placed: boolean;
|
|
62
|
+
note?: string;
|
|
63
|
+
};
|
|
54
64
|
/** A prepared cluster's migration, as its stamp records it: the step's command and the digest of each of its inputs. */
|
|
65
|
+
/** What a prepared cluster was migrated by: the step's command, its inputs' digests and, for a step run with the
|
|
66
|
+
* World up (`with: "world"`), the twins that were up for it, each by its version. */
|
|
55
67
|
export interface PreparedMigration {
|
|
56
68
|
command: string[];
|
|
57
69
|
inputs: Record<string, string>;
|
|
70
|
+
world?: Record<string, string>;
|
|
58
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The twins a step declared `with: "world"` runs beside, each by the version the World names: part of what the
|
|
74
|
+
* cluster was migrated with, so a World whose twins moved does not take a cluster another set of them wrote into.
|
|
75
|
+
* Read from the document itself, in id order.
|
|
76
|
+
*/
|
|
77
|
+
export declare function preparedWorldServices(worldConfig: string): Record<string, string>;
|
|
59
78
|
/**
|
|
60
79
|
* The SHA-256 of each of a migration step's inputs, under its cwd in the app's tree: a file's bytes; a directory's
|
|
61
80
|
* sorted relative paths, each with its type, its mode and its bytes (a link, its target). What the image build records
|
|
@@ -64,6 +83,8 @@ export interface PreparedMigration {
|
|
|
64
83
|
export declare function migrationInputDigests(cwd: string, inputs: readonly string[]): Record<string, string>;
|
|
65
84
|
/** The World's declared migration step for its prepared database, if any (runtime.database.prepare). */
|
|
66
85
|
export declare function databasePrepareOf(worldConfig: string): WorldDatabasePrepare | undefined;
|
|
86
|
+
/** `NAME=value` lines (an image's environment, VOLTER_WORLD_APP_ENV), as an environment. */
|
|
87
|
+
export declare function environmentFile(path: string): Record<string, string>;
|
|
67
88
|
/**
|
|
68
89
|
* The World's declared step (runtime.database.prepare) run once against a prepared cluster its backing is serving on
|
|
69
90
|
* the World's port: the declared command in the app's tree, with the image's environment and the World's values over
|
|
@@ -81,6 +102,8 @@ export declare function databasePrepareOf(worldConfig: string): WorldDatabasePre
|
|
|
81
102
|
export declare function keepFailureEvidence(name: string, evidence: string | {
|
|
82
103
|
file: string;
|
|
83
104
|
}, env: NodeJS.ProcessEnv): string;
|
|
105
|
+
/** A package script's own path: each `node_modules/.bin` from the working directory up to the application's root. */
|
|
106
|
+
export declare function packageScriptPath(cwd: string, appRoot: string): string[];
|
|
84
107
|
export declare function runDatabasePrepare(step: WorldDatabasePrepare, worldConfig: string, launch: HostLaunch): Promise<PreparedMigration>;
|
|
85
108
|
/** Run the host's open path once into the World's prepared directory; replaces an earlier preparation. */
|
|
86
109
|
export declare function pglitePrepare(service: InfraService, worldConfig: string, launch?: HostLaunch): Promise<string>;
|
|
@@ -125,8 +148,9 @@ export declare const DEVELOPMENT_MIGRATION_NOTICE = "development World: its data
|
|
|
125
148
|
export declare function takePreparedCluster(prepared: {
|
|
126
149
|
directory: string;
|
|
127
150
|
stamp: string;
|
|
128
|
-
}, expected: string | ((stamp: string) => boolean), serviceData: string, mismatch: string, migration?: PreparedMigration): {
|
|
151
|
+
}, expected: string | ((stamp: string) => boolean), serviceData: string, mismatch: string, migration?: PreparedMigration, copy?: (from: string, to: string) => void): {
|
|
129
152
|
placed: boolean;
|
|
153
|
+
copied?: boolean;
|
|
130
154
|
note?: string;
|
|
131
155
|
};
|
|
132
156
|
export declare function pgliteUp(services: InfraService[], dataDir: string, launch?: HostLaunch): Promise<void>;
|