@pulse-compute/wasm-guest-link 0.0.0 → 1.0.0-beta.2

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/src/stage.js ADDED
@@ -0,0 +1,578 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+
8
+ function loadFinalWasmPolicy() {
9
+ try {
10
+ return require('@pulse-compute/wasm-contracts/provider/final-wasm-policy');
11
+ } catch (error) {
12
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) {
13
+ return require('../../contracts/src/provider/final-wasm-policy.js');
14
+ }
15
+ throw error;
16
+ }
17
+ }
18
+
19
+ function loadPackageContract() {
20
+ try {
21
+ return require('@pulse-compute/wasm-contracts/package/package-contract');
22
+ } catch (error) {
23
+ if (error && ['MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(error.code)) {
24
+ return require('../../contracts/src/package/package-contract.js');
25
+ }
26
+ throw error;
27
+ }
28
+ }
29
+
30
+ const {
31
+ defineFinalWasmPolicy,
32
+ projectFinalWasmPolicy
33
+ } = loadFinalWasmPolicy();
34
+ const {
35
+ GUEST_UNIT_CONTRIBUTION_FIELDS,
36
+ normalizeCanonicalGuestUnitContribution
37
+ } = loadPackageContract();
38
+ const guestLink = require('./index.js');
39
+ const { fail, wrap } = require('./errors.js');
40
+
41
+ const GUEST_LINK_STAGE_INVOCATION_VERSION = 'pulse.guest-link-stage-invocation.v1';
42
+ const GUEST_LINK_STAGE_RESULT_VERSION = 'pulse.guest-link-stage-result.v1';
43
+ const GUEST_LINK_STAGE_INVOCATION_FIELDS = Object.freeze([
44
+ 'version',
45
+ 'primaryWasm',
46
+ 'guestUnits',
47
+ 'projectRoot',
48
+ 'profile',
49
+ 'finalWasmPolicy',
50
+ 'synchronizedPackages',
51
+ 'optimizationPosture'
52
+ ]);
53
+ const GUEST_LINK_STAGE_RESULT_FIELDS = Object.freeze([
54
+ 'version',
55
+ 'wasm',
56
+ 'wat',
57
+ 'guestUnits',
58
+ 'plan',
59
+ 'report',
60
+ 'audit',
61
+ 'materialization',
62
+ 'finalArtifact',
63
+ 'providerPackaging',
64
+ 'fallback'
65
+ ]);
66
+ const FINAL_WASM_POLICY_FIELDS = Object.freeze([
67
+ 'version',
68
+ 'descriptorOwner',
69
+ 'toolchainVersion',
70
+ 'descriptorIdentity',
71
+ 'permittedImports',
72
+ 'requiredExports',
73
+ 'featureBaseline',
74
+ 'allowedFeatures',
75
+ 'memory',
76
+ 'start',
77
+ 'descriptorSha256'
78
+ ]);
79
+
80
+ function sha256(value) {
81
+ return crypto.createHash('sha256').update(value).digest('hex');
82
+ }
83
+
84
+ function stable(value) {
85
+ if (Array.isArray(value)) return value.map(stable);
86
+ if (!value || typeof value !== 'object') return value;
87
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])]));
88
+ }
89
+
90
+ function plainObject(value) {
91
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
92
+ const prototype = Object.getPrototypeOf(value);
93
+ return prototype === Object.prototype || prototype === null;
94
+ }
95
+
96
+ function exactKeys(value, fields, label, code = guestLink.diagnosticCodes.invalid) {
97
+ if (!plainObject(value)) fail(code, `${label} must be an object.`);
98
+ const unknown = Object.keys(value).filter((key) => !fields.includes(key)).sort();
99
+ const missing = fields.filter((key) => !Object.hasOwn(value, key));
100
+ if (unknown.length > 0 || missing.length > 0) {
101
+ fail(code, `${label} does not match its exact versioned field set.`, { unknown, missing });
102
+ }
103
+ for (const key of Object.keys(value)) {
104
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
105
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) {
106
+ fail(code, `${label}.${key} must be an enumerable data property.`);
107
+ }
108
+ }
109
+ }
110
+
111
+ function string(value, label) {
112
+ if (typeof value !== 'string' || !value.trim() || value !== value.trim()) {
113
+ fail(guestLink.diagnosticCodes.invalid, `${label} must be a normalized non-empty string.`);
114
+ }
115
+ return value;
116
+ }
117
+
118
+ function exactSemver(value, label) {
119
+ const normalized = string(value, label);
120
+ if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(normalized)) {
121
+ fail(guestLink.diagnosticCodes.ownerMismatch, `${label} must be an exact semantic version.`);
122
+ }
123
+ return normalized;
124
+ }
125
+
126
+ function normalizeContribution(value) {
127
+ try {
128
+ return normalizeCanonicalGuestUnitContribution(value);
129
+ } catch (error) {
130
+ wrap(error, guestLink.diagnosticCodes.invalid, 'Guest-link requires one exact canonical guest-unit contribution.');
131
+ }
132
+ }
133
+
134
+ function normalizeFinalWasmPolicy(value) {
135
+ exactKeys(value, FINAL_WASM_POLICY_FIELDS, 'Guest-link final-Wasm policy', 'PULSE_GUEST_TARGET_POLICY_REQUIRED');
136
+ let normalized;
137
+ try {
138
+ normalized = defineFinalWasmPolicy({
139
+ version: value.version,
140
+ descriptorOwner: value.descriptorOwner,
141
+ toolchainVersion: value.toolchainVersion,
142
+ descriptorIdentity: value.descriptorIdentity,
143
+ permittedImports: value.permittedImports,
144
+ requiredExports: value.requiredExports
145
+ });
146
+ } catch (error) {
147
+ wrap(error, 'PULSE_GUEST_TARGET_POLICY_REQUIRED', 'Guest-link requires a valid selected-target final-Wasm policy.');
148
+ }
149
+ if (JSON.stringify(stable(value)) !== JSON.stringify(stable(normalized))) {
150
+ fail('PULSE_GUEST_TARGET_POLICY_REQUIRED', 'Guest-link final-Wasm policy does not match its normalized descriptor identity.');
151
+ }
152
+ return normalized;
153
+ }
154
+
155
+ function normalizeSynchronizedPackages(values) {
156
+ if (!Array.isArray(values)) {
157
+ fail(guestLink.diagnosticCodes.ownerMismatch, 'Guest-link requires an exact synchronized package catalog.');
158
+ }
159
+ const names = new Set();
160
+ return Object.freeze(values.map((entry, index) => {
161
+ exactKeys(entry, ['name', 'version'], `Guest-link synchronizedPackages[${index}]`, guestLink.diagnosticCodes.ownerMismatch);
162
+ const name = string(entry.name, `Guest-link synchronizedPackages[${index}].name`);
163
+ if (names.has(name)) {
164
+ fail(guestLink.diagnosticCodes.ownerMismatch, `Guest-link synchronized package ${name} is duplicated.`);
165
+ }
166
+ names.add(name);
167
+ return Object.freeze({
168
+ name,
169
+ version: exactSemver(entry.version, `Guest-link synchronizedPackages[${index}].version`)
170
+ });
171
+ }).sort((left, right) => left.name.localeCompare(right.name)));
172
+ }
173
+
174
+ function normalizeStageSelection(value, index) {
175
+ exactKeys(value, ['contribution', 'packageRoot'], `Guest-link guestUnits[${index}]`);
176
+ return Object.freeze({
177
+ contribution: normalizeContribution(value.contribution),
178
+ packageRoot: string(value.packageRoot, `Guest-link guestUnits[${index}].packageRoot`)
179
+ });
180
+ }
181
+
182
+ function normalizeGuestLinkStageInvocation(input) {
183
+ exactKeys(input, GUEST_LINK_STAGE_INVOCATION_FIELDS, 'Guest-link stage invocation');
184
+ if (input.version !== GUEST_LINK_STAGE_INVOCATION_VERSION) {
185
+ fail(
186
+ guestLink.diagnosticCodes.invalid,
187
+ `Guest-link stage invocation must use ${GUEST_LINK_STAGE_INVOCATION_VERSION}.`
188
+ );
189
+ }
190
+ if (!Array.isArray(input.guestUnits)) {
191
+ fail(guestLink.diagnosticCodes.invalid, 'Guest-link stage invocation.guestUnits must be an array.');
192
+ }
193
+ if (input.guestUnits.length !== 1) {
194
+ fail(
195
+ 'PULSE_GUEST_UNIT_CARDINALITY_UNSUPPORTED',
196
+ 'The initial guest-unit composition contract accepts exactly one selected unit.',
197
+ {
198
+ selected: input.guestUnits.map((entry) => entry && entry.contribution && entry.contribution.id).filter(Boolean).sort(),
199
+ automaticFallback: false
200
+ }
201
+ );
202
+ }
203
+ if (!Buffer.isBuffer(input.primaryWasm)) {
204
+ fail(guestLink.diagnosticCodes.invalid, 'Guest-link stage invocation.primaryWasm must be exact primary Wasm bytes.');
205
+ }
206
+ const optimizationPosture = string(
207
+ input.optimizationPosture,
208
+ 'Guest-link stage invocation.optimizationPosture'
209
+ );
210
+ if (!['native-default', 'native-size'].includes(optimizationPosture)) {
211
+ fail(
212
+ guestLink.diagnosticCodes.optimizationFailed,
213
+ `Unsupported guest-link optimization posture ${optimizationPosture}.`
214
+ );
215
+ }
216
+ return Object.freeze({
217
+ version: GUEST_LINK_STAGE_INVOCATION_VERSION,
218
+ primaryWasm: Buffer.from(input.primaryWasm),
219
+ guestUnits: Object.freeze(input.guestUnits.map(normalizeStageSelection)),
220
+ projectRoot: string(input.projectRoot, 'Guest-link stage invocation.projectRoot'),
221
+ profile: string(input.profile, 'Guest-link stage invocation.profile'),
222
+ finalWasmPolicy: normalizeFinalWasmPolicy(input.finalWasmPolicy),
223
+ synchronizedPackages: normalizeSynchronizedPackages(input.synchronizedPackages),
224
+ optimizationPosture
225
+ });
226
+ }
227
+
228
+ function inside(root, candidate) {
229
+ const relative = path.relative(root, candidate);
230
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
231
+ }
232
+
233
+ function readSelectedManifest(selection) {
234
+ const contribution = selection.contribution;
235
+ let packageRoot;
236
+ try {
237
+ packageRoot = fs.realpathSync(selection.packageRoot);
238
+ } catch (error) {
239
+ wrap(error, guestLink.diagnosticCodes.invalid, 'Guest-unit package root is unavailable.');
240
+ }
241
+ const relativeManifest = contribution.manifest.replace(/^\.\//, '');
242
+ const manifestFile = path.resolve(packageRoot, relativeManifest);
243
+ if (!inside(packageRoot, manifestFile) || !fs.existsSync(manifestFile)) {
244
+ fail(
245
+ guestLink.diagnosticCodes.invalid,
246
+ `Guest-unit manifest ${contribution.manifest} is not contained by ${contribution.owner}.`
247
+ );
248
+ }
249
+ const realManifest = fs.realpathSync(manifestFile);
250
+ if (!inside(packageRoot, realManifest) || !fs.statSync(realManifest).isFile()) {
251
+ fail(
252
+ guestLink.diagnosticCodes.invalid,
253
+ `Guest-unit manifest ${contribution.manifest} is not a contained regular file.`
254
+ );
255
+ }
256
+ const bytes = fs.readFileSync(realManifest);
257
+ let manifest;
258
+ try {
259
+ manifest = guestLink.normalizeGuestUnitManifest(JSON.parse(bytes.toString('utf8')));
260
+ } catch (error) {
261
+ wrap(error, guestLink.diagnosticCodes.invalid, `Guest-unit manifest ${contribution.manifest} is invalid.`);
262
+ }
263
+ if (
264
+ manifest.id !== contribution.id
265
+ || manifest.owner !== contribution.owner
266
+ || manifest.packageVersion !== contribution.packageVersion
267
+ || manifest.origin !== contribution.origin
268
+ ) {
269
+ fail(
270
+ guestLink.diagnosticCodes.ownerMismatch,
271
+ `Guest-unit contribution ${contribution.id} does not match its package-owned manifest.`
272
+ );
273
+ }
274
+ return Object.freeze({
275
+ packageRoot,
276
+ manifestFile: relativeManifest,
277
+ manifest,
278
+ manifestSha256: sha256(bytes)
279
+ });
280
+ }
281
+
282
+ function hostImports(primaryBytes, guestModule) {
283
+ let module;
284
+ try {
285
+ module = new WebAssembly.Module(primaryBytes);
286
+ } catch (error) {
287
+ wrap(error, guestLink.diagnosticCodes.invalid, 'Guest-link primary input is not valid core Wasm.');
288
+ }
289
+ return WebAssembly.Module.imports(module)
290
+ .filter((entry) => entry.module !== guestModule)
291
+ .filter((entry) => !(entry.module === 'env' && entry.name === 'memory' && entry.kind === 'memory'))
292
+ .map((entry) => Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind }));
293
+ }
294
+
295
+ function optimizationForPosture(posture) {
296
+ return posture === 'native-size'
297
+ ? Object.freeze({ mode: 'experimental-native-size', posture: 'native-size' })
298
+ : Object.freeze({ mode: 'default', posture: 'native-default' });
299
+ }
300
+
301
+ function makeGuestUnitPlan(selection, resolved, facts, primaryBytes) {
302
+ let targetPolicy;
303
+ try {
304
+ targetPolicy = projectFinalWasmPolicy(
305
+ facts.finalWasmPolicy,
306
+ hostImports(primaryBytes, resolved.manifest.module)
307
+ );
308
+ } catch (error) {
309
+ wrap(
310
+ error,
311
+ 'PULSE_GUEST_TARGET_POLICY_REQUIRED',
312
+ 'Guest-link primary imports exceed the selected target final-Wasm policy.'
313
+ );
314
+ }
315
+ const manifest = resolved.manifest;
316
+ const isV2 = manifest.version === guestLink.versions.guestUnitV2;
317
+ const optimization = optimizationForPosture(facts.optimizationPosture);
318
+ const input = {
319
+ version: isV2 ? guestLink.versions.guestUnitPlanV2 : guestLink.versions.guestUnitPlan,
320
+ profile: facts.profile,
321
+ target: targetPolicy.descriptorIdentity,
322
+ unit: isV2 ? {
323
+ manifestVersion: manifest.version,
324
+ id: manifest.id,
325
+ module: manifest.module,
326
+ owner: manifest.owner,
327
+ packageVersion: manifest.packageVersion,
328
+ abi: manifest.abi,
329
+ origin: manifest.origin,
330
+ manifestSha256: resolved.manifestSha256,
331
+ artifact: {
332
+ bytes: manifest.artifact.bytes,
333
+ sha256: manifest.artifact.sha256
334
+ },
335
+ source: {
336
+ included: true,
337
+ treeSha256: manifest.source.treeSha256
338
+ },
339
+ memoryIdentity: manifest.memory.identity,
340
+ frame: {
341
+ identity: guestLink.es256FrameV2.identity,
342
+ capacityBytes: guestLink.es256FrameV2.capacityBytes,
343
+ alignmentBytes: guestLink.es256FrameV2.alignmentBytes,
344
+ pointerWidthBits: guestLink.es256FrameV2.pointerWidthBits
345
+ },
346
+ toolchain: {
347
+ binaryenVersion: manifest.toolchain.versions.binaryen,
348
+ wasmOptSha256: manifest.provenance.binaryenWasmOptSha256
349
+ }
350
+ } : {
351
+ id: manifest.id,
352
+ module: manifest.module,
353
+ owner: manifest.owner,
354
+ packageVersion: manifest.packageVersion,
355
+ abi: manifest.abi,
356
+ origin: manifest.origin,
357
+ manifestSha256: resolved.manifestSha256,
358
+ artifactSha256: manifest.artifact.sha256,
359
+ source: {
360
+ included: Boolean(manifest.source),
361
+ treeSha256: manifest.source ? manifest.source.treeSha256 : null
362
+ }
363
+ },
364
+ targetPolicy,
365
+ materialization: {
366
+ workspace: '.pulse/guests',
367
+ directory: `.pulse/guests/${manifest.id}/${manifest.artifact.sha256}`,
368
+ artifact: 'unit.wasm',
369
+ manifest: 'unit.json',
370
+ contentAddress: 'artifact-sha256',
371
+ generated: true,
372
+ authoritative: false
373
+ },
374
+ trust: isV2 ? {
375
+ lowerer: 'trusted-first-party',
376
+ catalog: 'pulse.jwt-crypto-working-candidate.v1',
377
+ catalogStatus: 'unpublished-synchronized',
378
+ ownerMatchesContributionPackage: true,
379
+ installedVersionMatches: true,
380
+ status: 'passed'
381
+ } : {
382
+ lowerer: 'trusted-first-party',
383
+ releaseManifest: 'synchronized',
384
+ ownerMatchesContributionPackage: true,
385
+ installedVersionMatches: true,
386
+ status: 'passed'
387
+ },
388
+ composition: {
389
+ kind: 'core-wasm-static-link',
390
+ semanticContract: 'connect-validated-core-units-into-one-final-audited-native-artifact',
391
+ primaryModule: 'pulse_primary',
392
+ memoryOwnerModule: 'env'
393
+ },
394
+ fallback: { enabled: false, onFailure: 'stop-before-provider-packaging' }
395
+ };
396
+ if (isV2) {
397
+ input.optimization = {
398
+ mode: optimization.mode,
399
+ postLink: true,
400
+ posture: optimization.posture
401
+ };
402
+ }
403
+ return guestLink.normalizeGuestUnitPlan(input);
404
+ }
405
+
406
+ function normalizeGuestLinkStageResult(input) {
407
+ exactKeys(input, GUEST_LINK_STAGE_RESULT_FIELDS, 'Guest-link stage result');
408
+ if (input.version !== GUEST_LINK_STAGE_RESULT_VERSION) {
409
+ fail(
410
+ guestLink.diagnosticCodes.finalAuditFailed,
411
+ `Guest-link stage result must use ${GUEST_LINK_STAGE_RESULT_VERSION}.`
412
+ );
413
+ }
414
+ if (!Buffer.isBuffer(input.wasm) || typeof input.wat !== 'string') {
415
+ fail(guestLink.diagnosticCodes.finalAuditFailed, 'Guest-link stage result must contain exact final Wasm and text bytes.');
416
+ }
417
+ if (!Array.isArray(input.guestUnits) || input.guestUnits.length !== 1) {
418
+ fail(guestLink.diagnosticCodes.finalAuditFailed, 'Guest-link stage result must identify exactly one normalized contribution.');
419
+ }
420
+ const guestUnits = Object.freeze(input.guestUnits.map(normalizeContribution));
421
+ const plan = guestLink.normalizeGuestUnitPlan(input.plan);
422
+ const expectedReportVersion = plan.version === guestLink.versions.guestUnitPlanV2
423
+ ? guestLink.versions.guestLinkReportV2
424
+ : guestLink.versions.guestLinkReport;
425
+ const expectedAuditVersion = plan.version === guestLink.versions.guestUnitPlanV2
426
+ ? guestLink.versions.finalWasmAuditV2
427
+ : guestLink.versions.finalWasmAudit;
428
+ if (!plainObject(input.report) || input.report.version !== expectedReportVersion || input.report.status !== 'passed') {
429
+ fail(guestLink.diagnosticCodes.finalAuditFailed, 'Guest-link stage result report identity is invalid.');
430
+ }
431
+ if (!plainObject(input.audit) || input.audit.version !== expectedAuditVersion || input.audit.status !== 'passed') {
432
+ fail(guestLink.diagnosticCodes.finalAuditFailed, 'Guest-link stage result audit identity is invalid.');
433
+ }
434
+ exactKeys(input.finalArtifact, ['bytes', 'sha256'], 'Guest-link stage result.finalArtifact', guestLink.diagnosticCodes.finalAuditFailed);
435
+ const observedArtifact = Object.freeze({ bytes: input.wasm.length, sha256: sha256(input.wasm) });
436
+ if (
437
+ input.finalArtifact.bytes !== observedArtifact.bytes
438
+ || input.finalArtifact.sha256 !== observedArtifact.sha256
439
+ || input.report.finalArtifact.bytes !== observedArtifact.bytes
440
+ || input.report.finalArtifact.sha256 !== observedArtifact.sha256
441
+ || input.audit.artifact.bytes !== observedArtifact.bytes
442
+ || input.audit.artifact.sha256 !== observedArtifact.sha256
443
+ ) {
444
+ fail(guestLink.diagnosticCodes.finalAuditFailed, 'Guest-link stage result bytes do not match their report and audit identity.');
445
+ }
446
+ exactKeys(input.materialization, ['directory', 'reused', 'contentAddressed'], 'Guest-link stage result.materialization', guestLink.diagnosticCodes.finalAuditFailed);
447
+ if (input.materialization.contentAddressed !== true || typeof input.materialization.reused !== 'boolean') {
448
+ fail(guestLink.diagnosticCodes.finalAuditFailed, 'Guest-link stage materialization disposition is invalid.');
449
+ }
450
+ exactKeys(input.providerPackaging, [
451
+ 'authorized',
452
+ 'input',
453
+ 'artifactSha256',
454
+ 'automaticFallback'
455
+ ], 'Guest-link stage result.providerPackaging', guestLink.diagnosticCodes.finalAuditFailed);
456
+ if (
457
+ input.providerPackaging.authorized !== true
458
+ || input.providerPackaging.input !== 'exact-audited-final-bytes'
459
+ || input.providerPackaging.artifactSha256 !== observedArtifact.sha256
460
+ || input.providerPackaging.automaticFallback !== false
461
+ || input.audit.providerPackaging.authorized !== true
462
+ ) {
463
+ fail(guestLink.diagnosticCodes.finalAuditFailed, 'Guest-link stage did not authorize only exact audited final bytes.');
464
+ }
465
+ exactKeys(input.fallback, [
466
+ 'enabled',
467
+ 'used',
468
+ 'onFailure',
469
+ 'alternateOutputAuthorized'
470
+ ], 'Guest-link stage result.fallback', guestLink.diagnosticCodes.finalAuditFailed);
471
+ if (
472
+ input.fallback.enabled !== false
473
+ || input.fallback.used !== false
474
+ || input.fallback.onFailure !== 'stop-before-provider-packaging'
475
+ || input.fallback.alternateOutputAuthorized !== false
476
+ || input.report.fallback.enabled !== false
477
+ || input.report.fallback.used !== false
478
+ ) {
479
+ fail(guestLink.diagnosticCodes.finalAuditFailed, 'Guest-link stage fallback disposition is invalid.');
480
+ }
481
+ return Object.freeze({
482
+ version: GUEST_LINK_STAGE_RESULT_VERSION,
483
+ wasm: Buffer.from(input.wasm),
484
+ wat: input.wat,
485
+ guestUnits,
486
+ plan,
487
+ report: input.report,
488
+ audit: input.audit,
489
+ materialization: Object.freeze({
490
+ directory: input.materialization.directory,
491
+ reused: input.materialization.reused,
492
+ contentAddressed: true
493
+ }),
494
+ finalArtifact: observedArtifact,
495
+ providerPackaging: Object.freeze({
496
+ authorized: true,
497
+ input: 'exact-audited-final-bytes',
498
+ artifactSha256: observedArtifact.sha256,
499
+ automaticFallback: false
500
+ }),
501
+ fallback: Object.freeze({
502
+ enabled: false,
503
+ used: false,
504
+ onFailure: 'stop-before-provider-packaging',
505
+ alternateOutputAuthorized: false
506
+ })
507
+ });
508
+ }
509
+
510
+ function realizeGuestLinkStage(input) {
511
+ const invocation = normalizeGuestLinkStageInvocation(input);
512
+ const selection = invocation.guestUnits[0];
513
+ const resolved = readSelectedManifest(selection);
514
+ const plan = makeGuestUnitPlan(selection, resolved, invocation, invocation.primaryWasm);
515
+ const stageDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-guest-link-stage-'));
516
+ try {
517
+ const primaryFile = path.join(stageDirectory, 'primary.wasm');
518
+ fs.writeFileSync(primaryFile, invocation.primaryWasm);
519
+ const owner = guestLink.writeMemoryOwnerModule(stageDirectory);
520
+ const linked = guestLink.realizeGuestLinkPlan({
521
+ plan,
522
+ packageRoot: resolved.packageRoot,
523
+ manifestFile: resolved.manifestFile,
524
+ projectRoot: invocation.projectRoot,
525
+ synchronizedPackages: invocation.synchronizedPackages,
526
+ primaryFile,
527
+ memoryOwnerFile: owner.file,
528
+ outputDirectory: path.join(stageDirectory, 'output'),
529
+ optimizationPosture: plan.version === guestLink.versions.guestUnitPlanV2
530
+ ? plan.optimization.posture
531
+ : 'native-size'
532
+ });
533
+ const verifiedArtifact = guestLink.assertFinalArtifactIdentity(linked.files.final, linked.audit);
534
+ const finalBytes = fs.readFileSync(linked.files.final);
535
+ const finalWat = fs.readFileSync(linked.files.finalWat, 'utf8');
536
+ return normalizeGuestLinkStageResult({
537
+ version: GUEST_LINK_STAGE_RESULT_VERSION,
538
+ wasm: finalBytes,
539
+ wat: finalWat,
540
+ guestUnits: [selection.contribution],
541
+ plan,
542
+ report: linked.report,
543
+ audit: linked.audit,
544
+ materialization: {
545
+ directory: linked.materialization.directory,
546
+ reused: linked.materialization.reused,
547
+ contentAddressed: true
548
+ },
549
+ finalArtifact: verifiedArtifact,
550
+ providerPackaging: {
551
+ authorized: true,
552
+ input: 'exact-audited-final-bytes',
553
+ artifactSha256: verifiedArtifact.sha256,
554
+ automaticFallback: false
555
+ },
556
+ fallback: {
557
+ enabled: false,
558
+ used: false,
559
+ onFailure: 'stop-before-provider-packaging',
560
+ alternateOutputAuthorized: false
561
+ }
562
+ });
563
+ } finally {
564
+ fs.rmSync(stageDirectory, { recursive: true, force: true });
565
+ }
566
+ }
567
+
568
+ module.exports = Object.freeze({
569
+ GUEST_LINK_STAGE_INVOCATION_VERSION,
570
+ GUEST_LINK_STAGE_RESULT_VERSION,
571
+ GUEST_LINK_STAGE_INVOCATION_FIELDS,
572
+ GUEST_LINK_STAGE_RESULT_FIELDS,
573
+ normalizeGuestLinkStageInvocation,
574
+ normalizeGuestLinkStageResult,
575
+ makeGuestUnitPlan,
576
+ realizeGuestLinkStage,
577
+ GUEST_UNIT_CONTRIBUTION_FIELDS
578
+ });
@@ -0,0 +1,125 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { spawnSync } = require('node:child_process');
6
+ const { binaryenVersion, diagnosticCodes } = require('./constants.js');
7
+ const { fail } = require('./errors.js');
8
+ const { sha256 } = require('./files.js');
9
+
10
+ const allowedTools = new Set(['wasm-as', 'wasm-dis', 'wasm-merge', 'wasm-opt']);
11
+
12
+ function binaryenPackage() {
13
+ let manifestFile;
14
+ let manifest;
15
+ try {
16
+ manifestFile = require.resolve('binaryen/package.json');
17
+ manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
18
+ } catch {
19
+ fail(diagnosticCodes.invalid, 'The direct pinned Binaryen dependency is not installed or has invalid metadata.');
20
+ }
21
+ if (manifest.name !== 'binaryen' || manifest.version !== binaryenVersion) {
22
+ fail(diagnosticCodes.invalid, `Guest linking requires direct Binaryen ${binaryenVersion}.`, {
23
+ observedName: manifest.name,
24
+ observedVersion: manifest.version
25
+ });
26
+ }
27
+ return Object.freeze({
28
+ root: fs.realpathSync(path.dirname(manifestFile)),
29
+ manifestFile: fs.realpathSync(manifestFile),
30
+ version: manifest.version
31
+ });
32
+ }
33
+
34
+ function resolveTool(name) {
35
+ if (!allowedTools.has(name)) fail(diagnosticCodes.invalid, `Unknown Binaryen tool ${name}.`);
36
+ const packageInfo = binaryenPackage();
37
+ const candidate = path.join(packageInfo.root, 'bin', name);
38
+ if (!fs.existsSync(candidate) || !fs.statSync(candidate).isFile()) {
39
+ fail(diagnosticCodes.invalid, `Pinned Binaryen does not contain ${name}.`);
40
+ }
41
+ const real = fs.realpathSync(candidate);
42
+ if (!real.startsWith(`${packageInfo.root}${path.sep}`)) {
43
+ fail(diagnosticCodes.invalid, `Pinned Binaryen tool ${name} escapes its package root.`);
44
+ }
45
+ return Object.freeze({
46
+ name,
47
+ file: real,
48
+ version: packageInfo.version,
49
+ sha256: sha256(fs.readFileSync(real))
50
+ });
51
+ }
52
+
53
+ function restrictedEnvironment() {
54
+ const result = {};
55
+ for (const name of ['PATH', 'TMPDIR', 'TMP', 'TEMP', 'SystemRoot', 'WINDIR']) {
56
+ if (process.env[name]) result[name] = process.env[name];
57
+ }
58
+ return result;
59
+ }
60
+
61
+ function sanitizedOutput(value, replacements) {
62
+ let output = String(value || '');
63
+ for (const [from, to] of replacements) {
64
+ if (from) output = output.split(from).join(to);
65
+ }
66
+ return output.trim().slice(0, 4096);
67
+ }
68
+
69
+ function runTool(name, args, options = {}) {
70
+ const tool = resolveTool(name);
71
+ if (!Array.isArray(args) || args.some((entry) => typeof entry !== 'string')) {
72
+ fail(diagnosticCodes.invalid, `Arguments for ${name} must be a trusted normalized string array.`);
73
+ }
74
+ const cwd = options.cwd ? fs.realpathSync(options.cwd) : process.cwd();
75
+ const result = spawnSync(tool.file, args, {
76
+ cwd,
77
+ encoding: 'utf8',
78
+ env: restrictedEnvironment(),
79
+ timeout: options.timeoutMs || 30000,
80
+ maxBuffer: 16 * 1024 * 1024,
81
+ shell: false
82
+ });
83
+ if (result.error || result.status !== 0) {
84
+ const replacements = [
85
+ [cwd, '<work>'],
86
+ [tool.file, `<binaryen>/${name}`],
87
+ [path.dirname(tool.file), '<binaryen>'],
88
+ ...args
89
+ .filter((entry) => path.isAbsolute(entry))
90
+ .map((entry) => [entry, `<input>/${path.basename(entry)}`])
91
+ ];
92
+ fail(options.failureCode || diagnosticCodes.invalid, options.failureMessage || `${name} failed.`, {
93
+ tool: name,
94
+ version: tool.version,
95
+ status: result.status,
96
+ stdout: sanitizedOutput(result.stdout, replacements),
97
+ stderr: sanitizedOutput(result.stderr, replacements),
98
+ cause: result.error && sanitizedOutput(result.error.message, replacements)
99
+ });
100
+ }
101
+ return Object.freeze({
102
+ tool: Object.freeze({ name, version: tool.version, sha256: tool.sha256 }),
103
+ stdout: String(result.stdout || '').trim(),
104
+ stderr: String(result.stderr || '').trim()
105
+ });
106
+ }
107
+
108
+ function binaryenIdentity() {
109
+ const packageInfo = binaryenPackage();
110
+ return Object.freeze({
111
+ name: 'binaryen',
112
+ version: packageInfo.version,
113
+ tools: Object.freeze(['wasm-as', 'wasm-dis', 'wasm-merge', 'wasm-opt'].map((name) => {
114
+ const tool = resolveTool(name);
115
+ return Object.freeze({ name, sha256: tool.sha256 });
116
+ }))
117
+ });
118
+ }
119
+
120
+ module.exports = Object.freeze({
121
+ binaryenPackage,
122
+ resolveTool,
123
+ runTool,
124
+ binaryenIdentity
125
+ });