@pulse-compute/wasm-guest-link 0.0.0 → 1.0.0-beta.1
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/README.md +93 -2
- package/package.json +29 -7
- package/src/constants.js +159 -0
- package/src/contracts.js +1143 -0
- package/src/errors.js +76 -0
- package/src/files.js +149 -0
- package/src/index.js +33 -0
- package/src/inspect.js +510 -0
- package/src/materialize.js +424 -0
- package/src/memory-owner.js +29 -0
- package/src/pipeline.js +729 -0
- package/src/stage.js +578 -0
- package/src/toolchain.js +125 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const {
|
|
6
|
+
normalizeGuestUnitManifest,
|
|
7
|
+
normalizeGuestUnitPlan,
|
|
8
|
+
assertPlanMatchesManifest
|
|
9
|
+
} = require('./contracts.js');
|
|
10
|
+
const {
|
|
11
|
+
versions,
|
|
12
|
+
diagnosticCodes,
|
|
13
|
+
memoryAbi,
|
|
14
|
+
memoryAbiV2
|
|
15
|
+
} = require('./constants.js');
|
|
16
|
+
const { fail, wrap } = require('./errors.js');
|
|
17
|
+
const {
|
|
18
|
+
fileRecord,
|
|
19
|
+
sourceTreeRecord,
|
|
20
|
+
resolveExistingContained,
|
|
21
|
+
ensureContainedDirectory,
|
|
22
|
+
inside
|
|
23
|
+
} = require('./files.js');
|
|
24
|
+
const { inspectWasmFile, surfaceImport, surfaceExport } = require('./inspect.js');
|
|
25
|
+
|
|
26
|
+
function compare(value, expected, code, message, details) {
|
|
27
|
+
if (Buffer.isBuffer(value) && Buffer.isBuffer(expected)) {
|
|
28
|
+
if (!value.equals(expected)) fail(code, message, details);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (JSON.stringify(value) !== JSON.stringify(expected)) fail(code, message, details);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizedManifestExports(manifest) {
|
|
35
|
+
return manifest.exports.map((entry) => Object.freeze({
|
|
36
|
+
name: entry.name,
|
|
37
|
+
kind: entry.kind,
|
|
38
|
+
parameters: entry.parameters,
|
|
39
|
+
results: entry.results
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function memoryContract(manifest) {
|
|
44
|
+
return manifest.memory.identity === versions.memoryAbiV2 ? memoryAbiV2 : memoryAbi;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function planArtifactSha256(plan) {
|
|
48
|
+
return plan.version === versions.guestUnitPlanV2
|
|
49
|
+
? plan.unit.artifact.sha256
|
|
50
|
+
: plan.unit.artifactSha256;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveManifestContained(packageRoot, manifestSourceFile, relative, field, expectedKind = 'file') {
|
|
54
|
+
if (typeof relative !== 'string' || !relative || relative.includes('\\') || path.isAbsolute(relative)) {
|
|
55
|
+
fail(diagnosticCodes.invalid, `${field} must be a normalized manifest-relative path.`);
|
|
56
|
+
}
|
|
57
|
+
const candidate = path.resolve(path.dirname(manifestSourceFile), relative);
|
|
58
|
+
if (!inside(packageRoot, candidate) || !fs.existsSync(candidate)) {
|
|
59
|
+
fail(diagnosticCodes.invalid, `${field} does not resolve to an existing contained ${expectedKind}.`);
|
|
60
|
+
}
|
|
61
|
+
const real = fs.realpathSync(candidate);
|
|
62
|
+
if (!inside(packageRoot, real)) fail(diagnosticCodes.invalid, `${field} escapes its package root.`);
|
|
63
|
+
const stat = fs.statSync(real);
|
|
64
|
+
if ((expectedKind === 'file' && !stat.isFile()) || (expectedKind === 'directory' && !stat.isDirectory())) {
|
|
65
|
+
fail(diagnosticCodes.invalid, `${field} must resolve to a ${expectedKind}.`);
|
|
66
|
+
}
|
|
67
|
+
return real;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveManifestAsset(packageRoot, manifestSourceFile, manifest, relative, field, expectedKind = 'file') {
|
|
71
|
+
if (manifest.version === versions.guestUnitV2) {
|
|
72
|
+
return resolveManifestContained(packageRoot, manifestSourceFile, relative, field, expectedKind);
|
|
73
|
+
}
|
|
74
|
+
return resolveExistingContained(packageRoot, relative, field, expectedKind);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function assertGuestBinary(manifest, inspection) {
|
|
78
|
+
const contract = memoryContract(manifest);
|
|
79
|
+
compare(
|
|
80
|
+
inspection.imports.map(surfaceImport),
|
|
81
|
+
manifest.imports,
|
|
82
|
+
diagnosticCodes.importMismatch,
|
|
83
|
+
`Guest unit ${manifest.id} binary imports do not match its manifest.`
|
|
84
|
+
);
|
|
85
|
+
compare(
|
|
86
|
+
inspection.exports.map(surfaceExport),
|
|
87
|
+
normalizedManifestExports(manifest),
|
|
88
|
+
diagnosticCodes.exportMismatch,
|
|
89
|
+
`Guest unit ${manifest.id} binary exports do not match its manifest.`
|
|
90
|
+
);
|
|
91
|
+
if (
|
|
92
|
+
inspection.memories !== 1
|
|
93
|
+
|| inspection.importedMemories !== 1
|
|
94
|
+
|| inspection.definedMemories !== 0
|
|
95
|
+
|| JSON.stringify(inspection.memoryTypes) !== JSON.stringify([{
|
|
96
|
+
minimumPages: contract.minimumPages,
|
|
97
|
+
maximumPages: contract.maximumPages,
|
|
98
|
+
shared: false
|
|
99
|
+
}])
|
|
100
|
+
) {
|
|
101
|
+
fail(diagnosticCodes.memoryMismatch, `Guest unit ${manifest.id} binary violates its memory contract.`);
|
|
102
|
+
}
|
|
103
|
+
if (inspection.hasStart) fail(diagnosticCodes.startMismatch, `Guest unit ${manifest.id} binary has a forbidden start section.`);
|
|
104
|
+
if (inspection.features.length !== 0) fail(diagnosticCodes.featureMismatch, `Guest unit ${manifest.id} binary uses unexpected features.`);
|
|
105
|
+
if (
|
|
106
|
+
inspection.tables !== 0
|
|
107
|
+
|| inspection.instructions.memoryGrow !== 0
|
|
108
|
+
|| inspection.instructions.memoryCopy !== 0
|
|
109
|
+
|| inspection.instructions.memoryFill !== 0
|
|
110
|
+
|| inspection.instructions.memoryInit !== 0
|
|
111
|
+
|| inspection.instructions.callIndirect !== 0
|
|
112
|
+
) {
|
|
113
|
+
fail(diagnosticCodes.memoryMismatch, `Guest unit ${manifest.id} binary can mutate, grow, retain, or indirectly dispatch borrowed memory.`);
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
manifest.version === versions.guestUnit
|
|
117
|
+
&& (inspection.instructions.globalSet !== 0 || inspection.instructions.store !== 0)
|
|
118
|
+
) {
|
|
119
|
+
fail(diagnosticCodes.memoryMismatch, `Guest unit ${manifest.id} binary can mutate borrowed-span memory.`);
|
|
120
|
+
}
|
|
121
|
+
if (manifest.version === versions.guestUnitV2) {
|
|
122
|
+
if (
|
|
123
|
+
inspection.globals !== 1
|
|
124
|
+
|| inspection.mutableGlobals.length !== 1
|
|
125
|
+
|| JSON.stringify(inspection.mutableGlobals[0]) !== JSON.stringify({
|
|
126
|
+
name: 'global$0',
|
|
127
|
+
type: 'i32',
|
|
128
|
+
initializer: { instruction: 'i32.const', value: contract.layout.rustStack.endExclusive }
|
|
129
|
+
})
|
|
130
|
+
|| inspection.instructions.globalSet !== 64
|
|
131
|
+
|| inspection.instructions.store !== 500
|
|
132
|
+
) {
|
|
133
|
+
fail(
|
|
134
|
+
diagnosticCodes.memoryMismatch,
|
|
135
|
+
`Guest unit ${manifest.id} binary does not match the reviewed stack-only mutable-state posture.`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const segment of inspection.dataSegments) {
|
|
140
|
+
if (
|
|
141
|
+
segment.memoryIndex !== 0
|
|
142
|
+
|| segment.offset < contract.layout.rustStatic.start
|
|
143
|
+
|| segment.endExclusive > contract.layout.rustStatic.endExclusive
|
|
144
|
+
) {
|
|
145
|
+
fail(diagnosticCodes.memoryMismatch, `Guest unit ${manifest.id} static data escapes its reserved region.`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (
|
|
149
|
+
manifest.version === versions.guestUnitV2
|
|
150
|
+
&& JSON.stringify(inspection.dataSegments.map((entry) => ({
|
|
151
|
+
memoryIndex: entry.memoryIndex,
|
|
152
|
+
offset: entry.offset,
|
|
153
|
+
endExclusive: entry.endExclusive,
|
|
154
|
+
bytes: entry.bytes,
|
|
155
|
+
sha256: entry.sha256
|
|
156
|
+
}))) !== JSON.stringify([{
|
|
157
|
+
memoryIndex: 0,
|
|
158
|
+
offset: 131072,
|
|
159
|
+
endExclusive: 131656,
|
|
160
|
+
bytes: 584,
|
|
161
|
+
sha256: '231fcd9a2fc4e8190c8b96c9f89708b8039ea1b801cc564e32fbc14ff44e53ee'
|
|
162
|
+
}])
|
|
163
|
+
) {
|
|
164
|
+
fail(diagnosticCodes.memoryMismatch, `Guest unit ${manifest.id} static data does not match the reviewed layout.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function synchronizedVersion(packages, name) {
|
|
169
|
+
if (!Array.isArray(packages)) fail(diagnosticCodes.ownerMismatch, 'Guest trust requires the synchronized package catalog.');
|
|
170
|
+
const matches = packages.filter((entry) => entry && entry.name === name);
|
|
171
|
+
if (matches.length !== 1) fail(diagnosticCodes.ownerMismatch, `Guest owner ${name} is not uniquely synchronized.`);
|
|
172
|
+
return matches[0].version;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function verifyPackageOwner(packageRoot, manifestSourceFile, manifest, synchronizedPackages) {
|
|
176
|
+
const packageManifestFile = resolveManifestAsset(
|
|
177
|
+
packageRoot,
|
|
178
|
+
manifestSourceFile,
|
|
179
|
+
manifest,
|
|
180
|
+
manifest.provenance.packageManifest,
|
|
181
|
+
'guest provenance package manifest'
|
|
182
|
+
);
|
|
183
|
+
const lockfileFile = resolveManifestAsset(
|
|
184
|
+
packageRoot,
|
|
185
|
+
manifestSourceFile,
|
|
186
|
+
manifest,
|
|
187
|
+
manifest.provenance.lockfile,
|
|
188
|
+
'guest provenance lockfile'
|
|
189
|
+
);
|
|
190
|
+
let packageManifest;
|
|
191
|
+
try {
|
|
192
|
+
packageManifest = JSON.parse(fs.readFileSync(packageManifestFile, 'utf8'));
|
|
193
|
+
} catch (error) {
|
|
194
|
+
wrap(error, diagnosticCodes.ownerMismatch, 'Guest owner package manifest is invalid.');
|
|
195
|
+
}
|
|
196
|
+
const synchronized = synchronizedVersion(synchronizedPackages, manifest.owner);
|
|
197
|
+
if (
|
|
198
|
+
packageManifest.name !== manifest.owner
|
|
199
|
+
|| packageManifest.version !== manifest.packageVersion
|
|
200
|
+
|| synchronized !== manifest.packageVersion
|
|
201
|
+
) {
|
|
202
|
+
fail(diagnosticCodes.ownerMismatch, `Guest unit ${manifest.id} owner or version does not match synchronized package identity.`, {
|
|
203
|
+
owner: manifest.owner,
|
|
204
|
+
packageVersion: manifest.packageVersion,
|
|
205
|
+
observedOwner: packageManifest.name,
|
|
206
|
+
observedVersion: packageManifest.version,
|
|
207
|
+
synchronizedVersion: synchronized
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const lockfileRecord = fileRecord(lockfileFile);
|
|
211
|
+
if (
|
|
212
|
+
manifest.version === versions.guestUnitV2
|
|
213
|
+
&& lockfileRecord.sha256 !== manifest.provenance.cargoLockSha256
|
|
214
|
+
) {
|
|
215
|
+
fail(diagnosticCodes.hashMismatch, `Guest unit ${manifest.id} lockfile does not match its declared identity.`);
|
|
216
|
+
}
|
|
217
|
+
let buildScriptFile;
|
|
218
|
+
let buildScriptRecord;
|
|
219
|
+
if (manifest.version === versions.guestUnitV2) {
|
|
220
|
+
buildScriptFile = resolveManifestAsset(
|
|
221
|
+
packageRoot,
|
|
222
|
+
manifestSourceFile,
|
|
223
|
+
manifest,
|
|
224
|
+
manifest.provenance.buildScript,
|
|
225
|
+
'guest provenance maintainer build script'
|
|
226
|
+
);
|
|
227
|
+
buildScriptRecord = fileRecord(buildScriptFile);
|
|
228
|
+
if (buildScriptRecord.sha256 !== manifest.provenance.buildScriptSha256) {
|
|
229
|
+
fail(
|
|
230
|
+
diagnosticCodes.hashMismatch,
|
|
231
|
+
`Guest unit ${manifest.id} maintainer build script does not match its declared identity.`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return Object.freeze({
|
|
236
|
+
packageManifestFile,
|
|
237
|
+
lockfileFile,
|
|
238
|
+
buildScriptFile,
|
|
239
|
+
packageManifestRecord: fileRecord(packageManifestFile),
|
|
240
|
+
lockfileRecord,
|
|
241
|
+
buildScriptRecord
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function verifyExistingMaterialization(directory, artifactBytes, manifestBytes, artifactName, manifestName) {
|
|
246
|
+
const entries = fs.readdirSync(directory).sort();
|
|
247
|
+
compare(entries, [manifestName, artifactName].sort(), diagnosticCodes.materializationFailed, 'Existing guest materialization contains unexpected files.');
|
|
248
|
+
const artifactFile = path.join(directory, artifactName);
|
|
249
|
+
const manifestFile = path.join(directory, manifestName);
|
|
250
|
+
for (const file of [artifactFile, manifestFile]) {
|
|
251
|
+
const stat = fs.lstatSync(file);
|
|
252
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
253
|
+
fail(diagnosticCodes.materializationFailed, 'Existing guest materialization must contain regular files.');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
compare(fs.readFileSync(artifactFile), artifactBytes, diagnosticCodes.materializationFailed, 'Existing guest artifact bytes differ from the content address.');
|
|
257
|
+
compare(fs.readFileSync(manifestFile), manifestBytes, diagnosticCodes.materializationFailed, 'Existing guest manifest bytes differ from the content address.');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function materializePackagePrebuilt(options) {
|
|
261
|
+
const plan = normalizeGuestUnitPlan(options.plan);
|
|
262
|
+
let packageRoot;
|
|
263
|
+
try {
|
|
264
|
+
packageRoot = fs.realpathSync(options.packageRoot);
|
|
265
|
+
if (!fs.statSync(packageRoot).isDirectory()) throw new Error('not a directory');
|
|
266
|
+
} catch {
|
|
267
|
+
fail(diagnosticCodes.invalid, 'Guest package root must be an existing directory.');
|
|
268
|
+
}
|
|
269
|
+
const manifestSourceFile = resolveExistingContained(packageRoot, options.manifestFile, 'guest unit manifest');
|
|
270
|
+
const manifestBytes = fs.readFileSync(manifestSourceFile);
|
|
271
|
+
const manifestRecord = fileRecord(manifestSourceFile);
|
|
272
|
+
if (manifestRecord.sha256 !== plan.unit.manifestSha256) {
|
|
273
|
+
fail(diagnosticCodes.hashMismatch, `Guest unit ${plan.unit.id} manifest hash does not match its plan.`);
|
|
274
|
+
}
|
|
275
|
+
let parsed;
|
|
276
|
+
try {
|
|
277
|
+
parsed = JSON.parse(manifestBytes.toString('utf8'));
|
|
278
|
+
} catch (error) {
|
|
279
|
+
wrap(error, diagnosticCodes.invalid, `Guest unit ${plan.unit.id} manifest is not valid JSON.`);
|
|
280
|
+
}
|
|
281
|
+
const manifest = normalizeGuestUnitManifest(parsed);
|
|
282
|
+
assertPlanMatchesManifest(plan, manifest);
|
|
283
|
+
const owner = verifyPackageOwner(
|
|
284
|
+
packageRoot,
|
|
285
|
+
manifestSourceFile,
|
|
286
|
+
manifest,
|
|
287
|
+
options.synchronizedPackages
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const artifactSourceFile = resolveManifestAsset(
|
|
291
|
+
packageRoot,
|
|
292
|
+
manifestSourceFile,
|
|
293
|
+
manifest,
|
|
294
|
+
manifest.artifact.file,
|
|
295
|
+
'guest unit artifact'
|
|
296
|
+
);
|
|
297
|
+
const artifactRecord = fileRecord(artifactSourceFile);
|
|
298
|
+
if (
|
|
299
|
+
artifactRecord.bytes !== manifest.artifact.bytes
|
|
300
|
+
|| artifactRecord.sha256 !== manifest.artifact.sha256
|
|
301
|
+
|| artifactRecord.sha256 !== planArtifactSha256(plan)
|
|
302
|
+
) {
|
|
303
|
+
fail(diagnosticCodes.hashMismatch, `Guest unit ${manifest.id} artifact bytes do not match their declared identity.`, {
|
|
304
|
+
expectedBytes: manifest.artifact.bytes,
|
|
305
|
+
expectedSha256: manifest.artifact.sha256,
|
|
306
|
+
observedBytes: artifactRecord.bytes,
|
|
307
|
+
observedSha256: artifactRecord.sha256
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let sourceRecord = Object.freeze({ included: false, treeSha256: null, files: 0, bytes: 0 });
|
|
312
|
+
if (manifest.source) {
|
|
313
|
+
const sourceDirectory = resolveManifestAsset(
|
|
314
|
+
packageRoot,
|
|
315
|
+
manifestSourceFile,
|
|
316
|
+
manifest,
|
|
317
|
+
manifest.source.directory,
|
|
318
|
+
'guest unit source',
|
|
319
|
+
'directory'
|
|
320
|
+
);
|
|
321
|
+
if (!inside(sourceDirectory, owner.lockfileFile)) {
|
|
322
|
+
fail(diagnosticCodes.invalid, `Guest unit ${manifest.id} lockfile must be included in its source provenance directory.`);
|
|
323
|
+
}
|
|
324
|
+
const tree = sourceTreeRecord(sourceDirectory);
|
|
325
|
+
if (
|
|
326
|
+
tree.sha256 !== manifest.source.treeSha256
|
|
327
|
+
|| tree.sha256 !== plan.unit.source.treeSha256
|
|
328
|
+
|| (
|
|
329
|
+
manifest.version === versions.guestUnitV2
|
|
330
|
+
&& tree.sha256 !== manifest.provenance.sourceTreeSha256
|
|
331
|
+
)
|
|
332
|
+
) {
|
|
333
|
+
fail(diagnosticCodes.hashMismatch, `Guest unit ${manifest.id} source tree does not match its declared identity.`);
|
|
334
|
+
}
|
|
335
|
+
sourceRecord = Object.freeze({
|
|
336
|
+
included: true,
|
|
337
|
+
treeSha256: tree.sha256,
|
|
338
|
+
algorithm: tree.algorithm,
|
|
339
|
+
files: tree.files.length,
|
|
340
|
+
bytes: tree.bytes
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const inspection = inspectWasmFile(artifactSourceFile, {
|
|
345
|
+
label: `${manifest.id}-packaged`,
|
|
346
|
+
workDirectory: options.workDirectory
|
|
347
|
+
});
|
|
348
|
+
assertGuestBinary(manifest, inspection);
|
|
349
|
+
|
|
350
|
+
let projectRoot;
|
|
351
|
+
try {
|
|
352
|
+
projectRoot = fs.realpathSync(options.projectRoot);
|
|
353
|
+
if (!fs.statSync(projectRoot).isDirectory()) throw new Error('not a directory');
|
|
354
|
+
} catch {
|
|
355
|
+
fail(diagnosticCodes.materializationFailed, 'Guest project root must be an existing directory.');
|
|
356
|
+
}
|
|
357
|
+
const parentRelative = path.posix.dirname(plan.materialization.directory);
|
|
358
|
+
const parent = ensureContainedDirectory(projectRoot, parentRelative);
|
|
359
|
+
const destination = path.join(parent, path.posix.basename(plan.materialization.directory));
|
|
360
|
+
if (!inside(projectRoot, destination)) fail(diagnosticCodes.materializationFailed, 'Guest materialization destination escapes the project root.');
|
|
361
|
+
const artifactBytes = fs.readFileSync(artifactSourceFile);
|
|
362
|
+
let reused = false;
|
|
363
|
+
if (fs.existsSync(destination)) {
|
|
364
|
+
const realDestination = fs.realpathSync(destination);
|
|
365
|
+
if (!inside(projectRoot, realDestination) || !fs.statSync(realDestination).isDirectory()) {
|
|
366
|
+
fail(diagnosticCodes.materializationFailed, 'Existing guest materialization is not a contained directory.');
|
|
367
|
+
}
|
|
368
|
+
verifyExistingMaterialization(
|
|
369
|
+
realDestination,
|
|
370
|
+
artifactBytes,
|
|
371
|
+
manifestBytes,
|
|
372
|
+
plan.materialization.artifact,
|
|
373
|
+
plan.materialization.manifest
|
|
374
|
+
);
|
|
375
|
+
reused = true;
|
|
376
|
+
} else {
|
|
377
|
+
const staging = fs.mkdtempSync(path.join(parent, `.${path.basename(destination)}.staging-`));
|
|
378
|
+
try {
|
|
379
|
+
fs.writeFileSync(path.join(staging, plan.materialization.artifact), artifactBytes);
|
|
380
|
+
fs.writeFileSync(path.join(staging, plan.materialization.manifest), manifestBytes);
|
|
381
|
+
fs.renameSync(staging, destination);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (fs.existsSync(staging)) fs.rmSync(staging, { recursive: true, force: true });
|
|
384
|
+
if (error && error.code === 'EEXIST' && fs.existsSync(destination)) {
|
|
385
|
+
verifyExistingMaterialization(
|
|
386
|
+
destination,
|
|
387
|
+
artifactBytes,
|
|
388
|
+
manifestBytes,
|
|
389
|
+
plan.materialization.artifact,
|
|
390
|
+
plan.materialization.manifest
|
|
391
|
+
);
|
|
392
|
+
reused = true;
|
|
393
|
+
} else {
|
|
394
|
+
wrap(error, diagnosticCodes.materializationFailed, `Guest unit ${manifest.id} could not be materialized.`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return Object.freeze({
|
|
400
|
+
plan,
|
|
401
|
+
manifest,
|
|
402
|
+
manifestRecord,
|
|
403
|
+
artifactRecord,
|
|
404
|
+
sourceRecord,
|
|
405
|
+
provenance: Object.freeze({
|
|
406
|
+
packageManifest: owner.packageManifestRecord,
|
|
407
|
+
lockfile: owner.lockfileRecord,
|
|
408
|
+
...(manifest.version === versions.guestUnitV2
|
|
409
|
+
? { buildScript: owner.buildScriptRecord }
|
|
410
|
+
: {}),
|
|
411
|
+
source: sourceRecord
|
|
412
|
+
}),
|
|
413
|
+
inspection,
|
|
414
|
+
artifactFile: path.join(destination, plan.materialization.artifact),
|
|
415
|
+
manifestFile: path.join(destination, plan.materialization.manifest),
|
|
416
|
+
relativeDirectory: plan.materialization.directory,
|
|
417
|
+
reused
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
module.exports = Object.freeze({
|
|
422
|
+
assertGuestBinary,
|
|
423
|
+
materializePackagePrebuilt
|
|
424
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { fileRecord, writeAtomic } = require('./files.js');
|
|
6
|
+
|
|
7
|
+
// MVP core Wasm containing exactly `(memory 32 32)` exported as `memory`.
|
|
8
|
+
// Keeping these audited bytes in the link package avoids a shell/tool hook in
|
|
9
|
+
// the compiler and makes the memory owner independent of the contributing package.
|
|
10
|
+
const MEMORY_OWNER_BYTES = Buffer.from([
|
|
11
|
+
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
|
|
12
|
+
0x05, 0x04, 0x01, 0x01, 0x20, 0x20,
|
|
13
|
+
0x07, 0x0a, 0x01, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
function writeMemoryOwnerModule(directory) {
|
|
17
|
+
const target = path.resolve(directory);
|
|
18
|
+
fs.mkdirSync(target, { recursive: true });
|
|
19
|
+
const file = path.join(target, 'guest-memory-owner.wasm');
|
|
20
|
+
if (!fs.existsSync(file) || !fs.readFileSync(file).equals(MEMORY_OWNER_BYTES)) {
|
|
21
|
+
writeAtomic(file, MEMORY_OWNER_BYTES);
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze({ file, ...fileRecord(file) });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = Object.freeze({
|
|
27
|
+
MEMORY_OWNER_BYTES,
|
|
28
|
+
writeMemoryOwnerModule
|
|
29
|
+
});
|