@pulse-compute/provider-fastly 1.0.0-beta.5 → 1.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +6 -6
  2. package/dist/config-schema.json +7 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -0
  6. package/dist/index.js.map +1 -1
  7. package/package.json +11 -11
  8. package/src/build/canonical-target.js +10 -2
  9. package/src/build/effect-invocations.js +49 -0
  10. package/src/build/embedded-assets.as.ts +32 -0
  11. package/src/build/kv-native.as.ts +18 -8
  12. package/src/build/native-application-errors.js +49 -26
  13. package/src/build/native-http-effects.js +41 -12
  14. package/src/build/native-platform-capabilities.js +346 -120
  15. package/src/build/native-string-values.js +1 -1
  16. package/src/build/native-value-budget.js +58 -0
  17. package/src/build/package-native-application.js +94 -0
  18. package/src/build/request-budget.js +3 -1
  19. package/src/build/s3-body.as.ts +97 -0
  20. package/src/build/s3-native.as.ts +9 -3
  21. package/src/build/s3-native.js +2 -2
  22. package/src/build/schema-admission.js +83 -0
  23. package/src/build/schema-nested-json.js +93 -0
  24. package/src/build/schema-scalar-record.js +41 -0
  25. package/src/build/wasm-budget.js +28 -0
  26. package/src/config-api.js +2 -0
  27. package/src/config-schema.json +7 -0
  28. package/src/index.ts +5 -0
  29. package/src/javascript/jwt-verifier.js +14 -4
  30. package/src/javascript/package-effects.js +1 -1
  31. package/src/javascript/target-support-policy.js +11 -5
  32. package/src/javascript/target.js +10 -0
  33. package/src/provider-contract.js +4 -2
  34. package/src/testing/native-http-effects-host.js +5 -1
  35. package/src/testing/native-platform-capabilities-host.js +24 -3
  36. package/src/toolchain/config.js +4 -0
  37. package/src/toolchain/index.js +30 -1
  38. package/src/toolchain/native-execution.js +1 -0
  39. package/src/toolchain/s3.js +2 -2
@@ -1,5 +1,12 @@
1
1
  'use strict';
2
+
3
+ const { scalarRecordProjectionLines } = require('./schema-scalar-record.js');
4
+ const { hasJsonAdmission, tracksJsonDuplicates, schemaParserDepth, nestedJsonProjectionLines, schemaJsonAdmissionSource, schemaFetchJsonSource } = require('./schema-nested-json.js');
2
5
  const applicationErrors = require('./native-application-errors.js');
6
+ const effectInvocations = require('./effect-invocations.js');
7
+ const packageApplication = require('./package-native-application.js');
8
+ const nativeValueBudget = require('./native-value-budget.js');
9
+ const { schemaNeedsValueProjection } = require('@pulse-compute/wasm-contracts/schema-json/registry');
3
10
 
4
11
  const crypto = require('node:crypto');
5
12
  const { nativeStringFields, nativeStringConcat, nativeStringTrim, nativeStringIndex, needsNativeValueFailureGuard } = require('./native-string-values.js');
@@ -92,7 +99,10 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION = 'pulse.fastly-native-platfor
92
99
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_GENERATOR_VERSION = 'pulse.fastly-native-platform-capabilities-generator.v1';
93
100
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION = 'pulse.fastly-native-platform-capabilities-compiler.v1';
94
101
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION = 1;
95
- const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = 1024 * 1024;
102
+ // Internal compilation budget, not a Fastly service limit. Keep a finite guard
103
+ // while allowing composed applications beyond the original 1 MiB proof fixture.
104
+ const { DEFAULT_FASTLY_MAX_WASM_BYTES, normalizeFastlyMaxWasmBytes, assertFastlyWasmBudget } = require('./wasm-budget.js');
105
+ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = DEFAULT_FASTLY_MAX_WASM_BYTES;
96
106
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES = 65536;
97
107
  const FASTLY_NATIVE_SIZE_OPTIMIZATION = EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION;
98
108
 
@@ -156,9 +166,11 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KIND = Object.freeze({
156
166
  'grip.broadcast': 9,
157
167
  'assets.lookup': 10,
158
168
  'jwt.verify': 11,
169
+ 'jwt.sign': 20,
159
170
  's3.head': 12,
160
171
  's3.getText': 13,
161
172
  's3.putText': 14,
173
+ 's3.getBody': 21,
162
174
  'kv.getVersioned': 15,
163
175
  'kv.insertIfAbsent': 16,
164
176
  'kv.compareAndSwap': 17,
@@ -185,9 +197,11 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
185
197
  'grip.broadcast',
186
198
  'assets.lookup',
187
199
  'jwt.verify',
200
+ 'jwt.sign',
188
201
  's3.head',
189
202
  's3.getText',
190
- 's3.putText'
203
+ 's3.putText',
204
+ 's3.getBody'
191
205
  ]);
192
206
 
193
207
  const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
@@ -204,9 +218,11 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
204
218
  'grip.publish',
205
219
  'grip.broadcast',
206
220
  'jwt.verify',
221
+ 'jwt.sign',
207
222
  's3.head',
208
223
  's3.getText',
209
- 's3.putText'
224
+ 's3.putText',
225
+ 's3.getBody'
210
226
  ]);
211
227
 
212
228
  class FastlyNativePlatformCapabilitiesError extends Error {
@@ -268,45 +284,38 @@ function packageFacts(_plan, options = {}) {
268
284
  });
269
285
  }
270
286
 
287
+ function jwtVerificationAlgorithm(effect) {
288
+ return effect.resource.keyType === 'secret' ? 'HS256' : String(effect.resource.keyArtifactId).startsWith('pulse.jwt.rs256-key.') ? 'RS256' : 'ES256';
289
+ }
290
+
271
291
  function selectedJwtCrypto(plan) {
272
- const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
273
- if (!hasJwt) return undefined;
274
- const algorithms = plan.crypto && Array.isArray(plan.crypto.algorithms)
275
- ? plan.crypto.algorithms.filter((entry) => ['HS256', 'ES256'].includes(entry.algorithm))
276
- : [];
277
- if (algorithms.length !== 1) {
278
- fail(
279
- 'Fastly Native JWT requires one exact crypto realization.',
280
- 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID',
281
- { algorithms, automaticFallback: false }
282
- );
283
- }
284
- const selected = algorithms[0];
285
- const hs256 = selected.algorithm === 'HS256'
286
- && selected.realization === 'guest-source:pulse-hmac-as'
287
- && selected.implementation === 'pulse-hmac-as.v1'
288
- && selected.kind === 'guest-source'
289
- && selected.targetImplemented === true;
290
- const es256 = selected.algorithm === 'ES256'
291
- && selected.realization === 'guest-linked:pulse-es256-rustcrypto-p256'
292
- && selected.implementation === 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1'
293
- && selected.kind === 'guest-linked'
294
- && selected.targetImplemented === true;
295
- if ((!hs256 && !es256) || selected.automaticFallback !== false) {
296
- fail(
297
- 'Fastly Native JWT selected an unavailable or non-exact crypto realization.',
298
- 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID',
299
- { selected, automaticFallback: false }
300
- );
292
+ const effects = plan.effects || [];
293
+ const signing = effects.filter(effect => effect.kind === 'jwt.sign');
294
+ const verifies = effects.filter(effect => effect.kind === 'jwt.verify');
295
+ const required = new Set([
296
+ ...signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm') === 'HS256' ? 'HMAC-SHA256' : literalObjectField(inputExpression(effect, 'payload'), 'algorithm')),
297
+ ...verifies.map(effect => jwtVerificationAlgorithm(effect))
298
+ ]);
299
+ if (!required.size) return undefined;
300
+ const definitions = {
301
+ 'HMAC-SHA256': ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
302
+ HS256: ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
303
+ RS256: ['guest-linked:pulse-rs256-bearssl-i31', 'bearssl.0.6.rsa-i31.sha256.v1', 'guest-linked'],
304
+ ES256: ['guest-linked:pulse-es256-rustcrypto-p256', 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1', 'guest-linked']
305
+ };
306
+ for (const algorithm of required) {
307
+ const selected = plan.crypto?.algorithms.find(entry => entry.algorithm === algorithm);
308
+ const expected = definitions[algorithm];
309
+ if (!expected || !selected || selected.realization !== expected[0] || selected.implementation !== expected[1]
310
+ || selected.kind !== expected[2] || selected.targetImplemented !== true || selected.automaticFallback !== false) {
311
+ fail('JWT requires its explicitly selected Crypto realization.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
312
+ }
301
313
  }
302
- return Object.freeze({
303
- algorithm: selected.algorithm,
304
- realization: selected.realization,
305
- implementation: selected.implementation,
306
- kind: selected.kind,
307
- guestUnitRequired: es256,
308
- automaticFallback: false
309
- });
314
+ const verificationAlgorithms = [...new Set(verifies.map(effect => jwtVerificationAlgorithm(effect)))];
315
+ if (verificationAlgorithms.length > 1) fail('Fastly Native JWT verification requires one exact algorithm.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
316
+ const algorithm = verificationAlgorithms[0] || (required.has('RS256') ? 'RS256' : required.has('ES256') ? 'ES256' : 'HMAC-SHA256');
317
+ return Object.freeze({ ...plan.crypto.algorithms.find(entry => entry.algorithm === algorithm),
318
+ guestUnitRequired: required.has('ES256') || required.has('RS256'), signingAlgorithms: signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm')) });
310
319
  }
311
320
 
312
321
  function decodeP256Coordinate(value, artifactId, field) {
@@ -328,6 +337,26 @@ function decodeP256Coordinate(value, artifactId, field) {
328
337
  return bytes;
329
338
  }
330
339
 
340
+ function normalizedRs256Jwk(value, artifactId) {
341
+ const invalid = () => fail('Invalid bounded RSA public key artifact.', 'PULSE_FASTLY_NATIVE_JWT_KEY_ARTIFACT_INVALID', { artifactId });
342
+ if (!value || typeof value !== 'object' || Array.isArray(value)
343
+ || Object.keys(value).some(name => !['kty', 'n', 'e', 'alg', 'use', 'key_ops', 'kid'].includes(name))
344
+ || value.kty !== 'RSA' || value.alg !== undefined && value.alg !== 'RS256'
345
+ || value.use !== undefined && value.use !== 'sig'
346
+ || value.key_ops !== undefined && (!Array.isArray(value.key_ops) || value.key_ops.length !== 1 || value.key_ops[0] !== 'verify')
347
+ || value.kid !== undefined && (typeof value.kid !== 'string' || !value.kid.length || value.kid.includes('\0') || Buffer.byteLength(value.kid) > 256)) invalid();
348
+ const decode = (text, maximum) => {
349
+ if (typeof text !== 'string' || text.length > Math.ceil(maximum * 4 / 3) || !/^[A-Za-z0-9_-]+$/.test(text)) invalid();
350
+ const bytes = Buffer.from(text, 'base64url');
351
+ if (!bytes.length || bytes.length > maximum || bytes[0] === 0 || bytes.toString('base64url') !== text) invalid();
352
+ return bytes;
353
+ };
354
+ const n = decode(value.n, 512), e = decode(value.e, 4), k = n.length;
355
+ if (![256, 384, 512].includes(k) || n[0] < 128 || !(n[k-1] & 1) || e.readUIntBE(0, e.length) < 3 || !(e.at(-1) & 1)) invalid();
356
+ const bytes = Buffer.alloc(8+k); bytes.writeUInt32LE(k); n.copy(bytes, 4); e.copy(bytes, 8+k-e.length);
357
+ return Object.freeze({ kid: value.kid, bytes });
358
+ }
359
+
331
360
  function normalizedEs256Jwk(value, artifactId) {
332
361
  if (
333
362
  !value
@@ -374,13 +403,14 @@ function resolveEs256KeyArtifacts(plan, facts) {
374
403
  }
375
404
  const artifactId = effect.resource.keyArtifactId;
376
405
  const artifact = artifacts.get(artifactId);
406
+ const algorithm = jwtVerificationAlgorithm(effect).toLowerCase();
377
407
  if (
378
408
  !artifact
379
- || artifact.version !== 'pulse.jwt-es256-key-artifact.v1'
409
+ || artifact.version !== `pulse.jwt-${algorithm}-key-artifact.v1`
380
410
  || artifact.contractId !== 'pulse.jwt'
381
411
  || artifact.package !== '@pulse-compute/jwt'
382
- || artifact.kind !== 'jwt-es256-static-public-key'
383
- || artifact.mediaType !== 'application/vnd.pulse.jwt-es256-key+json'
412
+ || artifact.kind !== `jwt-${algorithm}-static-public-key`
413
+ || artifact.mediaType !== `application/vnd.pulse.jwt-${algorithm}-key+json`
384
414
  || artifact.id !== artifactId
385
415
  || artifact.materialHash !== sha256(stableStringify(artifact.data))
386
416
  || !artifact.data
@@ -406,7 +436,7 @@ function resolveEs256KeyArtifacts(plan, facts) {
406
436
  { effectId: effect.id, artifactId, keyCount: keys && keys.length }
407
437
  );
408
438
  }
409
- const normalized = keys.map((key) => normalizedEs256Jwk(key, artifactId));
439
+ const normalized = keys.map((key) => algorithm === 'rs256' ? normalizedRs256Jwk(key, artifactId) : normalizedEs256Jwk(key, artifactId));
410
440
  const kids = normalized.map((key) => key.kid).filter((kid) => kid !== undefined);
411
441
  if (new Set(kids).size !== kids.length) {
412
442
  fail(
@@ -482,8 +512,71 @@ function es256KeyArtifactSource(records) {
482
512
  return lines.join('\n');
483
513
  }
484
514
 
515
+ function jwtSigningCryptoCompositionSource(selected) {
516
+ const algorithms = selected?.signingAlgorithms || [];
517
+ const hs = algorithms.includes('HS256');
518
+ const es = algorithms.includes('ES256');
519
+ const rs = algorithms.includes('RS256');
520
+ return `function __pulse_fastly_jwt_crypto_sign(algorithm: string, key: Uint8Array, data: Uint8Array, output: Uint8Array): i32 {
521
+ ${hs ? `if (algorithm == "HS256") return pulse_crypto_hmac_sha256_bytes_v1(key.dataStart, key.length, data.dataStart, data.length, output.dataStart, output.length)` : ''}
522
+ ${rs ? `if (algorithm == "RS256") return __pulse_fastly_rs256(true, key, data, output)` : ''}
523
+ ${es ? `if (algorithm == "ES256") {
524
+ if (key.length != 96 || output.length != 64 || data.length > 16340) return -2
525
+ const pointer: usize = 524288
526
+ memory.fill(pointer, 0, 16640)
527
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
528
+ store<u32>(pointer + 12, (224 + data.length + 15) & ~15); store<u32>(pointer + 16, 1)
529
+ store<u32>(pointer + 24, 224); store<u32>(pointer + 28, data.length)
530
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, 96)
531
+ store<u32>(pointer + 40, 160); store<u32>(pointer + 44, 64)
532
+ memory.copy(pointer + 64, key.dataStart, 96); memory.copy(pointer + 224, data.dataStart, data.length)
533
+ const status = __pulse_crypto_es256_sign_anchor(i32(pointer), 16640)
534
+ if (status == 1) memory.copy(output.dataStart, pointer + 160, 64)
535
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
536
+ return status
537
+ }` : ''}
538
+ return -3
539
+ }`;
540
+ }
541
+
485
542
  function jwtCryptoCompositionSource(selected) {
486
- if (!selected) {
543
+ const rsa = selected && (selected.algorithm === 'RS256' || selected.signingAlgorithms?.includes('RS256'));
544
+ const helper = rsa ? rs256CompositionSource() : '';
545
+ return helper + jwtVerificationCompositionSource(selected);
546
+ }
547
+
548
+ function rs256CompositionSource() {
549
+ return `function __pulse_fastly_rs256(signing: bool, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
550
+ if (key.length < 8 || data.length > 12288) return -2
551
+ const k = load<u32>(key.dataStart)
552
+ if ((k != 256 && k != 384 && k != 512) || signature.length != i32(k) || key.length != i32(signing ? 8 + k * 9 / 2 : 8 + k)) return -2
553
+ const pointer: usize = 524288
554
+ const output = (64 + key.length + 15) & ~15
555
+ const input = output + signature.length
556
+ memory.fill(pointer, 0, 16640)
557
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
558
+ store<u32>(pointer + 12, (input + data.length + 15) & ~15); store<u32>(pointer + 16, 2)
559
+ store<u32>(pointer + 24, input); store<u32>(pointer + 28, data.length)
560
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, key.length)
561
+ store<u32>(pointer + 40, output); store<u32>(pointer + 44, k)
562
+ memory.copy(pointer + 64, key.dataStart, key.length); memory.copy(pointer + input, data.dataStart, data.length)
563
+ if (!signing) memory.copy(pointer + output, signature.dataStart, signature.length)
564
+ const status = signing ? __pulse_crypto_rs256_sign_anchor(i32(pointer), 16640) : __pulse_crypto_rs256_verify_anchor(i32(pointer), 16640)
565
+ if (signing && status == 1) memory.copy(signature.dataStart, pointer + output, signature.length)
566
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
567
+ return status
568
+ }\n`;
569
+ }
570
+
571
+ function jwtVerificationCompositionSource(selected) {
572
+ if (selected?.algorithm === 'RS256') return `
573
+ function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { __pulse_fastly_jwt_record_signing_input(input.byteLength, "") }
574
+ function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
575
+ if (algorithm != "RS256") return -2
576
+ return __pulse_fastly_rs256(false, key, data, signature)
577
+ }`;
578
+
579
+ if (!selected || selected.algorithm === 'HMAC-SHA256') {
487
580
  return [
488
581
  'function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { void input }',
489
582
  'function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 { void algorithm; void key; void data; void signature; return -3 }'
@@ -596,11 +689,10 @@ function validatePlanBoundary(plan, options = {}) {
596
689
  && effect.kind === 'assets.lookup';
597
690
  const jwt = effect.package === '@pulse-compute/jwt'
598
691
  && effect.contractId === 'pulse.jwt'
599
- && effect.kind === 'jwt.verify'
600
- && effect.operation === 'verify'
601
- && effect.capability === 'jwt.verify';
692
+ && ['verify', 'sign'].includes(effect.operation)
693
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
602
694
  const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
603
- && ['head', 'getText', 'putText'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
695
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
604
696
  const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
605
697
  && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
606
698
  return !grip && !assets && !jwt && !s3 && !digest;
@@ -620,11 +712,10 @@ function validatePlanBoundary(plan, options = {}) {
620
712
  && effect.kind === 'assets.lookup';
621
713
  const jwt = effect.package === '@pulse-compute/jwt'
622
714
  && effect.contractId === 'pulse.jwt'
623
- && effect.kind === 'jwt.verify'
624
- && effect.operation === 'verify'
625
- && effect.capability === 'jwt.verify';
715
+ && ['verify', 'sign'].includes(effect.operation)
716
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
626
717
  const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
627
- && ['head', 'getText', 'putText'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
718
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
628
719
  const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
629
720
  && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
630
721
  return !grip && !assets && !jwt && !s3 && !digest;
@@ -632,7 +723,7 @@ function validatePlanBoundary(plan, options = {}) {
632
723
  if (unsupportedDeclarations.length > 0) {
633
724
  fail('Fastly native package realization is restricted to the Assets, GRIP, JWT, S3, and Crypto digest contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', { effects: unsupportedDeclarations });
634
725
  }
635
- for (const effect of effects.filter((entry) => entry.kind === 'jwt.verify')) {
726
+ for (const effect of effects.filter((entry) => ['jwt.verify', 'jwt.sign'].includes(entry.kind))) {
636
727
  const resource = effect.resource;
637
728
  const knownShape = resource
638
729
  && !Object.keys(resource)
@@ -647,7 +738,7 @@ function validatePlanBoundary(plan, options = {}) {
647
738
  && typeof resource.keyArtifactId === 'string'
648
739
  && resource.keyArtifactId.length > 0
649
740
  && resource.secretBinding === null;
650
- if (!namedSecret && !staticPublicKey) {
741
+ if (!namedSecret && (!staticPublicKey || effect.kind === 'jwt.sign')) {
651
742
  fail('Fastly Native JWT requires one exact named-secret or bounded static public-key descriptor.', 'PULSE_FASTLY_NATIVE_JWT_KEY_DESCRIPTOR_INVALID', {
652
743
  effectId: effect.id,
653
744
  resource,
@@ -713,8 +804,8 @@ function resolveCapabilityBindings(plan, options = {}) {
713
804
  const effects = plan.effects || [];
714
805
  const hasConfig = effects.some((effect) => effect.kind === 'config.get');
715
806
  const hasSecret = effects.some((effect) => (
716
- effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText'].includes(effect.kind)
717
- || (effect.kind === 'jwt.verify' && effect.resource && effect.resource.keyType === 'secret')
807
+ effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)
808
+ || (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource && effect.resource.keyType === 'secret')
718
809
  ));
719
810
  const hasGripHold = effects.some((effect) => effect.kind === 'grip.hold');
720
811
  const hasGripBroadcast = effects.some((effect) => effect.kind === 'grip.broadcast');
@@ -740,6 +831,7 @@ function resolveCapabilityBindings(plan, options = {}) {
740
831
  for (const [index, effect] of effects.entries()) {
741
832
  if (effect.kind !== 'kv.get' && effect.kind !== 'kv.put' && !conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) && effect.kind !== 'assets.lookup') continue;
742
833
  const packagePayload = inputExpression(effect, 'payload');
834
+ if (effect.kind === 'assets.lookup' && literalObjectField(packagePayload, 'embeddedId')) continue;
743
835
  const logical = effect.resource && effect.resource.kind === 'literal'
744
836
  && effect.kind !== 'assets.lookup'
745
837
  ? String(effect.resource.value)
@@ -838,7 +930,7 @@ function resolveCapabilityBindings(plan, options = {}) {
838
930
  maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs),
839
931
  configStore,
840
932
  secretStore,
841
- s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload: { contentType: literalObjectField(inputExpression(effect, 'payload'), 'contentType') || undefined } })), source.s3),
933
+ s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload: Object.fromEntries(['contentType', 'method', 'range', 'ifNoneMatch'].map(name => [name, literalObjectField(inputExpression(effect, 'payload'), name) ?? undefined])) })), source.s3),
842
934
  kv: Object.freeze(kv),
843
935
  fetch: Object.freeze(fetch),
844
936
  backends: Object.freeze(backends),
@@ -872,7 +964,7 @@ function planRequiresFetchBodyRead(plan) {
872
964
  if (Array.isArray(value)) return value.some(visit);
873
965
  return Object.values(value).some(visit);
874
966
  }
875
- return visit(plan.entry);
967
+ return visit(plan.entry) || visit(plan.handlers) || visit(plan.stages) || visit(plan.helpers);
876
968
  }
877
969
 
878
970
  function requiredImportsForPlan(plan, bindings) {
@@ -900,7 +992,7 @@ function requiredImportsForPlan(plan, bindings) {
900
992
  const gripAuthenticationRequired = kinds.has('grip.broadcast')
901
993
  && (!bindings || Boolean(bindings.grip && bindings.grip.authentication));
902
994
  const jwtSecretRequired = (plan.effects || []).some((effect) => (
903
- effect.kind === 'jwt.verify'
995
+ ['jwt.verify', 'jwt.sign'].includes(effect.kind)
904
996
  && effect.resource
905
997
  && effect.resource.keyType === 'secret'
906
998
  ));
@@ -908,7 +1000,7 @@ function requiredImportsForPlan(plan, bindings) {
908
1000
  for (const key of ['fastly_secret_store:open', 'fastly_secret_store:get', 'fastly_secret_store:plaintext']) keys.add(key);
909
1001
  }
910
1002
  for (const kind of conditionalKv.KV_CONDITIONAL_KINDS) if (kinds.has(kind)) for (const key of conditionalKv.kvImports(kind)) keys.add(key);
911
- if (kinds.has('jwt.verify') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
1003
+ if (kinds.has('jwt.verify') || kinds.has('jwt.sign') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
912
1004
  if (kinds.has('kv.get') || kinds.has('kv.put') || kinds.has('assets.lookup')) keys.add('fastly_kv_store:open');
913
1005
  if (kinds.has('kv.get') || kinds.has('assets.lookup')) for (const key of ['fastly_kv_store:lookup', 'fastly_kv_store:lookup_wait_v2', 'fastly_http_body:read']) keys.add(key);
914
1006
  if (kinds.has('kv.put')) for (const key of ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait']) keys.add(key);
@@ -917,7 +1009,7 @@ function requiredImportsForPlan(plan, bindings) {
917
1009
  keys.add('fastly_log:endpoint_get');
918
1010
  keys.add('fastly_log:write');
919
1011
  }
920
- if (kinds.has('s3.head') || kinds.has('s3.getText') || kinds.has('s3.putText')) for (const key of require('./s3-native.js').S3_IMPORTS) keys.add(key);
1012
+ if (kinds.has('s3.head') || kinds.has('s3.getText') || kinds.has('s3.putText') || kinds.has('s3.getBody')) for (const key of require('./s3-native.js').S3_IMPORTS) keys.add(key);
921
1013
  return Object.freeze([...keys].sort());
922
1014
  }
923
1015
 
@@ -928,15 +1020,40 @@ function stripPulseHostImports(source) {
928
1020
  .join('\n');
929
1021
  }
930
1022
 
1023
+ function schemaTextEncodeIds(plan) {
1024
+ // Keep the PS3 accounting/failure boundary and all unproved codec families.
1025
+ if (nativeValueBudget.hasBoundedReadLoop(plan)) return [];
1026
+ function closedText(node, depth = 0) {
1027
+ if (depth > 16) return false;
1028
+ if (node.kind === 'string' || node.kind === 'boolean') return true;
1029
+ if (node.kind === 'nullable') return closedText(node.value, depth);
1030
+ if (node.kind === 'array') return closedText(node.element, depth + 1);
1031
+ return node.kind === 'object' && !node.additionalProperties
1032
+ && node.fields.every(field => closedText(field.value, depth + 1));
1033
+ }
1034
+ return ((plan.schemas && plan.schemas.registry && plan.schemas.registry.schemas) || [])
1035
+ .filter(schema => !schema.jsonLimits && schema.root.kind === 'object'
1036
+ && schemaNeedsValueProjection(schema.root) && closedText(schema.root))
1037
+ .map(schema => schema.id);
1038
+ }
1039
+
931
1040
  function generateSchemaRuntime(plan) {
932
1041
  const registry = plan.schemas && plan.schemas.registry ? plan.schemas.registry : { schemas: [], maxBytes: FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES, contentTypePolicy: 'accept-json-or-missing' };
933
1042
  const schemas = registry.schemas || [];
1043
+ const textEncodeIds = new Set(schemaTextEncodeIds(plan));
934
1044
  const nodeLines = [];
1045
+ const scalarProjectors = new Map();
1046
+ const flatObjectProjectors = new Map();
1047
+ const scalarKinds = ['string', 'boolean', 'i32', 'u32', 'f64', 'string-enum'];
935
1048
  let nodeIndex = 0;
936
1049
 
937
1050
  function emitNode(schemaIndex, node) {
938
1051
  const currentIndex = nodeIndex++;
939
1052
  const functionName = `__pulse_fastly_schema_${schemaIndex}_${currentIndex}`;
1053
+ const flatObject = node.kind === 'object' && !node.additionalProperties
1054
+ && node.fields.every(field => scalarKinds.includes(field.value.kind));
1055
+ // Locals belong to this function, so equivalent flat bodies need no node ID.
1056
+ const localIndex = flatObject ? 'flat' : currentIndex;
940
1057
  const child = node.kind === 'nullable'
941
1058
  ? emitNode(schemaIndex, node.value)
942
1059
  : node.kind === 'array'
@@ -945,32 +1062,43 @@ function generateSchemaRuntime(plan) {
945
1062
  const fields = node.kind === 'object'
946
1063
  ? node.fields.map((field) => Object.freeze({ field, apply: emitNode(schemaIndex, field.value) }))
947
1064
  : [];
948
- const body = [`function ${functionName}(valueHandle: i32): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
1065
+ const body = [`function ${functionName}(valueHandle: i32, checkDuplicates: bool): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
949
1066
  if (node.kind === 'nullable') {
950
1067
  body.push(' if (input.kind == PULSE_VALUE_NULL) return valueHandle');
951
- body.push(` return ${child}(valueHandle)`);
1068
+ body.push(` return ${child}(valueHandle, checkDuplicates)`);
952
1069
  } else if (node.kind === 'array') {
953
1070
  body.push(' if (input.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
954
1071
  body.push(' const output = host_value_array()');
955
1072
  body.push(' for (let index = 0; index < input.values.length; index += 1) {');
956
- body.push(` const item = ${child}(unchecked(input.values[index]))`);
1073
+ body.push(` const item = ${child}(unchecked(input.values[index]), checkDuplicates)`);
957
1074
  body.push(' if (item <= 0) return 0');
958
1075
  body.push(' host_value_array_push(output, item)');
959
1076
  body.push(' }');
960
1077
  body.push(' return output');
1078
+ } else if (node.kind === 'json-value' || node.kind === 'json-object') {
1079
+ body.push(...nestedJsonProjectionLines(node));
1080
+ } else if (node.kind === 'scalar-record') {
1081
+ body.push(...scalarRecordProjectionLines(node));
961
1082
  } else if (node.kind === 'object') {
962
1083
  body.push(' if (input.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 50, -1); return 0 }');
1084
+ if (node.additionalProperties) body.push(' if (checkDuplicates && input.duplicateJsonKeys) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }');
963
1085
  body.push(' const output = host_value_object()');
964
1086
  fields.forEach(({ field, apply }, fieldIndex) => {
965
- body.push(` const key_${currentIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
1087
+ body.push(` const key_${localIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
966
1088
  if (!field.required) body.push(` if (__pulse_fastly_find(input, ${quote(field.name)}) >= 0) {`);
967
- body.push(` const field_${currentIndex}_${fieldIndex} = host_value_property(valueHandle, key_${currentIndex}_${fieldIndex})`);
968
- body.push(` if (__pulse_fastly_value(field_${currentIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
969
- body.push(` const projected_${currentIndex}_${fieldIndex} = ${apply}(field_${currentIndex}_${fieldIndex})`);
970
- body.push(` if (projected_${currentIndex}_${fieldIndex} <= 0) return 0`);
971
- body.push(` host_value_object_set(output, key_${currentIndex}_${fieldIndex}, projected_${currentIndex}_${fieldIndex})`);
1089
+ body.push(` const field_${localIndex}_${fieldIndex} = host_value_property(valueHandle, key_${localIndex}_${fieldIndex})`);
1090
+ body.push(` if (__pulse_fastly_value(field_${localIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
1091
+ body.push(` const projected_${localIndex}_${fieldIndex} = ${apply}(field_${localIndex}_${fieldIndex}, checkDuplicates)`);
1092
+ body.push(` if (projected_${localIndex}_${fieldIndex} <= 0) return 0`);
1093
+ body.push(` host_value_object_set(output, key_${localIndex}_${fieldIndex}, projected_${localIndex}_${fieldIndex})`);
972
1094
  if (!field.required) body.push(' }');
973
1095
  });
1096
+ if (node.additionalProperties) {
1097
+ body.push(' for (let index = 0; index < input.keys.length; index++) {', ' const key = unchecked(input.keys[index])');
1098
+ if (node.fields.length) body.push(` if (${node.fields.map(field => `key == ${quote(field.name)}`).join(' || ')}) continue`);
1099
+ body.push(' const item = __pulse_fastly_copy_json(unchecked(input.values[index]), checkDuplicates)',
1100
+ ' if (item <= 0) return 0', ' host_value_object_set(output, __pulse_fastly_string_value(key), item)', ' }');
1101
+ }
974
1102
  body.push(' return output');
975
1103
  } else if (node.kind === 'string') {
976
1104
  body.push(' if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
@@ -995,6 +1123,32 @@ function generateSchemaRuntime(plan) {
995
1123
  throw new TypeError(`Unsupported Fastly native schema node ${String(node.kind)}.`);
996
1124
  }
997
1125
  body.push('}', '');
1126
+ // Only scalar validators preserve the original handle without allocating or
1127
+ // calling child projectors. Compare the exact emitted body, including enum
1128
+ // values/order and error stages.
1129
+ if (scalarKinds.includes(node.kind)) {
1130
+ const key = body.slice(1).join('\n');
1131
+ const existing = scalarProjectors.get(key);
1132
+ if (existing) return existing;
1133
+ const sharedName = `__pulse_fastly_schema_scalar_${scalarProjectors.size}`;
1134
+ scalarProjectors.set(key, sharedName);
1135
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1136
+ nodeLines.push(...body);
1137
+ return sharedName;
1138
+ }
1139
+ // Share code only after child scalar identities are resolved. Exact bodies
1140
+ // retain field order, presence checks, validation and errors. Each call still
1141
+ // allocates its own output; open/nested/nullable/array bodies remain separate.
1142
+ if (flatObject) {
1143
+ const key = body.slice(1).join('\n');
1144
+ const existing = flatObjectProjectors.get(key);
1145
+ if (existing) return existing;
1146
+ const sharedName = `__pulse_fastly_schema_flat_object_${flatObjectProjectors.size}`;
1147
+ flatObjectProjectors.set(key, sharedName);
1148
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1149
+ nodeLines.push(...body);
1150
+ return sharedName;
1151
+ }
998
1152
  nodeLines.push(...body);
999
1153
  return functionName;
1000
1154
  }
@@ -1004,17 +1158,22 @@ function generateSchemaRuntime(plan) {
1004
1158
  `const __PULSE_SCHEMA_MAX_BYTES: i32 = ${Number(registry.maxBytes || FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES)}`,
1005
1159
  `const __PULSE_SCHEMA_REQUIRE_JSON: bool = ${registry.contentTypePolicy === 'require-json' ? 'true' : 'false'}`,
1006
1160
  '',
1161
+ schemaJsonAdmissionSource(plan),
1162
+ schemaFetchJsonSource(),
1007
1163
  ...nodeLines,
1008
- 'function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool): i32 {',
1164
+ `function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool${textEncodeIds.size ? ', textResult: bool = false' : ''}): i32 {`,
1009
1165
  ' if (schemaId.length == 0) return valueHandle'
1010
1166
  ];
1011
1167
  for (const [schemaIndex, schema] of schemas.entries()) {
1012
1168
  lines.push(`${schemaIndex === 0 ? ' if' : ' else if'} (schemaId == ${quote(schema.id)}) {`);
1013
- lines.push(` const projected = ${roots[schemaIndex]}(valueHandle)`);
1169
+ if (schema.jsonLimits) lines.push(` if (__pulse_fastly_admit_bounded_value(valueHandle, __pulse_schema_json_limits_${schemaIndex}()).failure != 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }`);
1170
+ lines.push(` const projected = ${roots[schemaIndex]}(valueHandle, !encode)`);
1014
1171
  lines.push(' if (projected <= 0) return 0');
1015
1172
  lines.push(' const input = __pulse_fastly_json(projected, 0)');
1016
1173
  lines.push(` const normalized = encode ? __pulse_schema_encode_${schemaIndex}(input) : __pulse_schema_decode_${schemaIndex}(input)`);
1017
- lines.push(' return __pulse_fastly_parse_json(normalized)');
1174
+ if (textEncodeIds.has(schema.id)) lines.push(' if (textResult && encode) return __pulse_fastly_string_value(normalized)');
1175
+ if (schema.jsonLimits) lines.push(' const output = __pulse_fastly_parse_json(normalized)', ' if (output > 0) __pulse_fastly_deep_freeze(output)', ' return output');
1176
+ else lines.push(' return __pulse_fastly_parse_json(normalized)');
1018
1177
  lines.push(' }');
1019
1178
  }
1020
1179
  lines.push(' __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1)');
@@ -1037,7 +1196,7 @@ function effectResultSource(plan, bindings) {
1037
1196
  lines.push(`function __pulse_fastly_secret_store(effectIndex: i32): string { return ${quote(bindings.secretStore)} }`, '');
1038
1197
  lines.push('function __pulse_fastly_jwt_secret_binding(effectIndex: i32): string {', ' switch (effectIndex) {');
1039
1198
  for (const [index, effect] of (plan.effects || []).entries()) {
1040
- if (effect.kind === 'jwt.verify' && effect.resource.keyType === 'secret') {
1199
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource.keyType === 'secret') {
1041
1200
  lines.push(` case ${index}: return ${quote(effect.resource.secretBinding)}`);
1042
1201
  }
1043
1202
  }
@@ -1052,12 +1211,31 @@ function effectResultSource(plan, bindings) {
1052
1211
  for (const binding of bindings.grip.publish) lines.push(` case ${binding.index}: return ${quote(binding.authSecretRef || '')}`);
1053
1212
  lines.push(' default: return ""', ' }', '}', '');
1054
1213
 
1214
+ // A config lookup has the same ready-result handoff at every static site.
1215
+ // Keep the site indices in the switch; pass the active index through so the
1216
+ // pending slot and any state error still belong to the original invocation.
1217
+ const configSites = (plan.effects || []).flatMap((effect, index) => effect.kind === 'config.get' ? [index] : []);
1218
+ if (configSites.length > 1) {
1219
+ lines.push('function __pulse_fastly_resolve_config_get(effectIndex: i32): i32 {');
1220
+ lines.push(' const response = __pulse_fastly_wait_ready(effectIndex)');
1221
+ lines.push(' if (response <= 0) return 0');
1222
+ lines.push(' return response');
1223
+ lines.push('}', '');
1224
+ }
1225
+
1055
1226
  lines.push('function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {');
1056
1227
  lines.push(' switch (effectIndex) {');
1057
1228
  for (const [index, effect] of (plan.effects || []).entries()) {
1229
+ if (effect.kind === 'config.get' && configSites.length > 1) {
1230
+ if (index === configSites[0]) {
1231
+ for (const site of configSites) lines.push(` case ${site}:`);
1232
+ lines.push(' return __pulse_fastly_resolve_config_get(effectIndex)');
1233
+ }
1234
+ continue;
1235
+ }
1058
1236
  const result = effect.result || {};
1059
1237
  const decoder = result.decoder;
1060
- const waiter = conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) ? '__pulse_fastly_kv_conditional_wait' : ['s3.head', 's3.getText', 's3.putText'].includes(effect.kind) ? '__pulse_fastly_s3_wait' : effect.kind === 'fetch'
1238
+ const waiter = conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) ? '__pulse_fastly_kv_conditional_wait' : ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind) ? '__pulse_fastly_s3_wait' : effect.kind === 'fetch'
1061
1239
  ? '__pulse_fastly_wait_fetch'
1062
1240
  : effect.kind === 'grip.publish'
1063
1241
  ? '__pulse_fastly_wait_grip_publish'
@@ -1099,9 +1277,11 @@ function effectDispatchSource(plan) {
1099
1277
  'config.get': '__pulse_fastly_config_begin',
1100
1278
  'secret.get': '__pulse_fastly_secret_begin',
1101
1279
  'jwt.verify': '__pulse_fastly_jwt_begin',
1280
+ 'jwt.sign': '__pulse_fastly_jwt_sign_begin',
1102
1281
  's3.head': '__pulse_fastly_s3_begin',
1103
1282
  's3.getText': '__pulse_fastly_s3_begin',
1104
1283
  's3.putText': '__pulse_fastly_s3_begin',
1284
+ 's3.getBody': '__pulse_fastly_s3_begin',
1105
1285
  'kv.get': '__pulse_fastly_kv_get_begin',
1106
1286
  'kv.put': '__pulse_fastly_kv_put_begin',
1107
1287
  ...Object.fromEntries(conditionalKv.KV_CONDITIONAL_KINDS.map(kind => [kind, '__pulse_fastly_kv_conditional_begin'])),
@@ -1135,7 +1315,14 @@ function effectDispatchSource(plan) {
1135
1315
  }
1136
1316
 
1137
1317
  function fastlyRuntimeSource(plan, bindings, options = {}) {
1318
+ const textEncodeIds = schemaTextEncodeIds(plan);
1138
1319
  const effectCount = (plan.effects || []).length;
1320
+ const hasAssets = (plan.effects || []).some(effect => effect.kind === 'assets.lookup');
1321
+ const assetBeginSource = hasAssets ? String.raw`function __pulse_fastly_assets_payload(payload: __PulseFastlyValue): __PulseFastlyValue { const value = __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")); if (value.kind != PULSE_VALUE_OBJECT) __pulse_fastly_fail(PULSE_ERROR_VALUE, 146, -1); return value }
1322
+ function __pulse_fastly_assets_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const value = __pulse_fastly_assets_payload(payload); if (__pulse_fastly_last_error != 0) return; if (__pulse_fastly_value(__pulse_fastly_payload_field(value, 'embeddedId')).kind != PULSE_VALUE_UNDEFINED) { __pulse_fastly_ready_effect(effectIndex, __pulse_fastly_embedded_asset(value)); return; } const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(value, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 147, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }` : '';
1323
+ const assetResultSource = hasAssets ? String.raw`function __pulse_fastly_asset_content_type(key: string): string { const dot = key.lastIndexOf("."); const extension = dot < 0 ? "" : key.substring(dot + 1).toLowerCase(); if (extension == "css") return "text/css; charset=utf-8"; if (extension == "html") return "text/html; charset=utf-8"; if (extension == "js" || extension == "mjs") return "text/javascript; charset=utf-8"; if (extension == "json") return "application/json; charset=utf-8"; if (extension == "svg") return "image/svg+xml"; if (extension == "txt") return "text/plain; charset=utf-8"; if (extension == "webp") return "image/webp"; if (extension == "png") return "image/png"; if (extension == "jpg" || extension == "jpeg") return "image/jpeg"; if (extension == "gif") return "image/gif"; if (extension == "ico") return "image/x-icon"; return "application/octet-stream" }
1324
+ function __pulse_fastly_asset_text(text: string): string { const parser = new __PulseJsonParser(text); const parsed = parser.parse(); if (parser.failed || parsed <= 0) return text; const object = __pulse_fastly_value(parsed); if (object.kind != PULSE_VALUE_OBJECT) return text; const marker = __pulse_fastly_find(object, "__pulseKv"); const value = __pulse_fastly_find(object, "value"); if (marker < 0 || value < 0 || __pulse_fastly_number(unchecked(object.values[marker])) != 1.0) return text; return __pulse_fastly_string(unchecked(object.values[value])) }
1325
+ function __pulse_fastly_wait_assets(effectIndex: i32): i32 { if (__pulse_fastly_pending_mode[effectIndex] == PULSE_FASTLY_PENDING_READY) return __pulse_fastly_wait_ready(effectIndex); if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 148, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const bodyOut = __pulse_fastly_out_i32(); const metadata = new Uint8Array(1); const written = __pulse_fastly_out_i32(); const generation = new StaticArray<i64>(1); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(bodyOut), metadata.dataStart, 0, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 149, effectIndex); return 0 } const outer = __pulse_fastly_value(unchecked(__pulse_fastly_payloads[effectIndex])); const payload = __pulse_fastly_assets_payload(outer); if (__pulse_fastly_last_error != 0) return 0; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; const errorCode = __pulse_fastly_out_value(kvError); output.status = errorCode == 3 ? 404 : 200; if (errorCode != 1 && errorCode != 3) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 150, effectIndex); return 0 } const headersHandle = __pulse_fastly_payload_field(payload, "headers"); const headers = __pulse_fastly_value(headersHandle); let hasContentType = false; if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); if (name.toLowerCase() == "content-type") hasContentType = true; output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } } if (!hasContentType) output.headers.push(new __PulseFastlyHeader("content-type", output.status == 200 ? __pulse_fastly_asset_content_type(key) : "text/plain; charset=utf-8")); const cacheHandle = __pulse_fastly_payload_field(payload, "cacheControl"); if (__pulse_fastly_value(cacheHandle).kind != PULSE_VALUE_UNDEFINED) output.headers.push(new __PulseFastlyHeader("cache-control", __pulse_fastly_string(cacheHandle))); const methodHandle = __pulse_fastly_payload_field(payload, "method"); const method = __pulse_fastly_value(methodHandle).kind == PULSE_VALUE_UNDEFINED ? "GET" : __pulse_fastly_string(methodHandle).toUpperCase(); if (output.status == 200 && method != "HEAD") { const text = __pulse_fastly_read_body(__pulse_fastly_out_value(bodyOut), effectIndex); if (__pulse_fastly_last_error != 0) return 0; output.text = __pulse_fastly_asset_text(text) } return __pulse_fastly_put(output) }` : '';
1139
1326
  const jwtCrypto = options.jwtCrypto || selectedJwtCrypto(plan);
1140
1327
  const es256KeyRecords = options.es256KeyRecords || Object.freeze([]);
1141
1328
  const jwtReality = options.jwtReality && options.jwtReality.enabled === true
@@ -1214,7 +1401,7 @@ function __pulse_fastly_jwt_send_error(): void {}
1214
1401
  const gripAuthorizationSource = gripAuthentication
1215
1402
  ? `const secret = __pulse_fastly_grip_secret(effectIndex, ${quote(gripAuthentication.secretRef)}); if (__pulse_fastly_last_error != 0) return; headers.keys.push("authorization"); headers.values.push(__pulse_fastly_string_value("Bearer " + secret));`
1216
1403
  : '';
1217
- return String.raw`
1404
+ const source = String.raw`
1218
1405
  @external("fastly_abi", "init") declare function fastly_abi_init(version: i64): i32
1219
1406
  @external("fastly_http_req", "body_downstream_get") declare function fastly_http_req_body_downstream_get(requestOut: usize, bodyOut: usize): i32
1220
1407
  @external("fastly_http_req", "method_get") declare function fastly_http_req_method_get(handle: i32, buffer: usize, bufferLength: i32, writtenOut: usize): i32
@@ -1286,6 +1473,7 @@ const PULSE_ERROR_JWT: i32 = 1008
1286
1473
  const PULSE_ERROR_REQUEST_BODY: i32 = 1009
1287
1474
 
1288
1475
  class __PulseFastlyValue {
1476
+ ${tracksJsonDuplicates(plan) ? 'duplicateJsonKeys: bool = false' : ''}
1289
1477
  kind: i32 = PULSE_VALUE_UNDEFINED
1290
1478
  boolean: i32 = 0
1291
1479
  number: f64 = 0.0
@@ -1305,6 +1493,9 @@ ${nativeStringFields}
1305
1493
  responseHandle: i32 = 0
1306
1494
  bodyHandle: i32 = 0
1307
1495
  bodyLoaded: i32 = 0
1496
+ s3Body: bool = false
1497
+ s3Deadline: i64 = 0
1498
+ s3Length: i32 = 0
1308
1499
  status: i32 = 200
1309
1500
  // Scalar handles do not need collection backing stores.
1310
1501
  private headersStorage: Array<__PulseFastlyHeader> | null = null
@@ -1312,6 +1503,7 @@ ${nativeStringFields}
1312
1503
  if (this.headersStorage === null) this.headersStorage = new Array<__PulseFastlyHeader>()
1313
1504
  return this.headersStorage!
1314
1505
  }
1506
+ binaryBody: Uint8Array | null = null
1315
1507
  immutable: bool = false
1316
1508
  }
1317
1509
  class __PulseFastlyHeader {
@@ -1328,7 +1520,7 @@ class __PulseJsonParser {
1328
1520
  parse(): i32 { this.skip(); const value = this.value(0); this.skip(); if (this.index != this.source.length) this.failed = true; return this.failed ? 0 : value }
1329
1521
  value(depth: i32): i32 {
1330
1522
  this.skip(); if (this.conditionalKv && (depth > ${conditionalKv.KV_CONDITIONAL_LIMITS.depth + 1} || ++this.entries > ${conditionalKv.KV_CONDITIONAL_LIMITS.entries + 2})) { this.failed = true; this.tooLarge = true; return 0 }
1331
- if (this.index >= this.source.length || (!this.conditionalKv && depth > ${plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64})) { this.failed = true; return 0 }
1523
+ if (this.index >= this.source.length || (!this.conditionalKv && depth > ${schemaParserDepth(plan, plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64)})) { this.failed = true; return 0 }
1332
1524
  const c = this.source.charCodeAt(this.index)
1333
1525
  if (c == 34) return __pulse_fastly_string_value(this.string())
1334
1526
  if (c == 123) return this.object(depth + 1)
@@ -1342,14 +1534,18 @@ class __PulseJsonParser {
1342
1534
  string(): string {
1343
1535
  // Find a bounded span, then decode each UTF-16 unit once. Concatenating one
1344
1536
  // character at a time exhausts stub/fixed memory on a valid maximum KV value.
1345
- const start = this.index + 1; let end = start;
1537
+ const start = this.index + 1; let end = start; let plain = true;
1346
1538
  while (end < this.source.length) {
1347
1539
  const c = this.source.charCodeAt(end);
1348
1540
  if (c == 34) break;
1349
- if (c == 92) end += 1;
1541
+ if (c == 92) { plain = false; end += 1; }
1542
+ else if (c < 32) plain = false;
1350
1543
  end += 1;
1351
1544
  }
1352
1545
  if (end >= this.source.length) { this.failed = true; return "" }
1546
+ // A validated span needs one owned string, without a temporary UTF-16 array.
1547
+ // Keep escaped and malformed spans on the existing decoder path.
1548
+ if (plain) { this.index = end + 1; return this.source.substring(start, end); }
1353
1549
  const output = new Uint16Array(end - start); let count = 0; this.index = start;
1354
1550
  while (this.index < end) {
1355
1551
  let c = this.source.charCodeAt(this.index++);
@@ -1427,6 +1623,7 @@ class __PulseJsonParser {
1427
1623
  const key = this.string(); this.skip()
1428
1624
  if (this.conditionalKv && __pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) { this.failed = true; break }
1429
1625
  if (this.index >= this.source.length || this.source.charCodeAt(this.index) != 58) { this.failed = true; break }
1626
+ ${tracksJsonDuplicates(plan) ? 'if (__pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) __pulse_fastly_value(output).duplicateJsonKeys = true' : ''}
1430
1627
  this.index += 1; host_value_object_set(output, __pulse_fastly_string_value(key), this.value(depth)); this.skip()
1431
1628
  if (this.index >= this.source.length) { this.failed = true; break }
1432
1629
  const c = this.source.charCodeAt(this.index); this.index += 1
@@ -1533,7 +1730,7 @@ function __pulse_fastly_quote(value: string): string {
1533
1730
  return String.UTF16.decodeUnsafe(out.dataStart, out.byteLength);
1534
1731
  }
1535
1732
  function __pulse_fastly_json_parts(handle: i32, depth: i32, parts: Array<string>): void {
1536
- if (depth > ${plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64}) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1); parts.push("null"); return }
1733
+ if (depth > ${schemaParserDepth(plan, plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64)}) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1); parts.push("null"); return }
1537
1734
  const value = __pulse_fastly_value(handle)
1538
1735
  if (value.kind == PULSE_VALUE_BOOLEAN) { parts.push(value.boolean != 0 ? "true" : "false"); return }
1539
1736
  if (value.kind == PULSE_VALUE_NUMBER) { parts.push(__pulse_fastly_number_string(value.number)); return }
@@ -1623,7 +1820,7 @@ function host_request_path(): i32 { return __pulse_fastly_string_value(__pulse_f
1623
1820
  function host_request_headers(): i32 { return __pulse_request_headers_read() }
1624
1821
  function host_request_header(name: i32): i32 { const value = __pulse_fastly_request_header_text(__pulse_fastly_string(name)); return value.length == 0 ? host_value_undefined() : __pulse_fastly_string_value(value) }
1625
1822
  function host_request_text(): i32 { if (__pulse_request_body_failure != 0) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, __pulse_request_body_failure, -1); return 0 } if (__pulse_fastly_request_body_loaded == 0) { __pulse_fastly_request_body = __pulse_fastly_read_body(__pulse_fastly_request_body_handle, -1, true); __pulse_fastly_request_body_loaded = 1; if (__pulse_fastly_last_error == PULSE_ERROR_REQUEST_BODY) __pulse_request_body_failure = __pulse_fastly_error_stage } if (__pulse_fastly_last_error != 0) return 0; return __pulse_fastly_string_value(__pulse_fastly_request_body) }
1626
- function host_request_json(schema: i32): i32 { const parsed = __pulse_fastly_parse_json(__pulse_fastly_string(host_request_text())); if (parsed <= 0) return 0; const schemaValue = __pulse_fastly_value(schema); return schemaValue.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schemaValue.text, parsed, false) : parsed }
1823
+ function host_request_json(schema: i32): i32 { const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(__pulse_fastly_string(host_request_text()), schema)' : '__pulse_fastly_parse_json(__pulse_fastly_string(host_request_text()))'}; if (parsed <= 0) return 0; const schemaValue = __pulse_fastly_value(schema); return schemaValue.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schemaValue.text, parsed, false) : parsed }
1627
1824
  function __pulse_fastly_headers_from_options(options: __PulseFastlyValue, output: __PulseFastlyValue): void { const index = __pulse_fastly_find(options, "headers"); if (index < 0) return; const headers = __pulse_fastly_value(unchecked(options.values[index])); if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); const value = __pulse_fastly_value(unchecked(headers.values[i])); if (value.kind == PULSE_VALUE_ARRAY) { for (let j = 0; j < value.values.length; j += 1) output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(value.values[j])))) } else output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } return } if (headers.kind == PULSE_VALUE_ARRAY) { for (let i = 0; i < headers.values.length; i += 1) { const pair = __pulse_fastly_value(unchecked(headers.values[i])); if (pair.kind != PULSE_VALUE_ARRAY || pair.values.length < 2) continue; output.headers.push(new __PulseFastlyHeader(__pulse_fastly_string(unchecked(pair.values[0])), __pulse_fastly_string(unchecked(pair.values[1])))) } } }
1628
1825
  function __pulse_fastly_response(value: i32, optionsHandle: i32, json: bool): i32 { const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const options = __pulse_fastly_value(optionsHandle); let bodyValue = value; if (options.kind == PULSE_VALUE_OBJECT) { const statusIndex = __pulse_fastly_find(options, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(options.values[statusIndex]))); const schemaIndex = __pulse_fastly_find(options, "schema"); if (json && schemaIndex >= 0) { bodyValue = __pulse_fastly_schema_apply(__pulse_fastly_string(unchecked(options.values[schemaIndex])), value, true); if (bodyValue <= 0) return 0 } __pulse_fastly_headers_from_options(options, output) } output.text = json ? __pulse_fastly_json(bodyValue, 0) : __pulse_fastly_string(value); let hasContentType = false; for (let i = 0; i < output.headers.length; i += 1) if (unchecked(output.headers[i]).name.toLowerCase() == "content-type") hasContentType = true; if (!hasContentType) output.headers.push(new __PulseFastlyHeader("content-type", json ? "application/json; charset=utf-8" : "text/plain; charset=utf-8")); return __pulse_fastly_put(output) }
1629
1826
  function host_response_json(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, true) }
@@ -1633,7 +1830,7 @@ function host_schema_decode(text: i32, schema: i32): i32 {
1633
1830
  const input = __pulse_fastly_value(text)
1634
1831
  if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }
1635
1832
  if (String.UTF8.byteLength(input.text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1636
- const parsed = __pulse_fastly_parse_json(input.text)
1833
+ const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(input.text, schema)' : '__pulse_fastly_parse_json(input.text)'}
1637
1834
  if (parsed <= 0) return 0
1638
1835
  const decoded = __pulse_fastly_schema_apply(id.text, parsed, false)
1639
1836
  if (decoded <= 0) return 0
@@ -1643,11 +1840,12 @@ function host_schema_decode(text: i32, schema: i32): i32 {
1643
1840
  function host_schema_encode(value: i32, schema: i32): i32 {
1644
1841
  const id = __pulse_fastly_value(schema)
1645
1842
  if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
1646
- const projected = __pulse_fastly_schema_apply(id.text, value, true)
1843
+ ${textEncodeIds.length ? `const textResult = ${textEncodeIds.map(id => `id.text == ${quote(id)}`).join(' || ')}
1844
+ const projected = __pulse_fastly_schema_apply(id.text, value, true, textResult)` : 'const projected = __pulse_fastly_schema_apply(id.text, value, true)'}
1647
1845
  if (projected <= 0) return 0
1648
- const text = __pulse_fastly_json(projected, 0)
1846
+ const text = ${textEncodeIds.length ? 'textResult ? __pulse_fastly_string(projected) : ' : ''}__pulse_fastly_json(projected, 0)
1649
1847
  if (String.UTF8.byteLength(text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1650
- return __pulse_fastly_string_value(text)
1848
+ return ${textEncodeIds.length ? 'textResult ? projected : ' : ''}__pulse_fastly_string_value(text)
1651
1849
  }
1652
1850
  function host_response_text(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, false) }
1653
1851
  function host_response_custom(specHandle: i32): i32 { const spec = __pulse_fastly_value(specHandle); if (spec.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 40, -1); return 0 } const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const statusIndex = __pulse_fastly_find(spec, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(spec.values[statusIndex]))); const bodyIndex = __pulse_fastly_find(spec, "body"); if (bodyIndex >= 0) output.text = __pulse_fastly_string(unchecked(spec.values[bodyIndex])); __pulse_fastly_headers_from_options(spec, output); return __pulse_fastly_put(output) }
@@ -1658,7 +1856,7 @@ function host_grip_subscribe(responseHandle: i32, optionsHandle: i32): i32 { con
1658
1856
  function host_grip_handoff(optionsHandle: i32): i32 { const options = __pulse_fastly_value(optionsHandle); if (options.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 139, -1); return 0 } const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const statusIndex = __pulse_fastly_find(options, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(options.values[statusIndex]))); const bodyIndex = __pulse_fastly_find(options, "body"); if (bodyIndex >= 0) output.text = __pulse_fastly_string(unchecked(options.values[bodyIndex])); __pulse_fastly_headers_from_options(options, output); let hasContentType = false; for (let i = 0; i < output.headers.length; i += 1) if (unchecked(output.headers[i]).name.toLowerCase() == "content-type") hasContentType = true; if (!hasContentType) output.headers.push(new __PulseFastlyHeader("Content-Type", "application/websocket-events")); return __pulse_fastly_grip_frame(options, output) ? __pulse_fastly_put(output) : 0 }
1659
1857
  function host_kv_namespace(name: i32): i32 { return name }
1660
1858
  function host_fetch_text(responseHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 42, -1); return 0 } return __pulse_fastly_string_value(__pulse_fastly_fetch_body(response)) }
1661
- function host_fetch_json(responseHandle: i32, schemaHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 43, -1); return 0 } if (__PULSE_SCHEMA_REQUIRE_JSON) { const contentType = __pulse_fastly_fetch_header_text(response.responseHandle, "content-type"); if (contentType.length == 0 || contentType.toLowerCase().indexOf("json") < 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 44, -1); return 0 } } const parsed = __pulse_fastly_parse_json(__pulse_fastly_fetch_body(response)); if (parsed <= 0) return 0; const schema = __pulse_fastly_value(schemaHandle); return schema.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schema.text, parsed, false) : parsed }
1859
+ function host_fetch_json(responseHandle: i32, schemaHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 43, -1); return 0 } if (__PULSE_SCHEMA_REQUIRE_JSON) { const contentType = __pulse_fastly_fetch_header_text(response.responseHandle, "content-type"); if (contentType.length == 0 || contentType.toLowerCase().indexOf("json") < 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 44, -1); return 0 } } const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(__pulse_fastly_fetch_body(response), schemaHandle)' : '__pulse_fastly_parse_json(__pulse_fastly_fetch_body(response))'}; if (parsed <= 0) return 0; const schema = __pulse_fastly_value(schemaHandle); return schema.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schema.text, parsed, false) : parsed }
1662
1860
  function host_fetch_header(responseHandle: i32, nameHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 45, -1); return 0 } const value = __pulse_fastly_fetch_header_text(response.responseHandle, __pulse_fastly_string(nameHandle)); return value.length == 0 ? host_value_undefined() : __pulse_fastly_string_value(value) }
1663
1861
  function __pulse_fastly_payload_field(payload: __PulseFastlyValue, name: string): i32 { const index = __pulse_fastly_find(payload, name); return index < 0 ? host_value_undefined() : unchecked(payload.values[index]) }
1664
1862
  function __pulse_fastly_ready_effect(effectIndex: i32, handle: i32): void { unchecked(__pulse_fastly_ready[effectIndex] = handle); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_READY) }
@@ -1679,12 +1877,12 @@ function __pulse_fastly_jwt_record_verified(): void { __pulse_fastly_jwt_verifie
1679
1877
  function __pulse_fastly_jwt_record_signing_input(bytes: i32, digest: string): void { __pulse_fastly_jwt_signing_input_bytes = bytes; __pulse_fastly_jwt_signing_input_sha256 = digest }
1680
1878
  function __pulse_fastly_jwt_capture_clock(effectIndex: i32): f64 { __pulse_fastly_jwt_clock_calls += 1; __pulse_fastly_jwt_event_order += "C"; const out = new StaticArray<i64>(1); const status = wasi_snapshot_preview1_clock_time_get(0, 1, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 226, effectIndex); return NaN } ${jwtClockOverride ? `return ${jwtClockOverride}` : 'return f64(load<i64>(changetype<usize>(out))) / 1000000000.0'} }
1681
1879
  function __pulse_fastly_jwt_validate_schema(effectIndex: i32, schemaId: string, claimsHandle: i32): i32 { __pulse_fastly_jwt_schema_calls += 1; __pulse_fastly_jwt_event_order += "H"; if (__pulse_fastly_jwt_case_id() == "claims-schema-failure-after-registered-claims") return 0; return __pulse_fastly_schema_apply(schemaId, claimsHandle, false) }
1880
+ function __pulse_fastly_jwt_sign_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_sign(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
1682
1881
  function __pulse_fastly_jwt_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_verify(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
1683
1882
  function __pulse_fastly_start_request(effectIndex: i32, url: string, method: string, headers: __PulseFastlyValue, body: string, backend: string): void { const requestOut = __pulse_fastly_out_i32(); let status = fastly_http_req_new(changetype<usize>(requestOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 70, effectIndex); return } const requestHandle = __pulse_fastly_out_value(requestOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 71, effectIndex); return } const bodyHandle = __pulse_fastly_out_value(bodyOut); const methodBytes = __pulse_fastly_utf8(method); status = fastly_http_req_method_set(requestHandle, changetype<usize>(methodBytes), methodBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 72, effectIndex); return } const urlBytes = __pulse_fastly_utf8(url); status = fastly_http_req_uri_set(requestHandle, changetype<usize>(urlBytes), urlBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 73, effectIndex); return } if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const nameBytes = __pulse_fastly_utf8(unchecked(headers.keys[i])); const valueBytes = __pulse_fastly_utf8(__pulse_fastly_string(unchecked(headers.values[i]))); status = fastly_http_req_header_insert(requestHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 74, effectIndex); return } } } if (body.length > 0) { status = __pulse_fastly_write_body(bodyHandle, body); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 75, effectIndex); return } } if (backend.length == 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 76, effectIndex); return } const backendBytes = __pulse_fastly_utf8(backend); const pendingOut = __pulse_fastly_out_i32(); status = fastly_http_req_send_async(requestHandle, bodyHandle, changetype<usize>(backendBytes), backendBytes.byteLength, changetype<usize>(pendingOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 77, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(pendingOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1684
- function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const urlIndex = __pulse_fastly_find(payload, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payload.values[urlIndex])); const initIndex = __pulse_fastly_find(payload, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.values[initIndex])) : new __PulseFastlyValue(); let method = "GET"; let body = ""; let headers = new __PulseFastlyValue(); headers.kind = PULSE_VALUE_OBJECT; if (init.kind == PULSE_VALUE_OBJECT) { const methodIndex = __pulse_fastly_find(init, "method"); if (methodIndex >= 0) method = __pulse_fastly_string(unchecked(init.values[methodIndex])).toUpperCase(); const headersIndex = __pulse_fastly_find(init, "headers"); if (headersIndex >= 0) headers = __pulse_fastly_value(unchecked(init.values[headersIndex])); const bodyIndex = __pulse_fastly_find(init, "body"); const jsonIndex = __pulse_fastly_find(init, "json"); if (bodyIndex >= 0) { const bodyValue = unchecked(init.values[bodyIndex]); const bodyObject = __pulse_fastly_value(bodyValue); body = bodyObject.kind == PULSE_VALUE_STRING ? bodyObject.text : __pulse_fastly_json(bodyValue, 0) } else if (jsonIndex >= 0) { body = __pulse_fastly_json(unchecked(init.values[jsonIndex]), 0); let hasContentType = false; for (let i = 0; i < headers.keys.length; i += 1) if (unchecked(headers.keys[i]).toLowerCase() == "content-type") hasContentType = true; if (!hasContentType) { headers.keys.push("content-type"); headers.values.push(__pulse_fastly_string_value("application/json; charset=utf-8")) } } } __pulse_fastly_start_request(effectIndex, url, method, headers, body, __pulse_fastly_backend(effectIndex)) }
1883
+ function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const urlIndex = __pulse_fastly_find(payload, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payload.values[urlIndex])); const initIndex = __pulse_fastly_find(payload, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.values[initIndex])) : new __PulseFastlyValue(); let method = "GET"; let body = ""; let headers = new __PulseFastlyValue(); headers.kind = PULSE_VALUE_OBJECT; if (init.kind == PULSE_VALUE_OBJECT) { const methodIndex = __pulse_fastly_find(init, "method"); if (methodIndex >= 0) method = __pulse_fastly_string(unchecked(init.values[methodIndex])).toUpperCase(); const headersIndex = __pulse_fastly_find(init, "headers"); if (headersIndex >= 0) headers = __pulse_fastly_value(unchecked(init.values[headersIndex])); const bodyIndex = __pulse_fastly_find(init, "body"); const jsonIndex = __pulse_fastly_find(init, "json"); if (bodyIndex >= 0) { const bodyValue = unchecked(init.values[bodyIndex]); const bodyObject = __pulse_fastly_value(bodyValue); body = bodyObject.kind == PULSE_VALUE_STRING ? bodyObject.text : __pulse_fastly_json(bodyValue, 0) } else if (jsonIndex >= 0) { body = __pulse_fastly_fetch_json_body(init, unchecked(init.values[jsonIndex])); if (__pulse_fastly_last_error != 0) return; headers = __pulse_fastly_fetch_json_headers(headers) } } __pulse_fastly_start_request(effectIndex, url, method, headers, body, __pulse_fastly_backend(effectIndex)) }
1685
1884
  function __pulse_fastly_kv_get_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 116, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1686
- function __pulse_fastly_assets_payload(payload: __PulseFastlyValue): __PulseFastlyValue { const value = __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")); if (value.kind != PULSE_VALUE_OBJECT) __pulse_fastly_fail(PULSE_ERROR_VALUE, 146, -1); return value }
1687
- function __pulse_fastly_assets_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const value = __pulse_fastly_assets_payload(payload); if (__pulse_fastly_last_error != 0) return; const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(value, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 147, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1885
+ ${assetBeginSource}
1688
1886
  function __pulse_fastly_kv_put_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const value = __pulse_fastly_payload_field(payload, "value"); const bodyOut = __pulse_fastly_out_i32(); let status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 117, effectIndex); return } const body = __pulse_fastly_out_value(bodyOut); status = __pulse_fastly_write_body(body, "{\"__pulseKv\":1,\"value\":" + __pulse_fastly_json(value, 0) + "}"); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 118, effectIndex); return } const keyBytes = __pulse_fastly_utf8(key); const insertOut = __pulse_fastly_out_i32(); const config = new StaticArray<i64>(4); status = fastly_kv_store_insert(store, changetype<usize>(keyBytes), keyBytes.byteLength, body, 0, changetype<usize>(config), changetype<usize>(insertOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 119, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(insertOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1689
1887
  function __pulse_fastly_grip_add_channel(channel: string): void { if (channel.length == 0) return; for (let i = 0; i < __pulse_fastly_grip_channels.length; i += 1) if (unchecked(__pulse_fastly_grip_channels[i]) == channel) return; __pulse_fastly_grip_channels.push(channel) }
1690
1888
  function __pulse_fastly_grip_payload(payload: __PulseFastlyValue): __PulseFastlyValue { return __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")) }
@@ -1698,19 +1896,22 @@ function __pulse_fastly_wait_grip_publish(effectIndex: i32): i32 { const respons
1698
1896
  function __pulse_fastly_wait_grip_broadcast(effectIndex: i32): i32 { const responseHandle = __pulse_fastly_wait_fetch(effectIndex); if (responseHandle <= 0) return 0; const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 142, effectIndex); return 0 } const statusOut = __pulse_fastly_out_i32(); const statusCall = fastly_http_resp_status_get(response.responseHandle, changetype<usize>(statusOut)); if (statusCall != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 143, effectIndex); return 0 } const originStatus = __pulse_fastly_out_value(statusOut); if (originStatus < 200 || originStatus > 299) { __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 144, effectIndex); return 0 } const receipt = host_value_object(); host_value_object_set(receipt, __pulse_fastly_string_value("accepted"), host_value_boolean(1)); host_value_object_set(receipt, __pulse_fastly_string_value("status"), host_value_number(f64(originStatus))); return receipt }
1699
1897
  function __pulse_fastly_wait_ready(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_READY) { __pulse_fastly_fail(PULSE_ERROR_STATE, 124, effectIndex); return 0 } const value = unchecked(__pulse_fastly_ready[effectIndex]); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); unchecked(__pulse_fastly_ready[effectIndex] = 0); return value }
1700
1898
  function __pulse_fastly_wait_kv_get(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_READY) return __pulse_fastly_wait_ready(effectIndex); if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 125, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const bodyOut = __pulse_fastly_out_i32(); const metadata = new Uint8Array(1); const written = __pulse_fastly_out_i32(); const generation = new StaticArray<i64>(1); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(bodyOut), metadata.dataStart, 0, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 125, effectIndex); return 0 } const errorCode = __pulse_fastly_out_value(kvError); if (errorCode == 3) return host_value_undefined(); if (errorCode != 1) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 128, effectIndex); return 0 } const text = __pulse_fastly_read_body(__pulse_fastly_out_value(bodyOut), effectIndex); if (__pulse_fastly_last_error != 0) return 0; const parser = new __PulseJsonParser(text); const parsed = parser.parse(); if (parser.failed || parsed <= 0) return __pulse_fastly_string_value(text); const object = __pulse_fastly_value(parsed); if (object.kind == PULSE_VALUE_OBJECT) { const marker = __pulse_fastly_find(object, "__pulseKv"); const value = __pulse_fastly_find(object, "value"); if (marker >= 0 && value >= 0 && __pulse_fastly_number(unchecked(object.values[marker])) == 1.0) return unchecked(object.values[value]) } return parsed }
1701
- function __pulse_fastly_asset_content_type(key: string): string { const dot = key.lastIndexOf("."); const extension = dot < 0 ? "" : key.substring(dot + 1).toLowerCase(); if (extension == "css") return "text/css; charset=utf-8"; if (extension == "html") return "text/html; charset=utf-8"; if (extension == "js" || extension == "mjs") return "text/javascript; charset=utf-8"; if (extension == "json") return "application/json; charset=utf-8"; if (extension == "svg") return "image/svg+xml"; if (extension == "txt") return "text/plain; charset=utf-8"; if (extension == "webp") return "image/webp"; if (extension == "png") return "image/png"; if (extension == "jpg" || extension == "jpeg") return "image/jpeg"; if (extension == "gif") return "image/gif"; if (extension == "ico") return "image/x-icon"; return "application/octet-stream" }
1702
- function __pulse_fastly_asset_text(text: string): string { const parser = new __PulseJsonParser(text); const parsed = parser.parse(); if (parser.failed || parsed <= 0) return text; const object = __pulse_fastly_value(parsed); if (object.kind != PULSE_VALUE_OBJECT) return text; const marker = __pulse_fastly_find(object, "__pulseKv"); const value = __pulse_fastly_find(object, "value"); if (marker < 0 || value < 0 || __pulse_fastly_number(unchecked(object.values[marker])) != 1.0) return text; return __pulse_fastly_string(unchecked(object.values[value])) }
1703
- function __pulse_fastly_wait_assets(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 148, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const bodyOut = __pulse_fastly_out_i32(); const metadata = new Uint8Array(1); const written = __pulse_fastly_out_i32(); const generation = new StaticArray<i64>(1); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(bodyOut), metadata.dataStart, 0, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 149, effectIndex); return 0 } const outer = __pulse_fastly_value(unchecked(__pulse_fastly_payloads[effectIndex])); const payload = __pulse_fastly_assets_payload(outer); if (__pulse_fastly_last_error != 0) return 0; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; const errorCode = __pulse_fastly_out_value(kvError); output.status = errorCode == 3 ? 404 : 200; if (errorCode != 1 && errorCode != 3) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 150, effectIndex); return 0 } const headersHandle = __pulse_fastly_payload_field(payload, "headers"); const headers = __pulse_fastly_value(headersHandle); let hasContentType = false; if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); if (name.toLowerCase() == "content-type") hasContentType = true; output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } } if (!hasContentType) output.headers.push(new __PulseFastlyHeader("content-type", output.status == 200 ? __pulse_fastly_asset_content_type(key) : "text/plain; charset=utf-8")); const cacheHandle = __pulse_fastly_payload_field(payload, "cacheControl"); if (__pulse_fastly_value(cacheHandle).kind != PULSE_VALUE_UNDEFINED) output.headers.push(new __PulseFastlyHeader("cache-control", __pulse_fastly_string(cacheHandle))); const methodHandle = __pulse_fastly_payload_field(payload, "method"); const method = __pulse_fastly_value(methodHandle).kind == PULSE_VALUE_UNDEFINED ? "GET" : __pulse_fastly_string(methodHandle).toUpperCase(); if (output.status == 200 && method != "HEAD") { const text = __pulse_fastly_read_body(__pulse_fastly_out_value(bodyOut), effectIndex); if (__pulse_fastly_last_error != 0) return 0; output.text = __pulse_fastly_asset_text(text) } return __pulse_fastly_put(output) }
1899
+ ${assetResultSource}
1704
1900
  function __pulse_fastly_wait_kv_put(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 126, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_insert_wait(pending, changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK || __pulse_fastly_out_value(kvError) != 1) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 127, effectIndex); return 0 } return host_value_boolean(1) }
1705
1901
  function __pulse_fastly_write_body(bodyHandle: i32, value: string): i32 { const bytes = __pulse_fastly_utf8(value); let offset = 0; while (offset < bytes.byteLength) { const written = __pulse_fastly_out_i32(); const status = fastly_http_body_write(bodyHandle, changetype<usize>(bytes) + offset, bytes.byteLength - offset, 0, changetype<usize>(written)); if (status != FASTLY_STATUS_OK) return status; const count = __pulse_fastly_out_value(written); if (count <= 0) return 1; offset += count } return FASTLY_STATUS_OK }
1706
- function __pulse_fastly_send_result(handle: i32): i32 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_FETCH) return fastly_http_resp_send_downstream(value.responseHandle, value.bodyHandle, 0); if (value.kind != PULSE_VALUE_RESPONSE) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 90, -1); return PULSE_ERROR_VALUE } const responseOut = __pulse_fastly_out_i32(); let status = fastly_http_resp_new(changetype<usize>(responseOut)); if (status != FASTLY_STATUS_OK) return status; const responseHandle = __pulse_fastly_out_value(responseOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) return status; const bodyHandle = __pulse_fastly_out_value(bodyOut); status = fastly_http_resp_status_set(responseHandle, value.status); if (status != FASTLY_STATUS_OK) return status; for (let i = 0; i < value.headers.length; i += 1) { const header = unchecked(value.headers[i]); const nameBytes = __pulse_fastly_utf8(header.name); const valueBytes = __pulse_fastly_utf8(header.value); status = fastly_http_resp_header_append(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) return status } status = __pulse_fastly_write_body(bodyHandle, value.text); if (status != FASTLY_STATUS_OK) return status; return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0) }
1902
+ function __pulse_fastly_send_result(handle: i32): i32 { const value = __pulse_fastly_value(handle); ${bindings.s3.length ? 'if (value.s3Body) return __s3_send_body(value);' : ''} if (value.kind == PULSE_VALUE_FETCH) return fastly_http_resp_send_downstream(value.responseHandle, value.bodyHandle, 0); if (value.kind != PULSE_VALUE_RESPONSE) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 90, -1); return PULSE_ERROR_VALUE } const responseOut = __pulse_fastly_out_i32(); let status = fastly_http_resp_new(changetype<usize>(responseOut)); if (status != FASTLY_STATUS_OK) return status; const responseHandle = __pulse_fastly_out_value(responseOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) return status; const bodyHandle = __pulse_fastly_out_value(bodyOut); status = fastly_http_resp_status_set(responseHandle, value.status); if (status != FASTLY_STATUS_OK) return status; for (let i = 0; i < value.headers.length; i += 1) { const header = unchecked(value.headers[i]); const nameBytes = __pulse_fastly_utf8(header.name); const valueBytes = __pulse_fastly_utf8(header.value); status = fastly_http_resp_header_append(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) return status } status = __pulse_fastly_write_body(bodyHandle, value.text); if (status != FASTLY_STATUS_OK) return status; return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0) }
1707
1903
 
1708
1904
  ${jwtEvidenceSource}
1709
1905
  ${es256KeyArtifactSource(es256KeyRecords)}
1710
1906
  ${jwtCryptoCompositionSource(jwtCrypto)}
1907
+ ${jwtSigningCryptoCompositionSource(jwtCrypto)}
1711
1908
  ${generateSchemaRuntime(plan)}
1712
1909
  ${effectResultSource(plan, bindings)}
1713
1910
  `;
1911
+ if (!hasAssets) return source;
1912
+ const writer = 'status = __pulse_fastly_write_body(bodyHandle, value.text);';
1913
+ if (!source.includes(writer)) throw new Error('Embedded asset response writer boundary drift.');
1914
+ return source.replace(writer, 'status = value.binaryBody !== null ? __pulse_fastly_write_binary(bodyHandle, value.binaryBody!) : __pulse_fastly_write_body(bodyHandle, value.text);');
1714
1915
  }
1715
1916
 
1716
1917
  function driverSource(plan, options = {}) {
@@ -1727,6 +1928,13 @@ export function pulse_fastly_jwt_crypto_calls(): i32 { return __pulse_fastly_jwt
1727
1928
  export function pulse_fastly_jwt_clock_calls(): i32 { return __pulse_fastly_jwt_clock_calls }
1728
1929
  export function pulse_fastly_jwt_schema_calls(): i32 { return __pulse_fastly_jwt_schema_calls }
1729
1930
  export function ${entryName}(): void {
1931
+ if (__pulse_invocation_started) { __pulse_fastly_fail(PULSE_ERROR_STATE, 171, -1); return }
1932
+ __pulse_invocation_started = true
1933
+ __pulse_fastly_run_invocation()
1934
+ ${plan.effects.some(e => e.kind === 's3.getBody') ? '__s3_close_bodies()' : ''}
1935
+ __pulse_invocation_close()
1936
+ }
1937
+ function __pulse_fastly_run_invocation(): void {
1730
1938
  __pulse_fastly_last_error = fastly_abi_init(PULSE_FASTLY_EFFECTS_ABI_VERSION)
1731
1939
  if (__pulse_fastly_last_error != FASTLY_STATUS_OK) { __pulse_fastly_jwt_send_error(); return }
1732
1940
  const handles = new StaticArray<i32>(2)
@@ -1740,12 +1948,13 @@ export function ${entryName}(): void {
1740
1948
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1741
1949
  __pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
1742
1950
  let runStatus = pulse_start()
1743
- ${applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1951
+ ${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1744
1952
  for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
1745
1953
  if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
1954
+ const invocation = unchecked(__pulse_invocation_tickets[effectIndex])
1746
1955
  const result = __pulse_fastly_resolve_effect(effectIndex)
1747
1956
  if (result <= 0 || __pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1748
- if (pulse_set_effect_result(effectIndex, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
1957
+ if (__pulse_invocation_settle(effectIndex, invocation, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
1749
1958
  }
1750
1959
  runStatus = pulse_resume()
1751
1960
  }`}
@@ -1764,7 +1973,7 @@ function capabilityImports(effect) {
1764
1973
  if (effect.kind === 'time.now') return ['wasi_snapshot_preview1.clock_time_get'];
1765
1974
  if (effect.kind === 'config.get') return ['fastly_config_store.open', 'fastly_config_store.get'];
1766
1975
  if (effect.kind === 'secret.get') return ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'];
1767
- if (effect.kind === 'jwt.verify') {
1976
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind)) {
1768
1977
  return [
1769
1978
  ...(effect.resource && effect.resource.keyType === 'secret'
1770
1979
  ? [
@@ -1796,7 +2005,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1796
2005
  validatePlanBoundary(plan, options);
1797
2006
  const facts = packageFacts(plan, options);
1798
2007
  const jwtCrypto = selectedJwtCrypto(plan);
1799
- const es256KeyRecords = jwtCrypto && jwtCrypto.algorithm === 'ES256'
2008
+ const es256KeyRecords = jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1800
2009
  ? resolveEs256KeyArtifacts(plan, facts)
1801
2010
  : Object.freeze([]);
1802
2011
  if (jwtCrypto && jwtCrypto.guestUnitRequired && facts.guestUnits.length !== 1) {
@@ -1820,10 +2029,11 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1820
2029
  );
1821
2030
  }
1822
2031
  const bindings = resolveCapabilityBindings(plan, options);
1823
- const portable = generateCanonicalNativeAssemblyScript(plan, options);
2032
+ const portable = packageApplication.enabled(plan) ? packageApplication.portable(plan) : generateCanonicalNativeAssemblyScript(plan, options);
1824
2033
  const portableSource = stripPulseHostImports(portable.source);
1825
2034
  const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
1826
- const jwtSource = hasJwt ? pulseJwtAssemblyScriptSource() : undefined;
2035
+ const hasSign = plan.effects.some(effect => effect.kind === 'jwt.sign');
2036
+ const jwtSource = hasJwt || hasSign ? pulseJwtAssemblyScriptSource({ includeSigning: hasSign }) : undefined;
1827
2037
  let source = [
1828
2038
  '/* Generated by Pulse Pass98 Fastly native platform capability compiler. */',
1829
2039
  'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
@@ -1835,12 +2045,14 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1835
2045
  }),
1836
2046
  jwtSource ? jwtSource.source : '',
1837
2047
  require('./s3-native.js').s3NativeSource(plan, bindings),
2048
+ plan.effects.some(e => e.kind === 'assets.lookup') ? require('@pulse-compute/wasm-contracts/assets/contracts').embeddedNativeSource() + fs.readFileSync(path.join(__dirname, 'embedded-assets.as.ts'), 'utf8') : '',
1838
2049
  require('./time-native.js').timeNativeSource(plan),
1839
2050
  require('./digest-native.js').digestNativeSource(plan),
1840
2051
  conditionalKv.kvNativeSource(plan),
1841
2052
  require('./native-request-body.js').runtimeSource(),
1842
2053
  require('./native-request-headers.js').runtimeSource(),
1843
- applicationErrors.enabled(plan) ? applicationErrors.runtimeSource() : '',
2054
+ applicationErrors.enabled(plan) ? applicationErrors.runtimeSource(plan) : '',
2055
+ effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan) }),
1844
2056
  portableSource,
1845
2057
  driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
1846
2058
  ''
@@ -1848,7 +2060,10 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1848
2060
  if (applicationErrors.enabled(plan)) source = applicationErrors.instrument(source);
1849
2061
  source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
1850
2062
  source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan));
2063
+ if (packageApplication.enabled(plan)) source = packageApplication.instrument(source);
2064
+ source = effectInvocations.instrument(source);
1851
2065
  source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan);
2066
+ source = nativeValueBudget.instrument(source, plan);
1852
2067
  const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
1853
2068
  output[effect.kind] = (output[effect.kind] || 0) + 1;
1854
2069
  return output;
@@ -1875,7 +2090,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1875
2090
  crypto: portable.manifest.crypto,
1876
2091
  jwt: jwtSource
1877
2092
  ? Object.freeze({
1878
- status: jwtCrypto && jwtCrypto.algorithm === 'ES256'
2093
+ status: jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1879
2094
  ? 'implemented-g4'
1880
2095
  : 'implemented-e2',
1881
2096
  package: jwtSource.owner,
@@ -1894,6 +2109,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1894
2109
  })
1895
2110
  : Object.freeze({ status: 'inactive' }),
1896
2111
  effectCount: (plan.effects || []).length,
2112
+ effectInvocations: effectInvocations.policy,
1897
2113
  effectKinds,
1898
2114
  continuationCount: (plan.continuations || []).length,
1899
2115
  groupedContinuationCount: (plan.continuations || []).filter((entry) => (entry.effectIds || []).length > 1).length,
@@ -1912,19 +2128,20 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1912
2128
  ...(bindings.maxDurationMs === undefined ? [] : ['pulse_fastly_request_expired'])
1913
2129
  ]),
1914
2130
  policy: Object.freeze({
2131
+ ...(nativeValueBudget.hasBoundedReadLoop(plan) ? { readLoopMemory: nativeValueBudget.policy } : {}),
1915
2132
  nativeFastly: true,
1916
2133
  provider: 'fastly',
1917
2134
  providerNeutralInput: true,
1918
2135
  javascriptRuntime: false,
1919
2136
  jsComputeRuntime: false,
1920
- wasi: bindings.maxDurationMs !== undefined || hasJwt || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
2137
+ wasi: bindings.maxDurationMs !== undefined || hasJwt || hasSign || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
1921
2138
  effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
1922
2139
  continuations: true,
1923
2140
  groupedEffects: 'existing fetch start-before-wait ordering remains intact',
1924
2141
  config: 'Fastly Config Store open/get with undefined for missing values',
1925
2142
  secrets: 'Fastly Secret Store open/get/plaintext; proof traces redact plaintext values',
1926
- jwt: hasJwt
1927
- ? `package-owned compact-JWS and registered-claim semantics composed with ${jwtCrypto.realization}; provider-owned request, clock, schema, and transport`
2143
+ jwt: hasJwt || hasSign
2144
+ ? `package-owned compact-JWS, registered-claim, and bounded issuance semantics composed with ${jwtCrypto.realization}; provider-owned request, secret, clock, schema, and transport`
1928
2145
  : 'inactive',
1929
2146
  kv: 'Fastly KV Store async lookup/insert; conditional operations preserve u64 generations, bounded readiness, strict Pulse envelopes and uncertain dispatch',
1930
2147
  assets: 'Fastly KV Store lookup with provider-owned content type, cache headers, and GET/HEAD body ownership',
@@ -1960,8 +2177,9 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1960
2177
  });
1961
2178
  }
1962
2179
 
1963
- function inspectFastlyNativePlatformCapabilitiesWasm(input) {
2180
+ function inspectFastlyNativePlatformCapabilitiesWasm(input, options = {}) {
1964
2181
  const bytes = Buffer.isBuffer(input) ? input : fs.readFileSync(path.resolve(input));
2182
+ assertFastlyWasmBudget(bytes.length, options.maxWasmBytes);
1965
2183
  if (!WebAssembly.validate(bytes)) fail('Generated Fastly native platform capabilities module is not valid WebAssembly.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_INVALID');
1966
2184
  const module = new WebAssembly.Module(bytes);
1967
2185
  const imports = WebAssembly.Module.imports(module).map((entry) => Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind }));
@@ -1977,7 +2195,6 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1977
2195
  const requiredExports = ['_start', 'memory', 'pulse_fastly_last_error', 'pulse_fastly_error_stage', 'pulse_fastly_error_effect', 'pulse_start', 'pulse_resume', 'pulse_set_effect_result', 'pulse_result_handle'];
1978
2196
  const missingExports = requiredExports.filter((name) => !exportNames.has(name));
1979
2197
  if (missingExports.length > 0) fail('Generated Fastly native platform capabilities module is missing required exports.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_EXPORT_MISSING', { missingExports, exports });
1980
- if (bytes.length > FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES) fail('Generated Fastly native platform capabilities module exceeds the Pass98 size ceiling.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_TOO_LARGE', { bytes: bytes.length, maxBytes: FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES });
1981
2198
  return Object.freeze({
1982
2199
  valid: true,
1983
2200
  bytes: bytes.length,
@@ -1990,6 +2207,7 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1990
2207
  }
1991
2208
 
1992
2209
  function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2210
+ const maxWasmBytes = normalizeFastlyMaxWasmBytes(options.maxWasmBytes);
1993
2211
  const generated = generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options);
1994
2212
  const cwd = path.resolve(options.cwd || process.cwd());
1995
2213
  const compilerFallback = path.resolve(__dirname, '..', '..', '..', '..', 'wasm', 'packages', 'compiler');
@@ -2022,6 +2240,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2022
2240
  }
2023
2241
  jsonAs = Object.freeze({ transform, dependencyRoot, version: packageJson.version });
2024
2242
  }
2243
+ if (options.emitWat !== undefined && typeof options.emitWat !== 'boolean') throw new TypeError('emitWat must be a boolean.');
2244
+ const emitWat = options.emitWat === true;
2025
2245
  const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-fastly-native-platform-'));
2026
2246
  const requestedOutDir = options.outDir ? path.resolve(options.outDir) : undefined;
2027
2247
  const outputDir = requestedOutDir || stagingDir;
@@ -2029,6 +2249,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2029
2249
  const sourceFile = path.join(stagingDir, 'fastly-native-platform-capabilities.as.ts');
2030
2250
  const wasmFile = path.join(outputDir, options.wasmFile || 'fastly-native-platform-capabilities.wasm');
2031
2251
  const watFile = path.join(outputDir, options.watFile || 'fastly-native-platform-capabilities.wat');
2252
+ if (!emitWat) fs.rmSync(watFile, { force: true });
2032
2253
  const guestLinked = generated.guestUnits.length > 0;
2033
2254
  // asc resolves custom runtimes like module names and appends ".ts".
2034
2255
  const fixedMemoryRuntimeModule = path.join(__dirname, 'fixed-memory-runtime.as');
@@ -2045,16 +2266,16 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2045
2266
  asc.script,
2046
2267
  path.basename(sourceFile),
2047
2268
  '--outFile', assemblyScriptWasmFile,
2048
- '--textFile', assemblyScriptWatFile,
2049
2269
  '--runtime', guestLinked
2050
2270
  ? fixedMemoryRuntimeModule
2051
2271
  : (schemaCodecsActive ? 'incremental' : 'stub'),
2052
2272
  '--noAssert',
2053
2273
  '--optimize'
2054
2274
  ];
2055
- const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization);
2056
- if (!guestLinked && plan.effects.some(effect => effect.kind === 'crypto.digestText' || effect.kind.startsWith('s3.'))) {
2057
- args.push('--maximumMemory', '4096');
2275
+ if (emitWat) args.push('--textFile', assemblyScriptWatFile);
2276
+ const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization, { guestLinked });
2277
+ if (!guestLinked && (nativeValueBudget.hasBoundedReadLoop(plan) || plan.effects.some(effect => effect.kind === 'crypto.digestText' || effect.kind.startsWith('s3.')))) {
2278
+ args.push('--maximumMemory', String(nativeValueBudget.policy.maximumMemoryPages));
2058
2279
  }
2059
2280
  if (generated.guestUnits.length > 0) {
2060
2281
  args.push(
@@ -2132,7 +2353,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2132
2353
  failureMessage: 'Fastly Native start wrapper composition failed.'
2133
2354
  }
2134
2355
  );
2135
- runGuestTool(
2356
+ if (emitWat) runGuestTool(
2136
2357
  'wasm-dis',
2137
2358
  [wasmFile, '--mvp-features', '-o', watFile],
2138
2359
  {
@@ -2162,7 +2383,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2162
2383
  });
2163
2384
  }
2164
2385
  const primaryWasm = fs.readFileSync(wasmFile);
2165
- const primaryWat = fs.existsSync(watFile) ? fs.readFileSync(watFile, 'utf8') : '';
2386
+ const primaryWat = emitWat ? fs.readFileSync(watFile, 'utf8') : '';
2166
2387
  const primaryArtifact = Object.freeze({
2167
2388
  bytes: primaryWasm.length,
2168
2389
  sha256: sha256(primaryWasm)
@@ -2195,7 +2416,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2195
2416
  realization = Object.freeze({
2196
2417
  ...realization,
2197
2418
  wasm: stageResult.wasm,
2198
- wat: stageResult.wat,
2419
+ // Required guest-link disassembly audits remain enabled.
2420
+ wat: emitWat ? stageResult.wat : '',
2199
2421
  guestUnits: stageResult.guestUnits,
2200
2422
  guestLink: Object.freeze({
2201
2423
  version: stageResult.version,
@@ -2213,9 +2435,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2213
2435
  const wat = realization.wat;
2214
2436
  if (requestedOutDir && generated.guestUnits.length > 0) {
2215
2437
  fs.writeFileSync(wasmFile, wasm);
2216
- fs.writeFileSync(watFile, wat, 'utf8');
2438
+ if (emitWat) fs.writeFileSync(watFile, wat, 'utf8');
2217
2439
  }
2218
- const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm);
2440
+ const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm, { maxWasmBytes });
2219
2441
  const imported = new Set(inspection.imports.map((entry) => `${entry.module}:${entry.name}`));
2220
2442
  const requiredImports = requiredImportsForPlan(plan, generated.bindings);
2221
2443
  const missingRequiredImports = requiredImports.filter((key) => !imported.has(key));
@@ -2229,7 +2451,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2229
2451
  : undefined,
2230
2452
  optimization,
2231
2453
  wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
2232
- wat: Object.freeze({ bytes: Buffer.byteLength(wat), sha256: sha256(wat) }),
2454
+ wat: Object.freeze(emitWat
2455
+ ? { emitted: true, bytes: Buffer.byteLength(wat), sha256: sha256(wat) }
2456
+ : { emitted: false, bytes: 0, sha256: null }),
2233
2457
  importModules: inspection.importModules,
2234
2458
  imports: inspection.imports,
2235
2459
  requiredImports,
@@ -2262,7 +2486,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2262
2486
  assemblyScriptDurationMs,
2263
2487
  primaryArtifact,
2264
2488
  startWrapper,
2265
- output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile }) : undefined
2489
+ output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile: emitWat ? watFile : null }) : undefined
2266
2490
  });
2267
2491
  } finally {
2268
2492
  fs.rmSync(stagingDir, { recursive: true, force: true });
@@ -2280,10 +2504,12 @@ function writeFastlyNativePlatformCapabilitiesModule(compiled, outDir, options =
2280
2504
  const manifestFile = path.join(target, options.manifestFile || 'fastly-native-platform-capabilities-manifest.json');
2281
2505
  fs.writeFileSync(sourceFile, compiled.source, 'utf8');
2282
2506
  fs.writeFileSync(wasmFile, compiled.wasm);
2283
- fs.writeFileSync(watFile, compiled.wat, 'utf8');
2507
+ const emitWat = compiled.manifest.wat.emitted !== false;
2508
+ if (emitWat) fs.writeFileSync(watFile, compiled.wat, 'utf8');
2509
+ else fs.rmSync(watFile, { force: true });
2284
2510
  fs.writeFileSync(planFile, `${stableStringify(compiled.plan, 2)}\n`, 'utf8');
2285
2511
  fs.writeFileSync(manifestFile, `${stableStringify(compiled.manifest, 2)}\n`, 'utf8');
2286
- return Object.freeze({ target, sourceFile, wasmFile, watFile, planFile, manifestFile, manifest: compiled.manifest });
2512
+ return Object.freeze({ target, sourceFile, wasmFile, watFile: emitWat ? watFile : null, planFile, manifestFile, manifest: compiled.manifest });
2287
2513
  }
2288
2514
 
2289
2515
  module.exports = Object.freeze({