@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.
@@ -0,0 +1,1143 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ versions,
5
+ binaryenVersion,
6
+ diagnosticCodes,
7
+ memoryAbi,
8
+ memoryAbiV2,
9
+ es256FrameV2,
10
+ es256GuestUnit,
11
+ optimizationPostures
12
+ } = require('./constants.js');
13
+ const { fail } = require('./errors.js');
14
+ const { assertRelativePath } = require('./files.js');
15
+
16
+ const shaPattern = /^[a-f0-9]{64}$/;
17
+ const stableIdPattern = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
18
+ const modulePattern = /^[a-z][a-z0-9_]*$/;
19
+ const packagePattern = /^@pulse-compute\/[a-z0-9]+(?:-[a-z0-9]+)*$/;
20
+ const semverPattern = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
21
+ const valueTypes = new Set(['i32', 'i64', 'f32', 'f64']);
22
+ const importKinds = new Set(['function', 'memory', 'table', 'global']);
23
+ const forbiddenExecutionFields = new Set(['command', 'commands', 'argv', 'shell', 'executable']);
24
+
25
+ function plainObject(value) {
26
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
27
+ const prototype = Object.getPrototypeOf(value);
28
+ return prototype === Object.prototype || prototype === null;
29
+ }
30
+
31
+ function assertObject(value, field) {
32
+ if (!plainObject(value)) fail(diagnosticCodes.invalid, `${field} must be an object.`);
33
+ return value;
34
+ }
35
+
36
+ function exactKeys(value, required, optional, field) {
37
+ assertObject(value, field);
38
+ const allowed = new Set([...required, ...optional]);
39
+ const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort();
40
+ const missing = required.filter((key) => !Object.hasOwn(value, key));
41
+ if (unknown.length || missing.length) {
42
+ fail(diagnosticCodes.invalid, `${field} does not match its versioned field set.`, { unknown, missing });
43
+ }
44
+ }
45
+
46
+ function string(value, field) {
47
+ if (typeof value !== 'string' || value.length === 0 || value !== value.trim()) {
48
+ fail(diagnosticCodes.invalid, `${field} must be a non-empty normalized string.`);
49
+ }
50
+ return value;
51
+ }
52
+
53
+ function boolean(value, field) {
54
+ if (typeof value !== 'boolean') fail(diagnosticCodes.invalid, `${field} must be boolean.`);
55
+ return value;
56
+ }
57
+
58
+ function integer(value, field, minimum = 0) {
59
+ if (!Number.isSafeInteger(value) || value < minimum) {
60
+ fail(diagnosticCodes.invalid, `${field} must be an integer greater than or equal to ${minimum}.`);
61
+ }
62
+ return value;
63
+ }
64
+
65
+ function sha(value, field) {
66
+ if (typeof value !== 'string' || !shaPattern.test(value)) {
67
+ fail(diagnosticCodes.invalid, `${field} must be a lowercase SHA-256.`);
68
+ }
69
+ return value;
70
+ }
71
+
72
+ function stableId(value, field) {
73
+ string(value, field);
74
+ if (!stableIdPattern.test(value)) fail(diagnosticCodes.invalid, `${field} must be a stable dotted identity.`);
75
+ return value;
76
+ }
77
+
78
+ function moduleName(value, field) {
79
+ string(value, field);
80
+ if (!modulePattern.test(value)) fail(diagnosticCodes.invalid, `${field} must be a normalized core-Wasm module namespace.`);
81
+ return value;
82
+ }
83
+
84
+ function packageName(value, field) {
85
+ string(value, field);
86
+ if (!packagePattern.test(value)) fail(diagnosticCodes.ownerMismatch, `${field} must be a first-party Pulse package.`);
87
+ return value;
88
+ }
89
+
90
+ function packageVersion(value, field) {
91
+ string(value, field);
92
+ if (!semverPattern.test(value)) fail(diagnosticCodes.invalid, `${field} must be an exact semantic version.`);
93
+ return value;
94
+ }
95
+
96
+ function valueType(value, field) {
97
+ if (!valueTypes.has(value)) fail(diagnosticCodes.invalid, `${field} must be an MVP scalar value type.`);
98
+ return value;
99
+ }
100
+
101
+ function assertNoExecutionFields(value, field, depth = 0) {
102
+ if (depth > 12) fail(diagnosticCodes.invalid, `${field} exceeds the supported metadata depth.`);
103
+ if (Array.isArray(value)) {
104
+ value.forEach((entry, index) => assertNoExecutionFields(entry, `${field}[${index}]`, depth + 1));
105
+ return;
106
+ }
107
+ if (!plainObject(value)) return;
108
+ for (const [key, child] of Object.entries(value)) {
109
+ if (forbiddenExecutionFields.has(key.toLowerCase())) {
110
+ fail(diagnosticCodes.invalid, `${field} contains forbidden execution field ${key}.`);
111
+ }
112
+ assertNoExecutionFields(child, `${field}.${key}`, depth + 1);
113
+ }
114
+ }
115
+
116
+ function normalizeMemoryType(value, field) {
117
+ exactKeys(value, ['minimumPages', 'maximumPages', 'shared'], [], field);
118
+ const result = {
119
+ minimumPages: integer(value.minimumPages, `${field}.minimumPages`, 1),
120
+ maximumPages: integer(value.maximumPages, `${field}.maximumPages`, 1),
121
+ shared: boolean(value.shared, `${field}.shared`)
122
+ };
123
+ if (result.maximumPages < result.minimumPages) {
124
+ fail(diagnosticCodes.memoryMismatch, `${field} maximum must not be smaller than minimum.`);
125
+ }
126
+ return Object.freeze(result);
127
+ }
128
+
129
+ function normalizeImport(value, field) {
130
+ exactKeys(value, ['module', 'name', 'kind', 'type'], [], field);
131
+ const module = string(value.module, `${field}.module`);
132
+ const name = string(value.name, `${field}.name`);
133
+ if (value.kind === 'memory') {
134
+ return Object.freeze({ module, name, kind: 'memory', type: normalizeMemoryType(value.type, `${field}.type`) });
135
+ }
136
+ if (value.kind === 'function') {
137
+ exactKeys(value.type, ['parameters', 'results'], [], `${field}.type`);
138
+ if (!Array.isArray(value.type.parameters) || !Array.isArray(value.type.results)) {
139
+ fail(diagnosticCodes.invalid, `${field}.type parameters and results must be arrays.`);
140
+ }
141
+ return Object.freeze({
142
+ module,
143
+ name,
144
+ kind: 'function',
145
+ type: Object.freeze({
146
+ parameters: Object.freeze(value.type.parameters.map((entry, index) => valueType(entry, `${field}.type.parameters[${index}]`))),
147
+ results: Object.freeze(value.type.results.map((entry, index) => valueType(entry, `${field}.type.results[${index}]`)))
148
+ })
149
+ });
150
+ }
151
+ fail(diagnosticCodes.invalid, `${field}.kind must be function or memory.`);
152
+ }
153
+
154
+ function normalizeExport(value, field, includeRole) {
155
+ exactKeys(
156
+ value,
157
+ includeRole ? ['name', 'kind', 'parameters', 'results', 'role'] : ['name', 'kind'],
158
+ [],
159
+ field
160
+ );
161
+ const name = string(value.name, `${field}.name`);
162
+ if (includeRole) {
163
+ if (value.kind !== 'function') fail(diagnosticCodes.invalid, `${field}.kind must be function.`);
164
+ if (!['abi', 'evidence-only'].includes(value.role)) fail(diagnosticCodes.invalid, `${field}.role is unsupported.`);
165
+ if (!Array.isArray(value.parameters) || !Array.isArray(value.results)) {
166
+ fail(diagnosticCodes.invalid, `${field} parameters and results must be arrays.`);
167
+ }
168
+ return Object.freeze({
169
+ name,
170
+ kind: 'function',
171
+ parameters: Object.freeze(value.parameters.map((entry, index) => valueType(entry, `${field}.parameters[${index}]`))),
172
+ results: Object.freeze(value.results.map((entry, index) => valueType(entry, `${field}.results[${index}]`))),
173
+ role: value.role
174
+ });
175
+ }
176
+ if (!['function', 'memory', 'table', 'global'].includes(value.kind)) {
177
+ fail(diagnosticCodes.invalid, `${field}.kind is unsupported.`);
178
+ }
179
+ return Object.freeze({ name, kind: value.kind });
180
+ }
181
+
182
+ function uniqueSurface(values, field, includeModule = false) {
183
+ const keys = values.map((entry) => `${includeModule ? `${entry.module}\0` : ''}${entry.name}`);
184
+ if (new Set(keys).size !== keys.length) fail(diagnosticCodes.invalid, `${field} contains duplicate names.`);
185
+ }
186
+
187
+ function manifestRelativePath(value, field) {
188
+ string(value, field);
189
+ if (
190
+ value.includes('\\')
191
+ || value.startsWith('/')
192
+ || /^[A-Za-z]:/.test(value)
193
+ || value.split('/').some((part) => part === '' || part === '.')
194
+ ) {
195
+ fail(diagnosticCodes.invalid, `${field} must use normalized manifest-relative syntax.`);
196
+ }
197
+ return value;
198
+ }
199
+
200
+ function exactValue(value, expected, field, code = diagnosticCodes.invalid) {
201
+ if (JSON.stringify(value) !== JSON.stringify(expected)) {
202
+ fail(code, `${field} does not match the reviewed ES256 guest contract.`, { expected, actual: value });
203
+ }
204
+ return value;
205
+ }
206
+
207
+ function normalizeGuestUnitManifestV1(input) {
208
+ exactKeys(input, [
209
+ 'version',
210
+ 'id',
211
+ 'module',
212
+ 'owner',
213
+ 'packageVersion',
214
+ 'abi',
215
+ 'origin',
216
+ 'artifact',
217
+ 'toolchain',
218
+ 'imports',
219
+ 'exports',
220
+ 'memory',
221
+ 'start',
222
+ 'features',
223
+ 'provenance'
224
+ ], ['source'], 'guest unit');
225
+ assertNoExecutionFields(input, 'guest unit');
226
+ if (input.version !== versions.guestUnit) fail(diagnosticCodes.invalid, `Guest unit must use ${versions.guestUnit}.`);
227
+ const id = stableId(input.id, 'guest unit id');
228
+ const module = moduleName(input.module, 'guest unit module');
229
+ if (module === memoryAbi.importModule) {
230
+ fail(diagnosticCodes.invalid, 'Guest unit module must not collide with the memory-owner namespace.');
231
+ }
232
+ const owner = packageName(input.owner, 'guest unit owner');
233
+ const version = packageVersion(input.packageVersion, 'guest unit packageVersion');
234
+ const abi = stableId(input.abi, 'guest unit ABI');
235
+ if (input.origin !== 'package-prebuilt') fail(diagnosticCodes.invalid, 'Guest unit origin must be package-prebuilt.');
236
+
237
+ exactKeys(input.artifact, ['file', 'bytes', 'sha256'], [], 'guest unit artifact');
238
+ const artifact = Object.freeze({
239
+ file: assertRelativePath(input.artifact.file, 'guest unit artifact.file'),
240
+ bytes: integer(input.artifact.bytes, 'guest unit artifact.bytes', 1),
241
+ sha256: sha(input.artifact.sha256, 'guest unit artifact.sha256')
242
+ });
243
+
244
+ let source;
245
+ if (input.source !== undefined) {
246
+ exactKeys(input.source, ['included', 'directory', 'treeSha256'], [], 'guest unit source');
247
+ if (input.source.included !== true) fail(diagnosticCodes.invalid, 'Present guest source metadata must set included=true.');
248
+ source = Object.freeze({
249
+ included: true,
250
+ directory: assertRelativePath(input.source.directory, 'guest unit source.directory'),
251
+ treeSha256: sha(input.source.treeSha256, 'guest unit source.treeSha256')
252
+ });
253
+ }
254
+
255
+ exactKeys(input.toolchain, ['kind', 'target', 'locked', 'versions'], [], 'guest unit toolchain');
256
+ if (input.toolchain.kind !== 'rust-cargo' || input.toolchain.target !== 'wasm32v1-none' || input.toolchain.locked !== true) {
257
+ fail(diagnosticCodes.invalid, 'The initial guest toolchain must be locked rust-cargo for wasm32v1-none.');
258
+ }
259
+ exactKeys(input.toolchain.versions, ['rustc', 'cargo'], [], 'guest unit toolchain.versions');
260
+ const toolchain = Object.freeze({
261
+ kind: 'rust-cargo',
262
+ target: 'wasm32v1-none',
263
+ locked: true,
264
+ versions: Object.freeze({
265
+ rustc: string(input.toolchain.versions.rustc, 'guest unit rustc version'),
266
+ cargo: string(input.toolchain.versions.cargo, 'guest unit cargo version')
267
+ })
268
+ });
269
+
270
+ if (!Array.isArray(input.imports) || !Array.isArray(input.exports)) {
271
+ fail(diagnosticCodes.invalid, 'Guest unit imports and exports must be arrays.');
272
+ }
273
+ const imports = Object.freeze(input.imports.map((entry, index) => normalizeImport(entry, `guest unit imports[${index}]`)));
274
+ const exports = Object.freeze(input.exports.map((entry, index) => normalizeExport(entry, `guest unit exports[${index}]`, true)));
275
+ uniqueSurface(imports, 'guest unit imports', true);
276
+ uniqueSurface(exports, 'guest unit exports');
277
+ if (exports.filter((entry) => entry.role === 'abi').length !== 1) {
278
+ fail(diagnosticCodes.abiMismatch, 'The initial guest unit must declare exactly one ABI function export.');
279
+ }
280
+ const expectedMemoryImport = Object.freeze({
281
+ module: memoryAbi.importModule,
282
+ name: memoryAbi.importName,
283
+ kind: 'memory',
284
+ type: Object.freeze({
285
+ minimumPages: memoryAbi.minimumPages,
286
+ maximumPages: memoryAbi.maximumPages,
287
+ shared: false
288
+ })
289
+ });
290
+ if (JSON.stringify(imports) !== JSON.stringify([expectedMemoryImport])) {
291
+ fail(diagnosticCodes.importMismatch, 'The initial guest must import only the accepted fixed env.memory.');
292
+ }
293
+
294
+ exactKeys(input.memory, ['identity', 'import', 'owner'], [], 'guest unit memory');
295
+ if (
296
+ input.memory.identity !== versions.memoryAbi
297
+ || input.memory.import !== `${memoryAbi.importModule}.${memoryAbi.importName}`
298
+ || input.memory.owner !== 'link-stage'
299
+ ) {
300
+ fail(diagnosticCodes.memoryMismatch, 'Guest unit memory does not match the accepted borrowed-span ABI.');
301
+ }
302
+ exactKeys(input.start, ['policy'], [], 'guest unit start');
303
+ if (input.start.policy !== 'forbidden') fail(diagnosticCodes.startMismatch, 'Guest unit start policy must be forbidden.');
304
+ exactKeys(input.features, ['baseline', 'allowed', 'required'], [], 'guest unit features');
305
+ if (
306
+ input.features.baseline !== 'mvp'
307
+ || !Array.isArray(input.features.allowed)
308
+ || input.features.allowed.length !== 0
309
+ || !Array.isArray(input.features.required)
310
+ || input.features.required.length !== 0
311
+ ) {
312
+ fail(diagnosticCodes.featureMismatch, 'The initial guest unit must use the MVP feature baseline without additions.');
313
+ }
314
+
315
+ exactKeys(input.provenance, ['packageManifest', 'lockfile', 'reproducibleSourceIncluded'], [], 'guest unit provenance');
316
+ const provenance = Object.freeze({
317
+ packageManifest: assertRelativePath(input.provenance.packageManifest, 'guest unit provenance.packageManifest'),
318
+ lockfile: assertRelativePath(input.provenance.lockfile, 'guest unit provenance.lockfile'),
319
+ reproducibleSourceIncluded: boolean(input.provenance.reproducibleSourceIncluded, 'guest unit provenance.reproducibleSourceIncluded')
320
+ });
321
+ if (provenance.reproducibleSourceIncluded !== Boolean(source)) {
322
+ fail(diagnosticCodes.invalid, 'Guest source presence and provenance must agree.');
323
+ }
324
+
325
+ const normalized = {
326
+ version: versions.guestUnit,
327
+ id,
328
+ module,
329
+ owner,
330
+ packageVersion: version,
331
+ abi,
332
+ origin: 'package-prebuilt',
333
+ artifact
334
+ };
335
+ if (source) normalized.source = source;
336
+ normalized.toolchain = toolchain;
337
+ normalized.imports = imports;
338
+ normalized.exports = exports;
339
+ normalized.memory = Object.freeze({
340
+ identity: versions.memoryAbi,
341
+ import: `${memoryAbi.importModule}.${memoryAbi.importName}`,
342
+ owner: 'link-stage'
343
+ });
344
+ normalized.start = Object.freeze({ policy: 'forbidden' });
345
+ normalized.features = Object.freeze({ baseline: 'mvp', allowed: Object.freeze([]), required: Object.freeze([]) });
346
+ normalized.provenance = provenance;
347
+ return Object.freeze(normalized);
348
+ }
349
+
350
+ function normalizeGuestUnitManifestV2(input) {
351
+ exactKeys(input, [
352
+ 'version',
353
+ 'id',
354
+ 'module',
355
+ 'owner',
356
+ 'packageVersion',
357
+ 'abi',
358
+ 'origin',
359
+ 'artifact',
360
+ 'source',
361
+ 'toolchain',
362
+ 'imports',
363
+ 'exports',
364
+ 'memory',
365
+ 'start',
366
+ 'features',
367
+ 'provenance'
368
+ ], [], 'guest unit v2');
369
+ assertNoExecutionFields(input, 'guest unit v2');
370
+ exactValue(input.version, versions.guestUnitV2, 'guest unit v2 version');
371
+ const id = stableId(input.id, 'guest unit v2 id');
372
+ const module = moduleName(input.module, 'guest unit v2 module');
373
+ const owner = packageName(input.owner, 'guest unit v2 owner');
374
+ const packageVersionValue = packageVersion(input.packageVersion, 'guest unit v2 packageVersion');
375
+ const abi = stableId(input.abi, 'guest unit v2 ABI');
376
+ exactValue(id, es256GuestUnit.id, 'guest unit v2 id', diagnosticCodes.abiMismatch);
377
+ exactValue(module, es256GuestUnit.module, 'guest unit v2 module', diagnosticCodes.abiMismatch);
378
+ exactValue(owner, es256GuestUnit.owner, 'guest unit v2 owner', diagnosticCodes.ownerMismatch);
379
+ exactValue(
380
+ packageVersionValue,
381
+ es256GuestUnit.packageVersion,
382
+ 'guest unit v2 packageVersion',
383
+ diagnosticCodes.ownerMismatch
384
+ );
385
+ exactValue(abi, es256GuestUnit.abi, 'guest unit v2 ABI', diagnosticCodes.abiMismatch);
386
+ exactValue(input.origin, 'package-prebuilt', 'guest unit v2 origin');
387
+
388
+ exactKeys(input.artifact, ['file', 'bytes', 'sha256'], [], 'guest unit v2 artifact');
389
+ const artifact = Object.freeze({
390
+ file: assertRelativePath(input.artifact.file, 'guest unit v2 artifact.file'),
391
+ bytes: integer(input.artifact.bytes, 'guest unit v2 artifact.bytes', 1),
392
+ sha256: sha(input.artifact.sha256, 'guest unit v2 artifact.sha256')
393
+ });
394
+ exactValue(artifact, es256GuestUnit.artifact, 'guest unit v2 artifact', diagnosticCodes.hashMismatch);
395
+
396
+ exactKeys(input.source, ['included', 'directory', 'treeSha256'], [], 'guest unit v2 source');
397
+ if (input.source.included !== true) fail(diagnosticCodes.invalid, 'Guest unit v2 source must be included.');
398
+ const source = Object.freeze({
399
+ included: true,
400
+ directory: assertRelativePath(input.source.directory, 'guest unit v2 source.directory'),
401
+ treeSha256: sha(input.source.treeSha256, 'guest unit v2 source.treeSha256')
402
+ });
403
+ exactValue(source.directory, es256GuestUnit.source.directory, 'guest unit v2 source.directory');
404
+ exactValue(
405
+ source.treeSha256,
406
+ es256GuestUnit.source.treeSha256,
407
+ 'guest unit v2 source.treeSha256',
408
+ diagnosticCodes.hashMismatch
409
+ );
410
+
411
+ exactKeys(input.toolchain, ['kind', 'target', 'locked', 'versions'], [], 'guest unit v2 toolchain');
412
+ exactKeys(input.toolchain.versions, ['rustc', 'cargo', 'binaryen'], [], 'guest unit v2 toolchain.versions');
413
+ const toolchain = Object.freeze({
414
+ kind: string(input.toolchain.kind, 'guest unit v2 toolchain.kind'),
415
+ target: string(input.toolchain.target, 'guest unit v2 toolchain.target'),
416
+ locked: boolean(input.toolchain.locked, 'guest unit v2 toolchain.locked'),
417
+ versions: Object.freeze({
418
+ rustc: string(input.toolchain.versions.rustc, 'guest unit v2 rustc version'),
419
+ cargo: string(input.toolchain.versions.cargo, 'guest unit v2 cargo version'),
420
+ binaryen: string(input.toolchain.versions.binaryen, 'guest unit v2 Binaryen version')
421
+ })
422
+ });
423
+ exactValue(toolchain, Object.freeze({
424
+ kind: 'rust-cargo',
425
+ target: 'wasm32v1-none',
426
+ locked: true,
427
+ versions: es256GuestUnit.toolchain
428
+ }), 'guest unit v2 toolchain', diagnosticCodes.featureMismatch);
429
+
430
+ if (!Array.isArray(input.imports) || !Array.isArray(input.exports)) {
431
+ fail(diagnosticCodes.invalid, 'Guest unit v2 imports and exports must be arrays.');
432
+ }
433
+ const imports = Object.freeze(input.imports.map((entry, index) => (
434
+ normalizeImport(entry, `guest unit v2 imports[${index}]`)
435
+ )));
436
+ const exports = Object.freeze(input.exports.map((entry, index) => (
437
+ normalizeExport(entry, `guest unit v2 exports[${index}]`, true)
438
+ )));
439
+ uniqueSurface(imports, 'guest unit v2 imports', true);
440
+ uniqueSurface(exports, 'guest unit v2 exports');
441
+ const expectedImports = Object.freeze([Object.freeze({
442
+ module: memoryAbiV2.importModule,
443
+ name: memoryAbiV2.importName,
444
+ kind: 'memory',
445
+ type: Object.freeze({
446
+ minimumPages: memoryAbiV2.minimumPages,
447
+ maximumPages: memoryAbiV2.maximumPages,
448
+ shared: false
449
+ })
450
+ })]);
451
+ exactValue(imports, expectedImports, 'guest unit v2 imports', diagnosticCodes.importMismatch);
452
+ const expectedExports = Object.freeze([Object.freeze({
453
+ name: es256FrameV2.export,
454
+ kind: 'function',
455
+ parameters: es256FrameV2.parameters,
456
+ results: es256FrameV2.results,
457
+ role: 'abi'
458
+ })]);
459
+ exactValue(exports, expectedExports, 'guest unit v2 exports', diagnosticCodes.exportMismatch);
460
+ if (exports.filter((entry) => entry.role === 'abi').length !== 1) {
461
+ fail(diagnosticCodes.abiMismatch, 'Guest unit v2 must declare exactly one ABI function export.');
462
+ }
463
+
464
+ exactKeys(input.memory, ['identity', 'import', 'owner'], [], 'guest unit v2 memory');
465
+ exactValue(input.memory, {
466
+ identity: versions.memoryAbiV2,
467
+ import: `${memoryAbiV2.importModule}.${memoryAbiV2.importName}`,
468
+ owner: 'link-stage'
469
+ }, 'guest unit v2 memory', diagnosticCodes.memoryMismatch);
470
+ exactKeys(input.start, ['policy'], [], 'guest unit v2 start');
471
+ exactValue(input.start.policy, 'forbidden', 'guest unit v2 start policy', diagnosticCodes.startMismatch);
472
+ exactKeys(input.features, ['baseline', 'allowed', 'required'], [], 'guest unit v2 features');
473
+ exactValue(input.features, {
474
+ baseline: 'mvp',
475
+ allowed: [],
476
+ required: []
477
+ }, 'guest unit v2 features', diagnosticCodes.featureMismatch);
478
+
479
+ exactKeys(input.provenance, [
480
+ 'packageManifest',
481
+ 'lockfile',
482
+ 'reproducibleSourceIncluded',
483
+ 'cargoLockSha256',
484
+ 'sourceTreeSha256',
485
+ 'reconstructionCommandIdentity',
486
+ 'buildScript',
487
+ 'buildScriptSha256',
488
+ 'optimizationPosture',
489
+ 'binaryenWasmOptSha256',
490
+ 'g0SourceDecision',
491
+ 'g0SourceDecisionSha256'
492
+ ], [], 'guest unit v2 provenance');
493
+ const provenance = Object.freeze({
494
+ packageManifest: manifestRelativePath(
495
+ input.provenance.packageManifest,
496
+ 'guest unit v2 provenance.packageManifest'
497
+ ),
498
+ lockfile: assertRelativePath(input.provenance.lockfile, 'guest unit v2 provenance.lockfile'),
499
+ reproducibleSourceIncluded: boolean(
500
+ input.provenance.reproducibleSourceIncluded,
501
+ 'guest unit v2 provenance.reproducibleSourceIncluded'
502
+ ),
503
+ cargoLockSha256: sha(input.provenance.cargoLockSha256, 'guest unit v2 provenance.cargoLockSha256'),
504
+ sourceTreeSha256: sha(input.provenance.sourceTreeSha256, 'guest unit v2 provenance.sourceTreeSha256'),
505
+ reconstructionCommandIdentity: stableId(
506
+ input.provenance.reconstructionCommandIdentity,
507
+ 'guest unit v2 provenance.reconstructionCommandIdentity'
508
+ ),
509
+ buildScript: assertRelativePath(input.provenance.buildScript, 'guest unit v2 provenance.buildScript'),
510
+ buildScriptSha256: sha(input.provenance.buildScriptSha256, 'guest unit v2 provenance.buildScriptSha256'),
511
+ optimizationPosture: string(
512
+ input.provenance.optimizationPosture,
513
+ 'guest unit v2 provenance.optimizationPosture'
514
+ ),
515
+ binaryenWasmOptSha256: sha(
516
+ input.provenance.binaryenWasmOptSha256,
517
+ 'guest unit v2 provenance.binaryenWasmOptSha256'
518
+ ),
519
+ g0SourceDecision: manifestRelativePath(
520
+ input.provenance.g0SourceDecision,
521
+ 'guest unit v2 provenance.g0SourceDecision'
522
+ ),
523
+ g0SourceDecisionSha256: sha(
524
+ input.provenance.g0SourceDecisionSha256,
525
+ 'guest unit v2 provenance.g0SourceDecisionSha256'
526
+ )
527
+ });
528
+ exactValue(
529
+ provenance.packageManifest,
530
+ '../../package.json',
531
+ 'guest unit v2 provenance.packageManifest'
532
+ );
533
+ exactValue(
534
+ provenance.lockfile,
535
+ 'source/Cargo.lock',
536
+ 'guest unit v2 provenance.lockfile'
537
+ );
538
+ exactValue(provenance.reproducibleSourceIncluded, true, 'guest unit v2 source provenance');
539
+ exactValue(
540
+ provenance.cargoLockSha256,
541
+ es256GuestUnit.provenance.cargoLockSha256,
542
+ 'guest unit v2 Cargo.lock',
543
+ diagnosticCodes.hashMismatch
544
+ );
545
+ exactValue(
546
+ provenance.sourceTreeSha256,
547
+ source.treeSha256,
548
+ 'guest unit v2 provenance source tree',
549
+ diagnosticCodes.hashMismatch
550
+ );
551
+ for (const key of [
552
+ 'reconstructionCommandIdentity',
553
+ 'buildScript',
554
+ 'buildScriptSha256',
555
+ 'optimizationPosture',
556
+ 'binaryenWasmOptSha256',
557
+ 'g0SourceDecision',
558
+ 'g0SourceDecisionSha256'
559
+ ]) {
560
+ exactValue(
561
+ provenance[key],
562
+ es256GuestUnit.provenance[key],
563
+ `guest unit v2 provenance.${key}`,
564
+ key.toLowerCase().includes('sha256') ? diagnosticCodes.hashMismatch : diagnosticCodes.invalid
565
+ );
566
+ }
567
+ exactValue(toolchain.versions.binaryen, binaryenVersion, 'guest unit v2 Binaryen version', diagnosticCodes.featureMismatch);
568
+
569
+ return Object.freeze({
570
+ version: versions.guestUnitV2,
571
+ id,
572
+ module,
573
+ owner,
574
+ packageVersion: packageVersionValue,
575
+ abi,
576
+ origin: 'package-prebuilt',
577
+ artifact,
578
+ source,
579
+ toolchain,
580
+ imports,
581
+ exports,
582
+ memory: Object.freeze({
583
+ identity: versions.memoryAbiV2,
584
+ import: `${memoryAbiV2.importModule}.${memoryAbiV2.importName}`,
585
+ owner: 'link-stage'
586
+ }),
587
+ start: Object.freeze({ policy: 'forbidden' }),
588
+ features: Object.freeze({
589
+ baseline: 'mvp',
590
+ allowed: Object.freeze([]),
591
+ required: Object.freeze([])
592
+ }),
593
+ provenance
594
+ });
595
+ }
596
+
597
+ function normalizeGuestUnitManifest(input) {
598
+ if (!plainObject(input)) fail(diagnosticCodes.invalid, 'Guest unit must be an object.');
599
+ if (input.version === versions.guestUnit) return normalizeGuestUnitManifestV1(input);
600
+ if (input.version === versions.guestUnitV2) return normalizeGuestUnitManifestV2(input);
601
+ fail(
602
+ diagnosticCodes.invalid,
603
+ `Guest unit must use ${versions.guestUnit} or the selected ${versions.guestUnitV2} contract.`
604
+ );
605
+ }
606
+
607
+ function normalizeTargetPolicy(input) {
608
+ exactKeys(input, [
609
+ 'descriptorOwner',
610
+ 'toolchainVersion',
611
+ 'descriptorIdentity',
612
+ 'descriptorSha256',
613
+ 'allowedImports',
614
+ 'requiredExports',
615
+ 'featureBaseline',
616
+ 'allowedFeatures',
617
+ 'memory',
618
+ 'start'
619
+ ], [], 'guest unit plan targetPolicy');
620
+ if (!Array.isArray(input.allowedImports) || !Array.isArray(input.requiredExports) || !Array.isArray(input.allowedFeatures)) {
621
+ fail(diagnosticCodes.invalid, 'Target policy surfaces and features must be arrays.');
622
+ }
623
+ const allowedImports = Object.freeze(input.allowedImports.map((entry, index) => {
624
+ exactKeys(entry, ['module', 'name', 'kind'], [], `targetPolicy.allowedImports[${index}]`);
625
+ if (!importKinds.has(entry.kind)) {
626
+ fail(diagnosticCodes.invalid, `targetPolicy.allowedImports[${index}].kind is unsupported.`);
627
+ }
628
+ return Object.freeze({
629
+ module: string(entry.module, `targetPolicy.allowedImports[${index}].module`),
630
+ name: string(entry.name, `targetPolicy.allowedImports[${index}].name`),
631
+ kind: string(entry.kind, `targetPolicy.allowedImports[${index}].kind`)
632
+ });
633
+ }));
634
+ const requiredExports = Object.freeze(input.requiredExports.map((entry, index) => normalizeExport(entry, `targetPolicy.requiredExports[${index}]`, false)));
635
+ uniqueSurface(allowedImports, 'target policy allowedImports', true);
636
+ uniqueSurface(requiredExports, 'target policy requiredExports');
637
+ if (input.featureBaseline !== 'mvp' || input.allowedFeatures.length !== 0) {
638
+ fail(diagnosticCodes.featureMismatch, 'The initial target policy must require MVP without feature additions.');
639
+ }
640
+ exactKeys(input.memory, ['minimum', 'maximum', 'imported', 'growable'], [], 'targetPolicy.memory');
641
+ const memory = Object.freeze({
642
+ minimum: integer(input.memory.minimum, 'targetPolicy.memory.minimum', 0),
643
+ maximum: integer(input.memory.maximum, 'targetPolicy.memory.maximum', 0),
644
+ imported: integer(input.memory.imported, 'targetPolicy.memory.imported', 0),
645
+ growable: boolean(input.memory.growable, 'targetPolicy.memory.growable')
646
+ });
647
+ if (JSON.stringify(memory) !== JSON.stringify({ minimum: 1, maximum: 1, imported: 0, growable: false })) {
648
+ fail(diagnosticCodes.memoryMismatch, 'The initial target policy must require one defined non-growable memory.');
649
+ }
650
+ if (input.start !== 'forbidden') fail(diagnosticCodes.startMismatch, 'Target policy must forbid a start section.');
651
+ return Object.freeze({
652
+ descriptorOwner: packageName(input.descriptorOwner, 'targetPolicy.descriptorOwner'),
653
+ toolchainVersion: stableId(input.toolchainVersion, 'targetPolicy.toolchainVersion'),
654
+ descriptorIdentity: stableId(input.descriptorIdentity, 'targetPolicy.descriptorIdentity'),
655
+ descriptorSha256: sha(input.descriptorSha256, 'targetPolicy.descriptorSha256'),
656
+ allowedImports,
657
+ requiredExports,
658
+ featureBaseline: 'mvp',
659
+ allowedFeatures: Object.freeze([]),
660
+ memory,
661
+ start: 'forbidden'
662
+ });
663
+ }
664
+
665
+ function normalizeGuestUnitPlanV1(input) {
666
+ exactKeys(input, [
667
+ 'version',
668
+ 'profile',
669
+ 'target',
670
+ 'unit',
671
+ 'targetPolicy',
672
+ 'materialization',
673
+ 'trust',
674
+ 'composition',
675
+ 'fallback'
676
+ ], [], 'guest unit plan');
677
+ assertNoExecutionFields(input, 'guest unit plan');
678
+ if (input.version !== versions.guestUnitPlan) fail(diagnosticCodes.invalid, `Guest unit plan must use ${versions.guestUnitPlan}.`);
679
+ exactKeys(input.unit, [
680
+ 'id',
681
+ 'module',
682
+ 'owner',
683
+ 'packageVersion',
684
+ 'abi',
685
+ 'origin',
686
+ 'manifestSha256',
687
+ 'artifactSha256',
688
+ 'source'
689
+ ], [], 'guest unit plan unit');
690
+ exactKeys(input.unit.source, ['included', 'treeSha256'], [], 'guest unit plan source');
691
+ const unit = Object.freeze({
692
+ id: stableId(input.unit.id, 'guest unit plan unit.id'),
693
+ module: moduleName(input.unit.module, 'guest unit plan unit.module'),
694
+ owner: packageName(input.unit.owner, 'guest unit plan unit.owner'),
695
+ packageVersion: packageVersion(input.unit.packageVersion, 'guest unit plan unit.packageVersion'),
696
+ abi: stableId(input.unit.abi, 'guest unit plan unit.abi'),
697
+ origin: input.unit.origin,
698
+ manifestSha256: sha(input.unit.manifestSha256, 'guest unit plan unit.manifestSha256'),
699
+ artifactSha256: sha(input.unit.artifactSha256, 'guest unit plan unit.artifactSha256'),
700
+ source: Object.freeze({
701
+ included: boolean(input.unit.source.included, 'guest unit plan unit.source.included'),
702
+ treeSha256: input.unit.source.included
703
+ ? sha(input.unit.source.treeSha256, 'guest unit plan unit.source.treeSha256')
704
+ : null
705
+ })
706
+ });
707
+ if (unit.origin !== 'package-prebuilt') fail(diagnosticCodes.invalid, 'Guest unit plan origin must be package-prebuilt.');
708
+
709
+ const targetPolicy = normalizeTargetPolicy(input.targetPolicy);
710
+ const target = stableId(input.target, 'guest unit plan target');
711
+ if (targetPolicy.descriptorIdentity !== target) fail(diagnosticCodes.invalid, 'Target policy identity must equal the selected target.');
712
+
713
+ exactKeys(input.materialization, ['workspace', 'directory', 'artifact', 'manifest', 'contentAddress', 'generated', 'authoritative'], [], 'guest unit plan materialization');
714
+ const workspace = assertRelativePath(input.materialization.workspace, 'guest unit plan materialization.workspace');
715
+ const directory = assertRelativePath(input.materialization.directory, 'guest unit plan materialization.directory');
716
+ if (
717
+ workspace !== '.pulse/guests'
718
+ || directory !== `${workspace}/${unit.id}/${unit.artifactSha256}`
719
+ || input.materialization.artifact !== 'unit.wasm'
720
+ || input.materialization.manifest !== 'unit.json'
721
+ || input.materialization.contentAddress !== 'artifact-sha256'
722
+ || input.materialization.generated !== true
723
+ || input.materialization.authoritative !== false
724
+ ) {
725
+ fail(diagnosticCodes.materializationFailed, 'Guest unit plan materialization is not the accepted content-addressed workspace.');
726
+ }
727
+
728
+ exactKeys(input.trust, ['lowerer', 'releaseManifest', 'ownerMatchesContributionPackage', 'installedVersionMatches', 'status'], [], 'guest unit plan trust');
729
+ if (
730
+ input.trust.lowerer !== 'trusted-first-party'
731
+ || input.trust.releaseManifest !== 'synchronized'
732
+ || input.trust.ownerMatchesContributionPackage !== true
733
+ || input.trust.installedVersionMatches !== true
734
+ || input.trust.status !== 'passed'
735
+ ) {
736
+ fail(diagnosticCodes.ownerMismatch, 'Guest unit plan trust must be a fully passed synchronized first-party decision.');
737
+ }
738
+
739
+ exactKeys(input.composition, ['kind', 'semanticContract', 'primaryModule', 'memoryOwnerModule'], [], 'guest unit plan composition');
740
+ if (
741
+ input.composition.kind !== 'core-wasm-static-link'
742
+ || input.composition.semanticContract !== 'connect-validated-core-units-into-one-final-audited-native-artifact'
743
+ || moduleName(input.composition.primaryModule, 'guest unit plan composition.primaryModule') === unit.module
744
+ || input.composition.primaryModule === memoryAbi.importModule
745
+ || input.composition.memoryOwnerModule !== memoryAbi.importModule
746
+ ) {
747
+ fail(diagnosticCodes.invalid, 'Guest unit plan composition does not match the accepted static-link boundary.');
748
+ }
749
+ exactKeys(input.fallback, ['enabled', 'onFailure'], [], 'guest unit plan fallback');
750
+ if (input.fallback.enabled !== false || input.fallback.onFailure !== 'stop-before-provider-packaging') {
751
+ fail(diagnosticCodes.invalid, 'Guest unit plan fallback must be disabled and fail before provider packaging.');
752
+ }
753
+
754
+ return Object.freeze({
755
+ version: versions.guestUnitPlan,
756
+ profile: string(input.profile, 'guest unit plan profile'),
757
+ target,
758
+ unit,
759
+ targetPolicy,
760
+ materialization: Object.freeze({
761
+ workspace,
762
+ directory,
763
+ artifact: 'unit.wasm',
764
+ manifest: 'unit.json',
765
+ contentAddress: 'artifact-sha256',
766
+ generated: true,
767
+ authoritative: false
768
+ }),
769
+ trust: Object.freeze({
770
+ lowerer: 'trusted-first-party',
771
+ releaseManifest: 'synchronized',
772
+ ownerMatchesContributionPackage: true,
773
+ installedVersionMatches: true,
774
+ status: 'passed'
775
+ }),
776
+ composition: Object.freeze({
777
+ kind: 'core-wasm-static-link',
778
+ semanticContract: 'connect-validated-core-units-into-one-final-audited-native-artifact',
779
+ primaryModule: input.composition.primaryModule,
780
+ memoryOwnerModule: memoryAbi.importModule
781
+ }),
782
+ fallback: Object.freeze({ enabled: false, onFailure: 'stop-before-provider-packaging' })
783
+ });
784
+ }
785
+
786
+ function normalizeGuestUnitPlanV2(input) {
787
+ exactKeys(input, [
788
+ 'version',
789
+ 'profile',
790
+ 'target',
791
+ 'unit',
792
+ 'targetPolicy',
793
+ 'materialization',
794
+ 'trust',
795
+ 'composition',
796
+ 'optimization',
797
+ 'fallback'
798
+ ], [], 'guest unit plan v2');
799
+ assertNoExecutionFields(input, 'guest unit plan v2');
800
+ exactValue(input.version, versions.guestUnitPlanV2, 'guest unit plan v2 version');
801
+ exactKeys(input.unit, [
802
+ 'manifestVersion',
803
+ 'id',
804
+ 'module',
805
+ 'owner',
806
+ 'packageVersion',
807
+ 'abi',
808
+ 'origin',
809
+ 'manifestSha256',
810
+ 'artifact',
811
+ 'source',
812
+ 'memoryIdentity',
813
+ 'frame',
814
+ 'toolchain'
815
+ ], [], 'guest unit plan v2 unit');
816
+ exactKeys(input.unit.artifact, ['bytes', 'sha256'], [], 'guest unit plan v2 artifact');
817
+ exactKeys(input.unit.source, ['included', 'treeSha256'], [], 'guest unit plan v2 source');
818
+ exactKeys(
819
+ input.unit.frame,
820
+ ['identity', 'capacityBytes', 'alignmentBytes', 'pointerWidthBits'],
821
+ [],
822
+ 'guest unit plan v2 frame'
823
+ );
824
+ exactKeys(
825
+ input.unit.toolchain,
826
+ ['binaryenVersion', 'wasmOptSha256'],
827
+ [],
828
+ 'guest unit plan v2 toolchain'
829
+ );
830
+ const unit = Object.freeze({
831
+ manifestVersion: string(input.unit.manifestVersion, 'guest unit plan v2 unit.manifestVersion'),
832
+ id: stableId(input.unit.id, 'guest unit plan v2 unit.id'),
833
+ module: moduleName(input.unit.module, 'guest unit plan v2 unit.module'),
834
+ owner: packageName(input.unit.owner, 'guest unit plan v2 unit.owner'),
835
+ packageVersion: packageVersion(input.unit.packageVersion, 'guest unit plan v2 unit.packageVersion'),
836
+ abi: stableId(input.unit.abi, 'guest unit plan v2 unit.abi'),
837
+ origin: string(input.unit.origin, 'guest unit plan v2 unit.origin'),
838
+ manifestSha256: sha(input.unit.manifestSha256, 'guest unit plan v2 unit.manifestSha256'),
839
+ artifact: Object.freeze({
840
+ bytes: integer(input.unit.artifact.bytes, 'guest unit plan v2 unit.artifact.bytes', 1),
841
+ sha256: sha(input.unit.artifact.sha256, 'guest unit plan v2 unit.artifact.sha256')
842
+ }),
843
+ source: Object.freeze({
844
+ included: boolean(input.unit.source.included, 'guest unit plan v2 unit.source.included'),
845
+ treeSha256: sha(input.unit.source.treeSha256, 'guest unit plan v2 unit.source.treeSha256')
846
+ }),
847
+ memoryIdentity: string(input.unit.memoryIdentity, 'guest unit plan v2 unit.memoryIdentity'),
848
+ frame: Object.freeze({
849
+ identity: stableId(input.unit.frame.identity, 'guest unit plan v2 unit.frame.identity'),
850
+ capacityBytes: integer(input.unit.frame.capacityBytes, 'guest unit plan v2 unit.frame.capacityBytes', 1),
851
+ alignmentBytes: integer(input.unit.frame.alignmentBytes, 'guest unit plan v2 unit.frame.alignmentBytes', 1),
852
+ pointerWidthBits: integer(input.unit.frame.pointerWidthBits, 'guest unit plan v2 unit.frame.pointerWidthBits', 1)
853
+ }),
854
+ toolchain: Object.freeze({
855
+ binaryenVersion: string(
856
+ input.unit.toolchain.binaryenVersion,
857
+ 'guest unit plan v2 unit.toolchain.binaryenVersion'
858
+ ),
859
+ wasmOptSha256: sha(
860
+ input.unit.toolchain.wasmOptSha256,
861
+ 'guest unit plan v2 unit.toolchain.wasmOptSha256'
862
+ )
863
+ })
864
+ });
865
+ exactValue(unit.manifestVersion, versions.guestUnitV2, 'guest unit plan v2 manifest version');
866
+ exactValue(unit.id, es256GuestUnit.id, 'guest unit plan v2 unit id', diagnosticCodes.abiMismatch);
867
+ exactValue(unit.module, es256GuestUnit.module, 'guest unit plan v2 module', diagnosticCodes.abiMismatch);
868
+ exactValue(unit.owner, es256GuestUnit.owner, 'guest unit plan v2 owner', diagnosticCodes.ownerMismatch);
869
+ exactValue(
870
+ unit.packageVersion,
871
+ es256GuestUnit.packageVersion,
872
+ 'guest unit plan v2 package version',
873
+ diagnosticCodes.ownerMismatch
874
+ );
875
+ exactValue(unit.abi, es256GuestUnit.abi, 'guest unit plan v2 ABI', diagnosticCodes.abiMismatch);
876
+ exactValue(unit.origin, 'package-prebuilt', 'guest unit plan v2 origin');
877
+ exactValue(unit.artifact, {
878
+ bytes: es256GuestUnit.artifact.bytes,
879
+ sha256: es256GuestUnit.artifact.sha256
880
+ }, 'guest unit plan v2 artifact', diagnosticCodes.hashMismatch);
881
+ exactValue(unit.source, {
882
+ included: true,
883
+ treeSha256: es256GuestUnit.source.treeSha256
884
+ }, 'guest unit plan v2 source', diagnosticCodes.hashMismatch);
885
+ exactValue(
886
+ unit.memoryIdentity,
887
+ versions.memoryAbiV2,
888
+ 'guest unit plan v2 memory identity',
889
+ diagnosticCodes.memoryMismatch
890
+ );
891
+ exactValue(unit.frame, {
892
+ identity: es256FrameV2.identity,
893
+ capacityBytes: es256FrameV2.capacityBytes,
894
+ alignmentBytes: es256FrameV2.alignmentBytes,
895
+ pointerWidthBits: es256FrameV2.pointerWidthBits
896
+ }, 'guest unit plan v2 frame', diagnosticCodes.abiMismatch);
897
+ exactValue(unit.toolchain, {
898
+ binaryenVersion,
899
+ wasmOptSha256: es256GuestUnit.provenance.binaryenWasmOptSha256
900
+ }, 'guest unit plan v2 toolchain', diagnosticCodes.featureMismatch);
901
+
902
+ const targetPolicy = normalizeTargetPolicy(input.targetPolicy);
903
+ const target = stableId(input.target, 'guest unit plan v2 target');
904
+ if (targetPolicy.descriptorIdentity !== target) {
905
+ fail(diagnosticCodes.invalid, 'Guest unit plan v2 target policy identity must equal the selected target.');
906
+ }
907
+
908
+ exactKeys(
909
+ input.materialization,
910
+ ['workspace', 'directory', 'artifact', 'manifest', 'contentAddress', 'generated', 'authoritative'],
911
+ [],
912
+ 'guest unit plan v2 materialization'
913
+ );
914
+ const workspace = assertRelativePath(
915
+ input.materialization.workspace,
916
+ 'guest unit plan v2 materialization.workspace'
917
+ );
918
+ const directory = assertRelativePath(
919
+ input.materialization.directory,
920
+ 'guest unit plan v2 materialization.directory'
921
+ );
922
+ if (
923
+ workspace !== '.pulse/guests'
924
+ || directory !== `${workspace}/${unit.id}/${unit.artifact.sha256}`
925
+ || input.materialization.artifact !== 'unit.wasm'
926
+ || input.materialization.manifest !== 'unit.json'
927
+ || input.materialization.contentAddress !== 'artifact-sha256'
928
+ || input.materialization.generated !== true
929
+ || input.materialization.authoritative !== false
930
+ ) {
931
+ fail(
932
+ diagnosticCodes.materializationFailed,
933
+ 'Guest unit plan v2 materialization is not the accepted content-addressed workspace.'
934
+ );
935
+ }
936
+
937
+ exactKeys(
938
+ input.trust,
939
+ ['lowerer', 'catalog', 'catalogStatus', 'ownerMatchesContributionPackage', 'installedVersionMatches', 'status'],
940
+ [],
941
+ 'guest unit plan v2 trust'
942
+ );
943
+ exactValue(input.trust, {
944
+ lowerer: 'trusted-first-party',
945
+ catalog: 'pulse.jwt-crypto-working-candidate.v1',
946
+ catalogStatus: 'unpublished-synchronized',
947
+ ownerMatchesContributionPackage: true,
948
+ installedVersionMatches: true,
949
+ status: 'passed'
950
+ }, 'guest unit plan v2 trust', diagnosticCodes.ownerMismatch);
951
+
952
+ exactKeys(
953
+ input.composition,
954
+ ['kind', 'semanticContract', 'primaryModule', 'memoryOwnerModule'],
955
+ [],
956
+ 'guest unit plan v2 composition'
957
+ );
958
+ if (
959
+ input.composition.kind !== 'core-wasm-static-link'
960
+ || input.composition.semanticContract
961
+ !== 'connect-validated-core-units-into-one-final-audited-native-artifact'
962
+ || moduleName(input.composition.primaryModule, 'guest unit plan v2 composition.primaryModule') === unit.module
963
+ || input.composition.primaryModule === memoryAbiV2.importModule
964
+ || input.composition.memoryOwnerModule !== memoryAbiV2.importModule
965
+ ) {
966
+ fail(diagnosticCodes.invalid, 'Guest unit plan v2 composition does not match the private static-link boundary.');
967
+ }
968
+
969
+ exactKeys(
970
+ input.optimization,
971
+ ['mode', 'postLink', 'posture'],
972
+ [],
973
+ 'guest unit plan v2 optimization'
974
+ );
975
+ const optimizationMode = string(input.optimization.mode, 'guest unit plan v2 optimization.mode');
976
+ const expectedPosture = optimizationMode === 'default'
977
+ ? 'native-default'
978
+ : optimizationMode === 'experimental-native-size'
979
+ ? 'native-size'
980
+ : null;
981
+ if (
982
+ expectedPosture === null
983
+ || input.optimization.postLink !== true
984
+ || input.optimization.posture !== expectedPosture
985
+ ) {
986
+ fail(diagnosticCodes.optimizationFailed, 'Guest unit plan v2 optimization mode and posture do not match.');
987
+ }
988
+
989
+ exactKeys(input.fallback, ['enabled', 'onFailure'], [], 'guest unit plan v2 fallback');
990
+ exactValue(input.fallback, {
991
+ enabled: false,
992
+ onFailure: 'stop-before-provider-packaging'
993
+ }, 'guest unit plan v2 fallback');
994
+
995
+ return Object.freeze({
996
+ version: versions.guestUnitPlanV2,
997
+ profile: string(input.profile, 'guest unit plan v2 profile'),
998
+ target,
999
+ unit,
1000
+ targetPolicy,
1001
+ materialization: Object.freeze({
1002
+ workspace,
1003
+ directory,
1004
+ artifact: 'unit.wasm',
1005
+ manifest: 'unit.json',
1006
+ contentAddress: 'artifact-sha256',
1007
+ generated: true,
1008
+ authoritative: false
1009
+ }),
1010
+ trust: Object.freeze({
1011
+ lowerer: 'trusted-first-party',
1012
+ catalog: 'pulse.jwt-crypto-working-candidate.v1',
1013
+ catalogStatus: 'unpublished-synchronized',
1014
+ ownerMatchesContributionPackage: true,
1015
+ installedVersionMatches: true,
1016
+ status: 'passed'
1017
+ }),
1018
+ composition: Object.freeze({
1019
+ kind: 'core-wasm-static-link',
1020
+ semanticContract: 'connect-validated-core-units-into-one-final-audited-native-artifact',
1021
+ primaryModule: input.composition.primaryModule,
1022
+ memoryOwnerModule: memoryAbiV2.importModule
1023
+ }),
1024
+ optimization: Object.freeze({
1025
+ mode: optimizationMode,
1026
+ postLink: true,
1027
+ posture: expectedPosture
1028
+ }),
1029
+ fallback: Object.freeze({
1030
+ enabled: false,
1031
+ onFailure: 'stop-before-provider-packaging'
1032
+ })
1033
+ });
1034
+ }
1035
+
1036
+ function normalizeGuestUnitPlan(input) {
1037
+ if (!plainObject(input)) fail(diagnosticCodes.invalid, 'Guest unit plan must be an object.');
1038
+ if (input.version === versions.guestUnitPlan) return normalizeGuestUnitPlanV1(input);
1039
+ if (input.version === versions.guestUnitPlanV2) return normalizeGuestUnitPlanV2(input);
1040
+ fail(
1041
+ diagnosticCodes.invalid,
1042
+ `Guest unit plan must use ${versions.guestUnitPlan} or ${versions.guestUnitPlanV2}.`
1043
+ );
1044
+ }
1045
+
1046
+ function assertPlanMatchesManifest(plan, manifest) {
1047
+ if (plan.version === versions.guestUnitPlanV2 || manifest.version === versions.guestUnitV2) {
1048
+ if (plan.version !== versions.guestUnitPlanV2 || manifest.version !== versions.guestUnitV2) {
1049
+ fail(diagnosticCodes.abiMismatch, 'Guest unit plan and manifest versions do not match.');
1050
+ }
1051
+ const expectedV2 = {
1052
+ manifestVersion: manifest.version,
1053
+ id: manifest.id,
1054
+ module: manifest.module,
1055
+ owner: manifest.owner,
1056
+ packageVersion: manifest.packageVersion,
1057
+ abi: manifest.abi,
1058
+ origin: manifest.origin,
1059
+ artifact: {
1060
+ bytes: manifest.artifact.bytes,
1061
+ sha256: manifest.artifact.sha256
1062
+ },
1063
+ source: {
1064
+ included: true,
1065
+ treeSha256: manifest.source.treeSha256
1066
+ },
1067
+ memoryIdentity: manifest.memory.identity,
1068
+ frame: {
1069
+ identity: es256FrameV2.identity,
1070
+ capacityBytes: es256FrameV2.capacityBytes,
1071
+ alignmentBytes: es256FrameV2.alignmentBytes,
1072
+ pointerWidthBits: es256FrameV2.pointerWidthBits
1073
+ },
1074
+ toolchain: {
1075
+ binaryenVersion: manifest.toolchain.versions.binaryen,
1076
+ wasmOptSha256: manifest.provenance.binaryenWasmOptSha256
1077
+ }
1078
+ };
1079
+ const actualV2 = {
1080
+ manifestVersion: plan.unit.manifestVersion,
1081
+ id: plan.unit.id,
1082
+ module: plan.unit.module,
1083
+ owner: plan.unit.owner,
1084
+ packageVersion: plan.unit.packageVersion,
1085
+ abi: plan.unit.abi,
1086
+ origin: plan.unit.origin,
1087
+ artifact: plan.unit.artifact,
1088
+ source: plan.unit.source,
1089
+ memoryIdentity: plan.unit.memoryIdentity,
1090
+ frame: plan.unit.frame,
1091
+ toolchain: plan.unit.toolchain
1092
+ };
1093
+ if (JSON.stringify(actualV2) !== JSON.stringify(expectedV2)) {
1094
+ fail(
1095
+ diagnosticCodes.abiMismatch,
1096
+ 'Guest unit v2 plan and manifest identities do not match.',
1097
+ { expected: expectedV2, actual: actualV2 }
1098
+ );
1099
+ }
1100
+ return;
1101
+ }
1102
+ const expected = {
1103
+ id: manifest.id,
1104
+ module: manifest.module,
1105
+ owner: manifest.owner,
1106
+ packageVersion: manifest.packageVersion,
1107
+ abi: manifest.abi,
1108
+ origin: manifest.origin,
1109
+ artifactSha256: manifest.artifact.sha256,
1110
+ source: {
1111
+ included: Boolean(manifest.source),
1112
+ treeSha256: manifest.source ? manifest.source.treeSha256 : null
1113
+ }
1114
+ };
1115
+ const actual = {
1116
+ id: plan.unit.id,
1117
+ module: plan.unit.module,
1118
+ owner: plan.unit.owner,
1119
+ packageVersion: plan.unit.packageVersion,
1120
+ abi: plan.unit.abi,
1121
+ origin: plan.unit.origin,
1122
+ artifactSha256: plan.unit.artifactSha256,
1123
+ source: plan.unit.source
1124
+ };
1125
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
1126
+ fail(diagnosticCodes.abiMismatch, 'Guest unit plan and manifest identities do not match.', { expected, actual });
1127
+ }
1128
+ }
1129
+
1130
+ function optimizationArguments(posture) {
1131
+ const args = optimizationPostures[posture];
1132
+ if (!args) fail(diagnosticCodes.optimizationFailed, `Unsupported guest-link optimization posture ${posture}.`);
1133
+ return args;
1134
+ }
1135
+
1136
+ module.exports = Object.freeze({
1137
+ normalizeGuestUnitManifest,
1138
+ normalizeGuestUnitPlan,
1139
+ assertPlanMatchesManifest,
1140
+ normalizeImport,
1141
+ normalizeExport,
1142
+ optimizationArguments
1143
+ });