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

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 +354 -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 +31 -2
  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');
@@ -15,6 +22,7 @@ const {
15
22
  } = require('@pulse-compute/wasm-contracts/package/package-contract');
16
23
  const {
17
24
  EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION,
25
+ prepareReportCapture,
18
26
  appendAssemblyScriptOptimizationArgs
19
27
  } = require('@pulse-compute/wasm-build-support/native-optimization');
20
28
 
@@ -92,7 +100,10 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION = 'pulse.fastly-native-platfor
92
100
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_GENERATOR_VERSION = 'pulse.fastly-native-platform-capabilities-generator.v1';
93
101
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION = 'pulse.fastly-native-platform-capabilities-compiler.v1';
94
102
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION = 1;
95
- const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = 1024 * 1024;
103
+ // Internal compilation budget, not a Fastly service limit. Keep a finite guard
104
+ // while allowing composed applications beyond the original 1 MiB proof fixture.
105
+ const { DEFAULT_FASTLY_MAX_WASM_BYTES, normalizeFastlyMaxWasmBytes, assertFastlyWasmBudget } = require('./wasm-budget.js');
106
+ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = DEFAULT_FASTLY_MAX_WASM_BYTES;
96
107
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES = 65536;
97
108
  const FASTLY_NATIVE_SIZE_OPTIMIZATION = EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION;
98
109
 
@@ -156,9 +167,11 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KIND = Object.freeze({
156
167
  'grip.broadcast': 9,
157
168
  'assets.lookup': 10,
158
169
  'jwt.verify': 11,
170
+ 'jwt.sign': 20,
159
171
  's3.head': 12,
160
172
  's3.getText': 13,
161
173
  's3.putText': 14,
174
+ 's3.getBody': 21,
162
175
  'kv.getVersioned': 15,
163
176
  'kv.insertIfAbsent': 16,
164
177
  'kv.compareAndSwap': 17,
@@ -185,9 +198,11 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
185
198
  'grip.broadcast',
186
199
  'assets.lookup',
187
200
  'jwt.verify',
201
+ 'jwt.sign',
188
202
  's3.head',
189
203
  's3.getText',
190
- 's3.putText'
204
+ 's3.putText',
205
+ 's3.getBody'
191
206
  ]);
192
207
 
193
208
  const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
@@ -204,9 +219,11 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
204
219
  'grip.publish',
205
220
  'grip.broadcast',
206
221
  'jwt.verify',
222
+ 'jwt.sign',
207
223
  's3.head',
208
224
  's3.getText',
209
- 's3.putText'
225
+ 's3.putText',
226
+ 's3.getBody'
210
227
  ]);
211
228
 
212
229
  class FastlyNativePlatformCapabilitiesError extends Error {
@@ -268,45 +285,38 @@ function packageFacts(_plan, options = {}) {
268
285
  });
269
286
  }
270
287
 
288
+ function jwtVerificationAlgorithm(effect) {
289
+ return effect.resource.keyType === 'secret' ? 'HS256' : String(effect.resource.keyArtifactId).startsWith('pulse.jwt.rs256-key.') ? 'RS256' : 'ES256';
290
+ }
291
+
271
292
  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
- );
293
+ const effects = plan.effects || [];
294
+ const signing = effects.filter(effect => effect.kind === 'jwt.sign');
295
+ const verifies = effects.filter(effect => effect.kind === 'jwt.verify');
296
+ const required = new Set([
297
+ ...signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm') === 'HS256' ? 'HMAC-SHA256' : literalObjectField(inputExpression(effect, 'payload'), 'algorithm')),
298
+ ...verifies.map(effect => jwtVerificationAlgorithm(effect))
299
+ ]);
300
+ if (!required.size) return undefined;
301
+ const definitions = {
302
+ 'HMAC-SHA256': ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
303
+ HS256: ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
304
+ RS256: ['guest-linked:pulse-rs256-bearssl-i31', 'bearssl.0.6.rsa-i31.sha256.v1', 'guest-linked'],
305
+ ES256: ['guest-linked:pulse-es256-rustcrypto-p256', 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1', 'guest-linked']
306
+ };
307
+ for (const algorithm of required) {
308
+ const selected = plan.crypto?.algorithms.find(entry => entry.algorithm === algorithm);
309
+ const expected = definitions[algorithm];
310
+ if (!expected || !selected || selected.realization !== expected[0] || selected.implementation !== expected[1]
311
+ || selected.kind !== expected[2] || selected.targetImplemented !== true || selected.automaticFallback !== false) {
312
+ fail('JWT requires its explicitly selected Crypto realization.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
313
+ }
301
314
  }
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
- });
315
+ const verificationAlgorithms = [...new Set(verifies.map(effect => jwtVerificationAlgorithm(effect)))];
316
+ if (verificationAlgorithms.length > 1) fail('Fastly Native JWT verification requires one exact algorithm.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
317
+ const algorithm = verificationAlgorithms[0] || (required.has('RS256') ? 'RS256' : required.has('ES256') ? 'ES256' : 'HMAC-SHA256');
318
+ return Object.freeze({ ...plan.crypto.algorithms.find(entry => entry.algorithm === algorithm),
319
+ guestUnitRequired: required.has('ES256') || required.has('RS256'), signingAlgorithms: signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm')) });
310
320
  }
311
321
 
312
322
  function decodeP256Coordinate(value, artifactId, field) {
@@ -328,6 +338,26 @@ function decodeP256Coordinate(value, artifactId, field) {
328
338
  return bytes;
329
339
  }
330
340
 
341
+ function normalizedRs256Jwk(value, artifactId) {
342
+ const invalid = () => fail('Invalid bounded RSA public key artifact.', 'PULSE_FASTLY_NATIVE_JWT_KEY_ARTIFACT_INVALID', { artifactId });
343
+ if (!value || typeof value !== 'object' || Array.isArray(value)
344
+ || Object.keys(value).some(name => !['kty', 'n', 'e', 'alg', 'use', 'key_ops', 'kid'].includes(name))
345
+ || value.kty !== 'RSA' || value.alg !== undefined && value.alg !== 'RS256'
346
+ || value.use !== undefined && value.use !== 'sig'
347
+ || value.key_ops !== undefined && (!Array.isArray(value.key_ops) || value.key_ops.length !== 1 || value.key_ops[0] !== 'verify')
348
+ || value.kid !== undefined && (typeof value.kid !== 'string' || !value.kid.length || value.kid.includes('\0') || Buffer.byteLength(value.kid) > 256)) invalid();
349
+ const decode = (text, maximum) => {
350
+ if (typeof text !== 'string' || text.length > Math.ceil(maximum * 4 / 3) || !/^[A-Za-z0-9_-]+$/.test(text)) invalid();
351
+ const bytes = Buffer.from(text, 'base64url');
352
+ if (!bytes.length || bytes.length > maximum || bytes[0] === 0 || bytes.toString('base64url') !== text) invalid();
353
+ return bytes;
354
+ };
355
+ const n = decode(value.n, 512), e = decode(value.e, 4), k = n.length;
356
+ if (![256, 384, 512].includes(k) || n[0] < 128 || !(n[k-1] & 1) || e.readUIntBE(0, e.length) < 3 || !(e.at(-1) & 1)) invalid();
357
+ const bytes = Buffer.alloc(8+k); bytes.writeUInt32LE(k); n.copy(bytes, 4); e.copy(bytes, 8+k-e.length);
358
+ return Object.freeze({ kid: value.kid, bytes });
359
+ }
360
+
331
361
  function normalizedEs256Jwk(value, artifactId) {
332
362
  if (
333
363
  !value
@@ -374,13 +404,14 @@ function resolveEs256KeyArtifacts(plan, facts) {
374
404
  }
375
405
  const artifactId = effect.resource.keyArtifactId;
376
406
  const artifact = artifacts.get(artifactId);
407
+ const algorithm = jwtVerificationAlgorithm(effect).toLowerCase();
377
408
  if (
378
409
  !artifact
379
- || artifact.version !== 'pulse.jwt-es256-key-artifact.v1'
410
+ || artifact.version !== `pulse.jwt-${algorithm}-key-artifact.v1`
380
411
  || artifact.contractId !== 'pulse.jwt'
381
412
  || artifact.package !== '@pulse-compute/jwt'
382
- || artifact.kind !== 'jwt-es256-static-public-key'
383
- || artifact.mediaType !== 'application/vnd.pulse.jwt-es256-key+json'
413
+ || artifact.kind !== `jwt-${algorithm}-static-public-key`
414
+ || artifact.mediaType !== `application/vnd.pulse.jwt-${algorithm}-key+json`
384
415
  || artifact.id !== artifactId
385
416
  || artifact.materialHash !== sha256(stableStringify(artifact.data))
386
417
  || !artifact.data
@@ -406,7 +437,7 @@ function resolveEs256KeyArtifacts(plan, facts) {
406
437
  { effectId: effect.id, artifactId, keyCount: keys && keys.length }
407
438
  );
408
439
  }
409
- const normalized = keys.map((key) => normalizedEs256Jwk(key, artifactId));
440
+ const normalized = keys.map((key) => algorithm === 'rs256' ? normalizedRs256Jwk(key, artifactId) : normalizedEs256Jwk(key, artifactId));
410
441
  const kids = normalized.map((key) => key.kid).filter((kid) => kid !== undefined);
411
442
  if (new Set(kids).size !== kids.length) {
412
443
  fail(
@@ -482,8 +513,71 @@ function es256KeyArtifactSource(records) {
482
513
  return lines.join('\n');
483
514
  }
484
515
 
516
+ function jwtSigningCryptoCompositionSource(selected) {
517
+ const algorithms = selected?.signingAlgorithms || [];
518
+ const hs = algorithms.includes('HS256');
519
+ const es = algorithms.includes('ES256');
520
+ const rs = algorithms.includes('RS256');
521
+ return `function __pulse_fastly_jwt_crypto_sign(algorithm: string, key: Uint8Array, data: Uint8Array, output: Uint8Array): i32 {
522
+ ${hs ? `if (algorithm == "HS256") return pulse_crypto_hmac_sha256_bytes_v1(key.dataStart, key.length, data.dataStart, data.length, output.dataStart, output.length)` : ''}
523
+ ${rs ? `if (algorithm == "RS256") return __pulse_fastly_rs256(true, key, data, output)` : ''}
524
+ ${es ? `if (algorithm == "ES256") {
525
+ if (key.length != 96 || output.length != 64 || data.length > 16340) return -2
526
+ const pointer: usize = 524288
527
+ memory.fill(pointer, 0, 16640)
528
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
529
+ store<u32>(pointer + 12, (224 + data.length + 15) & ~15); store<u32>(pointer + 16, 1)
530
+ store<u32>(pointer + 24, 224); store<u32>(pointer + 28, data.length)
531
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, 96)
532
+ store<u32>(pointer + 40, 160); store<u32>(pointer + 44, 64)
533
+ memory.copy(pointer + 64, key.dataStart, 96); memory.copy(pointer + 224, data.dataStart, data.length)
534
+ const status = __pulse_crypto_es256_sign_anchor(i32(pointer), 16640)
535
+ if (status == 1) memory.copy(output.dataStart, pointer + 160, 64)
536
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
537
+ return status
538
+ }` : ''}
539
+ return -3
540
+ }`;
541
+ }
542
+
485
543
  function jwtCryptoCompositionSource(selected) {
486
- if (!selected) {
544
+ const rsa = selected && (selected.algorithm === 'RS256' || selected.signingAlgorithms?.includes('RS256'));
545
+ const helper = rsa ? rs256CompositionSource() : '';
546
+ return helper + jwtVerificationCompositionSource(selected);
547
+ }
548
+
549
+ function rs256CompositionSource() {
550
+ return `function __pulse_fastly_rs256(signing: bool, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
551
+ if (key.length < 8 || data.length > 12288) return -2
552
+ const k = load<u32>(key.dataStart)
553
+ if ((k != 256 && k != 384 && k != 512) || signature.length != i32(k) || key.length != i32(signing ? 8 + k * 9 / 2 : 8 + k)) return -2
554
+ const pointer: usize = 524288
555
+ const output = (64 + key.length + 15) & ~15
556
+ const input = output + signature.length
557
+ memory.fill(pointer, 0, 16640)
558
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
559
+ store<u32>(pointer + 12, (input + data.length + 15) & ~15); store<u32>(pointer + 16, 2)
560
+ store<u32>(pointer + 24, input); store<u32>(pointer + 28, data.length)
561
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, key.length)
562
+ store<u32>(pointer + 40, output); store<u32>(pointer + 44, k)
563
+ memory.copy(pointer + 64, key.dataStart, key.length); memory.copy(pointer + input, data.dataStart, data.length)
564
+ if (!signing) memory.copy(pointer + output, signature.dataStart, signature.length)
565
+ const status = signing ? __pulse_crypto_rs256_sign_anchor(i32(pointer), 16640) : __pulse_crypto_rs256_verify_anchor(i32(pointer), 16640)
566
+ if (signing && status == 1) memory.copy(signature.dataStart, pointer + output, signature.length)
567
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
568
+ return status
569
+ }\n`;
570
+ }
571
+
572
+ function jwtVerificationCompositionSource(selected) {
573
+ if (selected?.algorithm === 'RS256') return `
574
+ function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { __pulse_fastly_jwt_record_signing_input(input.byteLength, "") }
575
+ function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
576
+ if (algorithm != "RS256") return -2
577
+ return __pulse_fastly_rs256(false, key, data, signature)
578
+ }`;
579
+
580
+ if (!selected || selected.algorithm === 'HMAC-SHA256') {
487
581
  return [
488
582
  'function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { void input }',
489
583
  '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 +690,10 @@ function validatePlanBoundary(plan, options = {}) {
596
690
  && effect.kind === 'assets.lookup';
597
691
  const jwt = effect.package === '@pulse-compute/jwt'
598
692
  && effect.contractId === 'pulse.jwt'
599
- && effect.kind === 'jwt.verify'
600
- && effect.operation === 'verify'
601
- && effect.capability === 'jwt.verify';
693
+ && ['verify', 'sign'].includes(effect.operation)
694
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
602
695
  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;
696
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
604
697
  const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
605
698
  && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
606
699
  return !grip && !assets && !jwt && !s3 && !digest;
@@ -620,11 +713,10 @@ function validatePlanBoundary(plan, options = {}) {
620
713
  && effect.kind === 'assets.lookup';
621
714
  const jwt = effect.package === '@pulse-compute/jwt'
622
715
  && effect.contractId === 'pulse.jwt'
623
- && effect.kind === 'jwt.verify'
624
- && effect.operation === 'verify'
625
- && effect.capability === 'jwt.verify';
716
+ && ['verify', 'sign'].includes(effect.operation)
717
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
626
718
  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;
719
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
628
720
  const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
629
721
  && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
630
722
  return !grip && !assets && !jwt && !s3 && !digest;
@@ -632,7 +724,7 @@ function validatePlanBoundary(plan, options = {}) {
632
724
  if (unsupportedDeclarations.length > 0) {
633
725
  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
726
  }
635
- for (const effect of effects.filter((entry) => entry.kind === 'jwt.verify')) {
727
+ for (const effect of effects.filter((entry) => ['jwt.verify', 'jwt.sign'].includes(entry.kind))) {
636
728
  const resource = effect.resource;
637
729
  const knownShape = resource
638
730
  && !Object.keys(resource)
@@ -647,7 +739,7 @@ function validatePlanBoundary(plan, options = {}) {
647
739
  && typeof resource.keyArtifactId === 'string'
648
740
  && resource.keyArtifactId.length > 0
649
741
  && resource.secretBinding === null;
650
- if (!namedSecret && !staticPublicKey) {
742
+ if (!namedSecret && (!staticPublicKey || effect.kind === 'jwt.sign')) {
651
743
  fail('Fastly Native JWT requires one exact named-secret or bounded static public-key descriptor.', 'PULSE_FASTLY_NATIVE_JWT_KEY_DESCRIPTOR_INVALID', {
652
744
  effectId: effect.id,
653
745
  resource,
@@ -713,8 +805,8 @@ function resolveCapabilityBindings(plan, options = {}) {
713
805
  const effects = plan.effects || [];
714
806
  const hasConfig = effects.some((effect) => effect.kind === 'config.get');
715
807
  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')
808
+ effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)
809
+ || (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource && effect.resource.keyType === 'secret')
718
810
  ));
719
811
  const hasGripHold = effects.some((effect) => effect.kind === 'grip.hold');
720
812
  const hasGripBroadcast = effects.some((effect) => effect.kind === 'grip.broadcast');
@@ -740,6 +832,7 @@ function resolveCapabilityBindings(plan, options = {}) {
740
832
  for (const [index, effect] of effects.entries()) {
741
833
  if (effect.kind !== 'kv.get' && effect.kind !== 'kv.put' && !conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) && effect.kind !== 'assets.lookup') continue;
742
834
  const packagePayload = inputExpression(effect, 'payload');
835
+ if (effect.kind === 'assets.lookup' && literalObjectField(packagePayload, 'embeddedId')) continue;
743
836
  const logical = effect.resource && effect.resource.kind === 'literal'
744
837
  && effect.kind !== 'assets.lookup'
745
838
  ? String(effect.resource.value)
@@ -838,7 +931,7 @@ function resolveCapabilityBindings(plan, options = {}) {
838
931
  maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs),
839
932
  configStore,
840
933
  secretStore,
841
- s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload: { contentType: literalObjectField(inputExpression(effect, 'payload'), 'contentType') || undefined } })), source.s3),
934
+ 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
935
  kv: Object.freeze(kv),
843
936
  fetch: Object.freeze(fetch),
844
937
  backends: Object.freeze(backends),
@@ -872,7 +965,7 @@ function planRequiresFetchBodyRead(plan) {
872
965
  if (Array.isArray(value)) return value.some(visit);
873
966
  return Object.values(value).some(visit);
874
967
  }
875
- return visit(plan.entry);
968
+ return visit(plan.entry) || visit(plan.handlers) || visit(plan.stages) || visit(plan.helpers);
876
969
  }
877
970
 
878
971
  function requiredImportsForPlan(plan, bindings) {
@@ -900,7 +993,7 @@ function requiredImportsForPlan(plan, bindings) {
900
993
  const gripAuthenticationRequired = kinds.has('grip.broadcast')
901
994
  && (!bindings || Boolean(bindings.grip && bindings.grip.authentication));
902
995
  const jwtSecretRequired = (plan.effects || []).some((effect) => (
903
- effect.kind === 'jwt.verify'
996
+ ['jwt.verify', 'jwt.sign'].includes(effect.kind)
904
997
  && effect.resource
905
998
  && effect.resource.keyType === 'secret'
906
999
  ));
@@ -908,7 +1001,7 @@ function requiredImportsForPlan(plan, bindings) {
908
1001
  for (const key of ['fastly_secret_store:open', 'fastly_secret_store:get', 'fastly_secret_store:plaintext']) keys.add(key);
909
1002
  }
910
1003
  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');
1004
+ if (kinds.has('jwt.verify') || kinds.has('jwt.sign') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
912
1005
  if (kinds.has('kv.get') || kinds.has('kv.put') || kinds.has('assets.lookup')) keys.add('fastly_kv_store:open');
913
1006
  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
1007
  if (kinds.has('kv.put')) for (const key of ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait']) keys.add(key);
@@ -917,7 +1010,7 @@ function requiredImportsForPlan(plan, bindings) {
917
1010
  keys.add('fastly_log:endpoint_get');
918
1011
  keys.add('fastly_log:write');
919
1012
  }
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);
1013
+ 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
1014
  return Object.freeze([...keys].sort());
922
1015
  }
923
1016
 
@@ -928,15 +1021,40 @@ function stripPulseHostImports(source) {
928
1021
  .join('\n');
929
1022
  }
930
1023
 
1024
+ function schemaTextEncodeIds(plan) {
1025
+ // Keep the PS3 accounting/failure boundary and all unproved codec families.
1026
+ if (nativeValueBudget.hasBoundedReadLoop(plan)) return [];
1027
+ function closedText(node, depth = 0) {
1028
+ if (depth > 16) return false;
1029
+ if (node.kind === 'string' || node.kind === 'boolean') return true;
1030
+ if (node.kind === 'nullable') return closedText(node.value, depth);
1031
+ if (node.kind === 'array') return closedText(node.element, depth + 1);
1032
+ return node.kind === 'object' && !node.additionalProperties
1033
+ && node.fields.every(field => closedText(field.value, depth + 1));
1034
+ }
1035
+ return ((plan.schemas && plan.schemas.registry && plan.schemas.registry.schemas) || [])
1036
+ .filter(schema => !schema.jsonLimits && schema.root.kind === 'object'
1037
+ && schemaNeedsValueProjection(schema.root) && closedText(schema.root))
1038
+ .map(schema => schema.id);
1039
+ }
1040
+
931
1041
  function generateSchemaRuntime(plan) {
932
1042
  const registry = plan.schemas && plan.schemas.registry ? plan.schemas.registry : { schemas: [], maxBytes: FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES, contentTypePolicy: 'accept-json-or-missing' };
933
1043
  const schemas = registry.schemas || [];
1044
+ const textEncodeIds = new Set(schemaTextEncodeIds(plan));
934
1045
  const nodeLines = [];
1046
+ const scalarProjectors = new Map();
1047
+ const flatObjectProjectors = new Map();
1048
+ const scalarKinds = ['string', 'boolean', 'i32', 'u32', 'f64', 'string-enum'];
935
1049
  let nodeIndex = 0;
936
1050
 
937
1051
  function emitNode(schemaIndex, node) {
938
1052
  const currentIndex = nodeIndex++;
939
1053
  const functionName = `__pulse_fastly_schema_${schemaIndex}_${currentIndex}`;
1054
+ const flatObject = node.kind === 'object' && !node.additionalProperties
1055
+ && node.fields.every(field => scalarKinds.includes(field.value.kind));
1056
+ // Locals belong to this function, so equivalent flat bodies need no node ID.
1057
+ const localIndex = flatObject ? 'flat' : currentIndex;
940
1058
  const child = node.kind === 'nullable'
941
1059
  ? emitNode(schemaIndex, node.value)
942
1060
  : node.kind === 'array'
@@ -945,32 +1063,43 @@ function generateSchemaRuntime(plan) {
945
1063
  const fields = node.kind === 'object'
946
1064
  ? node.fields.map((field) => Object.freeze({ field, apply: emitNode(schemaIndex, field.value) }))
947
1065
  : [];
948
- const body = [`function ${functionName}(valueHandle: i32): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
1066
+ const body = [`function ${functionName}(valueHandle: i32, checkDuplicates: bool): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
949
1067
  if (node.kind === 'nullable') {
950
1068
  body.push(' if (input.kind == PULSE_VALUE_NULL) return valueHandle');
951
- body.push(` return ${child}(valueHandle)`);
1069
+ body.push(` return ${child}(valueHandle, checkDuplicates)`);
952
1070
  } else if (node.kind === 'array') {
953
1071
  body.push(' if (input.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
954
1072
  body.push(' const output = host_value_array()');
955
1073
  body.push(' for (let index = 0; index < input.values.length; index += 1) {');
956
- body.push(` const item = ${child}(unchecked(input.values[index]))`);
1074
+ body.push(` const item = ${child}(unchecked(input.values[index]), checkDuplicates)`);
957
1075
  body.push(' if (item <= 0) return 0');
958
1076
  body.push(' host_value_array_push(output, item)');
959
1077
  body.push(' }');
960
1078
  body.push(' return output');
1079
+ } else if (node.kind === 'json-value' || node.kind === 'json-object') {
1080
+ body.push(...nestedJsonProjectionLines(node));
1081
+ } else if (node.kind === 'scalar-record') {
1082
+ body.push(...scalarRecordProjectionLines(node));
961
1083
  } else if (node.kind === 'object') {
962
1084
  body.push(' if (input.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 50, -1); return 0 }');
1085
+ if (node.additionalProperties) body.push(' if (checkDuplicates && input.duplicateJsonKeys) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }');
963
1086
  body.push(' const output = host_value_object()');
964
1087
  fields.forEach(({ field, apply }, fieldIndex) => {
965
- body.push(` const key_${currentIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
1088
+ body.push(` const key_${localIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
966
1089
  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})`);
1090
+ body.push(` const field_${localIndex}_${fieldIndex} = host_value_property(valueHandle, key_${localIndex}_${fieldIndex})`);
1091
+ body.push(` if (__pulse_fastly_value(field_${localIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
1092
+ body.push(` const projected_${localIndex}_${fieldIndex} = ${apply}(field_${localIndex}_${fieldIndex}, checkDuplicates)`);
1093
+ body.push(` if (projected_${localIndex}_${fieldIndex} <= 0) return 0`);
1094
+ body.push(` host_value_object_set(output, key_${localIndex}_${fieldIndex}, projected_${localIndex}_${fieldIndex})`);
972
1095
  if (!field.required) body.push(' }');
973
1096
  });
1097
+ if (node.additionalProperties) {
1098
+ body.push(' for (let index = 0; index < input.keys.length; index++) {', ' const key = unchecked(input.keys[index])');
1099
+ if (node.fields.length) body.push(` if (${node.fields.map(field => `key == ${quote(field.name)}`).join(' || ')}) continue`);
1100
+ body.push(' const item = __pulse_fastly_copy_json(unchecked(input.values[index]), checkDuplicates)',
1101
+ ' if (item <= 0) return 0', ' host_value_object_set(output, __pulse_fastly_string_value(key), item)', ' }');
1102
+ }
974
1103
  body.push(' return output');
975
1104
  } else if (node.kind === 'string') {
976
1105
  body.push(' if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
@@ -995,6 +1124,32 @@ function generateSchemaRuntime(plan) {
995
1124
  throw new TypeError(`Unsupported Fastly native schema node ${String(node.kind)}.`);
996
1125
  }
997
1126
  body.push('}', '');
1127
+ // Only scalar validators preserve the original handle without allocating or
1128
+ // calling child projectors. Compare the exact emitted body, including enum
1129
+ // values/order and error stages.
1130
+ if (scalarKinds.includes(node.kind)) {
1131
+ const key = body.slice(1).join('\n');
1132
+ const existing = scalarProjectors.get(key);
1133
+ if (existing) return existing;
1134
+ const sharedName = `__pulse_fastly_schema_scalar_${scalarProjectors.size}`;
1135
+ scalarProjectors.set(key, sharedName);
1136
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1137
+ nodeLines.push(...body);
1138
+ return sharedName;
1139
+ }
1140
+ // Share code only after child scalar identities are resolved. Exact bodies
1141
+ // retain field order, presence checks, validation and errors. Each call still
1142
+ // allocates its own output; open/nested/nullable/array bodies remain separate.
1143
+ if (flatObject) {
1144
+ const key = body.slice(1).join('\n');
1145
+ const existing = flatObjectProjectors.get(key);
1146
+ if (existing) return existing;
1147
+ const sharedName = `__pulse_fastly_schema_flat_object_${flatObjectProjectors.size}`;
1148
+ flatObjectProjectors.set(key, sharedName);
1149
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1150
+ nodeLines.push(...body);
1151
+ return sharedName;
1152
+ }
998
1153
  nodeLines.push(...body);
999
1154
  return functionName;
1000
1155
  }
@@ -1004,17 +1159,22 @@ function generateSchemaRuntime(plan) {
1004
1159
  `const __PULSE_SCHEMA_MAX_BYTES: i32 = ${Number(registry.maxBytes || FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES)}`,
1005
1160
  `const __PULSE_SCHEMA_REQUIRE_JSON: bool = ${registry.contentTypePolicy === 'require-json' ? 'true' : 'false'}`,
1006
1161
  '',
1162
+ schemaJsonAdmissionSource(plan),
1163
+ schemaFetchJsonSource(),
1007
1164
  ...nodeLines,
1008
- 'function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool): i32 {',
1165
+ `function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool${textEncodeIds.size ? ', textResult: bool = false' : ''}): i32 {`,
1009
1166
  ' if (schemaId.length == 0) return valueHandle'
1010
1167
  ];
1011
1168
  for (const [schemaIndex, schema] of schemas.entries()) {
1012
1169
  lines.push(`${schemaIndex === 0 ? ' if' : ' else if'} (schemaId == ${quote(schema.id)}) {`);
1013
- lines.push(` const projected = ${roots[schemaIndex]}(valueHandle)`);
1170
+ 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 }`);
1171
+ lines.push(` const projected = ${roots[schemaIndex]}(valueHandle, !encode)`);
1014
1172
  lines.push(' if (projected <= 0) return 0');
1015
1173
  lines.push(' const input = __pulse_fastly_json(projected, 0)');
1016
1174
  lines.push(` const normalized = encode ? __pulse_schema_encode_${schemaIndex}(input) : __pulse_schema_decode_${schemaIndex}(input)`);
1017
- lines.push(' return __pulse_fastly_parse_json(normalized)');
1175
+ if (textEncodeIds.has(schema.id)) lines.push(' if (textResult && encode) return __pulse_fastly_string_value(normalized)');
1176
+ if (schema.jsonLimits) lines.push(' const output = __pulse_fastly_parse_json(normalized)', ' if (output > 0) __pulse_fastly_deep_freeze(output)', ' return output');
1177
+ else lines.push(' return __pulse_fastly_parse_json(normalized)');
1018
1178
  lines.push(' }');
1019
1179
  }
1020
1180
  lines.push(' __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1)');
@@ -1037,7 +1197,7 @@ function effectResultSource(plan, bindings) {
1037
1197
  lines.push(`function __pulse_fastly_secret_store(effectIndex: i32): string { return ${quote(bindings.secretStore)} }`, '');
1038
1198
  lines.push('function __pulse_fastly_jwt_secret_binding(effectIndex: i32): string {', ' switch (effectIndex) {');
1039
1199
  for (const [index, effect] of (plan.effects || []).entries()) {
1040
- if (effect.kind === 'jwt.verify' && effect.resource.keyType === 'secret') {
1200
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource.keyType === 'secret') {
1041
1201
  lines.push(` case ${index}: return ${quote(effect.resource.secretBinding)}`);
1042
1202
  }
1043
1203
  }
@@ -1052,12 +1212,31 @@ function effectResultSource(plan, bindings) {
1052
1212
  for (const binding of bindings.grip.publish) lines.push(` case ${binding.index}: return ${quote(binding.authSecretRef || '')}`);
1053
1213
  lines.push(' default: return ""', ' }', '}', '');
1054
1214
 
1215
+ // A config lookup has the same ready-result handoff at every static site.
1216
+ // Keep the site indices in the switch; pass the active index through so the
1217
+ // pending slot and any state error still belong to the original invocation.
1218
+ const configSites = (plan.effects || []).flatMap((effect, index) => effect.kind === 'config.get' ? [index] : []);
1219
+ if (configSites.length > 1) {
1220
+ lines.push('function __pulse_fastly_resolve_config_get(effectIndex: i32): i32 {');
1221
+ lines.push(' const response = __pulse_fastly_wait_ready(effectIndex)');
1222
+ lines.push(' if (response <= 0) return 0');
1223
+ lines.push(' return response');
1224
+ lines.push('}', '');
1225
+ }
1226
+
1055
1227
  lines.push('function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {');
1056
1228
  lines.push(' switch (effectIndex) {');
1057
1229
  for (const [index, effect] of (plan.effects || []).entries()) {
1230
+ if (effect.kind === 'config.get' && configSites.length > 1) {
1231
+ if (index === configSites[0]) {
1232
+ for (const site of configSites) lines.push(` case ${site}:`);
1233
+ lines.push(' return __pulse_fastly_resolve_config_get(effectIndex)');
1234
+ }
1235
+ continue;
1236
+ }
1058
1237
  const result = effect.result || {};
1059
1238
  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'
1239
+ 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
1240
  ? '__pulse_fastly_wait_fetch'
1062
1241
  : effect.kind === 'grip.publish'
1063
1242
  ? '__pulse_fastly_wait_grip_publish'
@@ -1099,9 +1278,11 @@ function effectDispatchSource(plan) {
1099
1278
  'config.get': '__pulse_fastly_config_begin',
1100
1279
  'secret.get': '__pulse_fastly_secret_begin',
1101
1280
  'jwt.verify': '__pulse_fastly_jwt_begin',
1281
+ 'jwt.sign': '__pulse_fastly_jwt_sign_begin',
1102
1282
  's3.head': '__pulse_fastly_s3_begin',
1103
1283
  's3.getText': '__pulse_fastly_s3_begin',
1104
1284
  's3.putText': '__pulse_fastly_s3_begin',
1285
+ 's3.getBody': '__pulse_fastly_s3_begin',
1105
1286
  'kv.get': '__pulse_fastly_kv_get_begin',
1106
1287
  'kv.put': '__pulse_fastly_kv_put_begin',
1107
1288
  ...Object.fromEntries(conditionalKv.KV_CONDITIONAL_KINDS.map(kind => [kind, '__pulse_fastly_kv_conditional_begin'])),
@@ -1135,7 +1316,14 @@ function effectDispatchSource(plan) {
1135
1316
  }
1136
1317
 
1137
1318
  function fastlyRuntimeSource(plan, bindings, options = {}) {
1319
+ const textEncodeIds = schemaTextEncodeIds(plan);
1138
1320
  const effectCount = (plan.effects || []).length;
1321
+ const hasAssets = (plan.effects || []).some(effect => effect.kind === 'assets.lookup');
1322
+ 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 }
1323
+ 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) }` : '';
1324
+ 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" }
1325
+ 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])) }
1326
+ 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
1327
  const jwtCrypto = options.jwtCrypto || selectedJwtCrypto(plan);
1140
1328
  const es256KeyRecords = options.es256KeyRecords || Object.freeze([]);
1141
1329
  const jwtReality = options.jwtReality && options.jwtReality.enabled === true
@@ -1214,7 +1402,7 @@ function __pulse_fastly_jwt_send_error(): void {}
1214
1402
  const gripAuthorizationSource = gripAuthentication
1215
1403
  ? `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
1404
  : '';
1217
- return String.raw`
1405
+ const source = String.raw`
1218
1406
  @external("fastly_abi", "init") declare function fastly_abi_init(version: i64): i32
1219
1407
  @external("fastly_http_req", "body_downstream_get") declare function fastly_http_req_body_downstream_get(requestOut: usize, bodyOut: usize): i32
1220
1408
  @external("fastly_http_req", "method_get") declare function fastly_http_req_method_get(handle: i32, buffer: usize, bufferLength: i32, writtenOut: usize): i32
@@ -1286,6 +1474,7 @@ const PULSE_ERROR_JWT: i32 = 1008
1286
1474
  const PULSE_ERROR_REQUEST_BODY: i32 = 1009
1287
1475
 
1288
1476
  class __PulseFastlyValue {
1477
+ ${tracksJsonDuplicates(plan) ? 'duplicateJsonKeys: bool = false' : ''}
1289
1478
  kind: i32 = PULSE_VALUE_UNDEFINED
1290
1479
  boolean: i32 = 0
1291
1480
  number: f64 = 0.0
@@ -1305,6 +1494,9 @@ ${nativeStringFields}
1305
1494
  responseHandle: i32 = 0
1306
1495
  bodyHandle: i32 = 0
1307
1496
  bodyLoaded: i32 = 0
1497
+ s3Body: bool = false
1498
+ s3Deadline: i64 = 0
1499
+ s3Length: i32 = 0
1308
1500
  status: i32 = 200
1309
1501
  // Scalar handles do not need collection backing stores.
1310
1502
  private headersStorage: Array<__PulseFastlyHeader> | null = null
@@ -1312,6 +1504,7 @@ ${nativeStringFields}
1312
1504
  if (this.headersStorage === null) this.headersStorage = new Array<__PulseFastlyHeader>()
1313
1505
  return this.headersStorage!
1314
1506
  }
1507
+ binaryBody: Uint8Array | null = null
1315
1508
  immutable: bool = false
1316
1509
  }
1317
1510
  class __PulseFastlyHeader {
@@ -1328,7 +1521,7 @@ class __PulseJsonParser {
1328
1521
  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
1522
  value(depth: i32): i32 {
1330
1523
  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 }
1524
+ 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
1525
  const c = this.source.charCodeAt(this.index)
1333
1526
  if (c == 34) return __pulse_fastly_string_value(this.string())
1334
1527
  if (c == 123) return this.object(depth + 1)
@@ -1342,14 +1535,18 @@ class __PulseJsonParser {
1342
1535
  string(): string {
1343
1536
  // Find a bounded span, then decode each UTF-16 unit once. Concatenating one
1344
1537
  // character at a time exhausts stub/fixed memory on a valid maximum KV value.
1345
- const start = this.index + 1; let end = start;
1538
+ const start = this.index + 1; let end = start; let plain = true;
1346
1539
  while (end < this.source.length) {
1347
1540
  const c = this.source.charCodeAt(end);
1348
1541
  if (c == 34) break;
1349
- if (c == 92) end += 1;
1542
+ if (c == 92) { plain = false; end += 1; }
1543
+ else if (c < 32) plain = false;
1350
1544
  end += 1;
1351
1545
  }
1352
1546
  if (end >= this.source.length) { this.failed = true; return "" }
1547
+ // A validated span needs one owned string, without a temporary UTF-16 array.
1548
+ // Keep escaped and malformed spans on the existing decoder path.
1549
+ if (plain) { this.index = end + 1; return this.source.substring(start, end); }
1353
1550
  const output = new Uint16Array(end - start); let count = 0; this.index = start;
1354
1551
  while (this.index < end) {
1355
1552
  let c = this.source.charCodeAt(this.index++);
@@ -1427,6 +1624,7 @@ class __PulseJsonParser {
1427
1624
  const key = this.string(); this.skip()
1428
1625
  if (this.conditionalKv && __pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) { this.failed = true; break }
1429
1626
  if (this.index >= this.source.length || this.source.charCodeAt(this.index) != 58) { this.failed = true; break }
1627
+ ${tracksJsonDuplicates(plan) ? 'if (__pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) __pulse_fastly_value(output).duplicateJsonKeys = true' : ''}
1430
1628
  this.index += 1; host_value_object_set(output, __pulse_fastly_string_value(key), this.value(depth)); this.skip()
1431
1629
  if (this.index >= this.source.length) { this.failed = true; break }
1432
1630
  const c = this.source.charCodeAt(this.index); this.index += 1
@@ -1533,7 +1731,7 @@ function __pulse_fastly_quote(value: string): string {
1533
1731
  return String.UTF16.decodeUnsafe(out.dataStart, out.byteLength);
1534
1732
  }
1535
1733
  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 }
1734
+ 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
1735
  const value = __pulse_fastly_value(handle)
1538
1736
  if (value.kind == PULSE_VALUE_BOOLEAN) { parts.push(value.boolean != 0 ? "true" : "false"); return }
1539
1737
  if (value.kind == PULSE_VALUE_NUMBER) { parts.push(__pulse_fastly_number_string(value.number)); return }
@@ -1623,7 +1821,7 @@ function host_request_path(): i32 { return __pulse_fastly_string_value(__pulse_f
1623
1821
  function host_request_headers(): i32 { return __pulse_request_headers_read() }
1624
1822
  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
1823
  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 }
1824
+ 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
1825
  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
1826
  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
1827
  function host_response_json(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, true) }
@@ -1633,7 +1831,7 @@ function host_schema_decode(text: i32, schema: i32): i32 {
1633
1831
  const input = __pulse_fastly_value(text)
1634
1832
  if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }
1635
1833
  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)
1834
+ const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(input.text, schema)' : '__pulse_fastly_parse_json(input.text)'}
1637
1835
  if (parsed <= 0) return 0
1638
1836
  const decoded = __pulse_fastly_schema_apply(id.text, parsed, false)
1639
1837
  if (decoded <= 0) return 0
@@ -1643,11 +1841,12 @@ function host_schema_decode(text: i32, schema: i32): i32 {
1643
1841
  function host_schema_encode(value: i32, schema: i32): i32 {
1644
1842
  const id = __pulse_fastly_value(schema)
1645
1843
  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)
1844
+ ${textEncodeIds.length ? `const textResult = ${textEncodeIds.map(id => `id.text == ${quote(id)}`).join(' || ')}
1845
+ const projected = __pulse_fastly_schema_apply(id.text, value, true, textResult)` : 'const projected = __pulse_fastly_schema_apply(id.text, value, true)'}
1647
1846
  if (projected <= 0) return 0
1648
- const text = __pulse_fastly_json(projected, 0)
1847
+ const text = ${textEncodeIds.length ? 'textResult ? __pulse_fastly_string(projected) : ' : ''}__pulse_fastly_json(projected, 0)
1649
1848
  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)
1849
+ return ${textEncodeIds.length ? 'textResult ? projected : ' : ''}__pulse_fastly_string_value(text)
1651
1850
  }
1652
1851
  function host_response_text(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, false) }
1653
1852
  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 +1857,7 @@ function host_grip_subscribe(responseHandle: i32, optionsHandle: i32): i32 { con
1658
1857
  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
1858
  function host_kv_namespace(name: i32): i32 { return name }
1660
1859
  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 }
1860
+ 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
1861
  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
1862
  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
1863
  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 +1878,12 @@ function __pulse_fastly_jwt_record_verified(): void { __pulse_fastly_jwt_verifie
1679
1878
  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
1879
  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
1880
  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) }
1881
+ 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
1882
  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
1883
  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)) }
1884
+ 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
1885
  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) }
1886
+ ${assetBeginSource}
1688
1887
  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
1888
  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
1889
  function __pulse_fastly_grip_payload(payload: __PulseFastlyValue): __PulseFastlyValue { return __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")) }
@@ -1698,19 +1897,22 @@ function __pulse_fastly_wait_grip_publish(effectIndex: i32): i32 { const respons
1698
1897
  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
1898
  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
1899
  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) }
1900
+ ${assetResultSource}
1704
1901
  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
1902
  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) }
1903
+ 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
1904
 
1708
1905
  ${jwtEvidenceSource}
1709
1906
  ${es256KeyArtifactSource(es256KeyRecords)}
1710
1907
  ${jwtCryptoCompositionSource(jwtCrypto)}
1908
+ ${jwtSigningCryptoCompositionSource(jwtCrypto)}
1711
1909
  ${generateSchemaRuntime(plan)}
1712
1910
  ${effectResultSource(plan, bindings)}
1713
1911
  `;
1912
+ if (!hasAssets) return source;
1913
+ const writer = 'status = __pulse_fastly_write_body(bodyHandle, value.text);';
1914
+ if (!source.includes(writer)) throw new Error('Embedded asset response writer boundary drift.');
1915
+ return source.replace(writer, 'status = value.binaryBody !== null ? __pulse_fastly_write_binary(bodyHandle, value.binaryBody!) : __pulse_fastly_write_body(bodyHandle, value.text);');
1714
1916
  }
1715
1917
 
1716
1918
  function driverSource(plan, options = {}) {
@@ -1727,6 +1929,13 @@ export function pulse_fastly_jwt_crypto_calls(): i32 { return __pulse_fastly_jwt
1727
1929
  export function pulse_fastly_jwt_clock_calls(): i32 { return __pulse_fastly_jwt_clock_calls }
1728
1930
  export function pulse_fastly_jwt_schema_calls(): i32 { return __pulse_fastly_jwt_schema_calls }
1729
1931
  export function ${entryName}(): void {
1932
+ if (__pulse_invocation_started) { __pulse_fastly_fail(PULSE_ERROR_STATE, 171, -1); return }
1933
+ __pulse_invocation_started = true
1934
+ __pulse_fastly_run_invocation()
1935
+ ${plan.effects.some(e => e.kind === 's3.getBody') ? '__s3_close_bodies()' : ''}
1936
+ __pulse_invocation_close()
1937
+ }
1938
+ function __pulse_fastly_run_invocation(): void {
1730
1939
  __pulse_fastly_last_error = fastly_abi_init(PULSE_FASTLY_EFFECTS_ABI_VERSION)
1731
1940
  if (__pulse_fastly_last_error != FASTLY_STATUS_OK) { __pulse_fastly_jwt_send_error(); return }
1732
1941
  const handles = new StaticArray<i32>(2)
@@ -1740,12 +1949,13 @@ export function ${entryName}(): void {
1740
1949
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1741
1950
  __pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
1742
1951
  let runStatus = pulse_start()
1743
- ${applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1952
+ ${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1744
1953
  for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
1745
1954
  if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
1955
+ const invocation = unchecked(__pulse_invocation_tickets[effectIndex])
1746
1956
  const result = __pulse_fastly_resolve_effect(effectIndex)
1747
1957
  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 }
1958
+ if (__pulse_invocation_settle(effectIndex, invocation, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
1749
1959
  }
1750
1960
  runStatus = pulse_resume()
1751
1961
  }`}
@@ -1764,7 +1974,7 @@ function capabilityImports(effect) {
1764
1974
  if (effect.kind === 'time.now') return ['wasi_snapshot_preview1.clock_time_get'];
1765
1975
  if (effect.kind === 'config.get') return ['fastly_config_store.open', 'fastly_config_store.get'];
1766
1976
  if (effect.kind === 'secret.get') return ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'];
1767
- if (effect.kind === 'jwt.verify') {
1977
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind)) {
1768
1978
  return [
1769
1979
  ...(effect.resource && effect.resource.keyType === 'secret'
1770
1980
  ? [
@@ -1796,7 +2006,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1796
2006
  validatePlanBoundary(plan, options);
1797
2007
  const facts = packageFacts(plan, options);
1798
2008
  const jwtCrypto = selectedJwtCrypto(plan);
1799
- const es256KeyRecords = jwtCrypto && jwtCrypto.algorithm === 'ES256'
2009
+ const es256KeyRecords = jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1800
2010
  ? resolveEs256KeyArtifacts(plan, facts)
1801
2011
  : Object.freeze([]);
1802
2012
  if (jwtCrypto && jwtCrypto.guestUnitRequired && facts.guestUnits.length !== 1) {
@@ -1820,10 +2030,11 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1820
2030
  );
1821
2031
  }
1822
2032
  const bindings = resolveCapabilityBindings(plan, options);
1823
- const portable = generateCanonicalNativeAssemblyScript(plan, options);
2033
+ const portable = packageApplication.enabled(plan) ? packageApplication.portable(plan) : generateCanonicalNativeAssemblyScript(plan, options);
1824
2034
  const portableSource = stripPulseHostImports(portable.source);
1825
2035
  const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
1826
- const jwtSource = hasJwt ? pulseJwtAssemblyScriptSource() : undefined;
2036
+ const hasSign = plan.effects.some(effect => effect.kind === 'jwt.sign');
2037
+ const jwtSource = hasJwt || hasSign ? pulseJwtAssemblyScriptSource({ includeSigning: hasSign }) : undefined;
1827
2038
  let source = [
1828
2039
  '/* Generated by Pulse Pass98 Fastly native platform capability compiler. */',
1829
2040
  'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
@@ -1835,12 +2046,14 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1835
2046
  }),
1836
2047
  jwtSource ? jwtSource.source : '',
1837
2048
  require('./s3-native.js').s3NativeSource(plan, bindings),
2049
+ 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
2050
  require('./time-native.js').timeNativeSource(plan),
1839
2051
  require('./digest-native.js').digestNativeSource(plan),
1840
2052
  conditionalKv.kvNativeSource(plan),
1841
2053
  require('./native-request-body.js').runtimeSource(),
1842
2054
  require('./native-request-headers.js').runtimeSource(),
1843
- applicationErrors.enabled(plan) ? applicationErrors.runtimeSource() : '',
2055
+ applicationErrors.enabled(plan) ? applicationErrors.runtimeSource(plan) : '',
2056
+ effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan) }),
1844
2057
  portableSource,
1845
2058
  driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
1846
2059
  ''
@@ -1848,7 +2061,10 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1848
2061
  if (applicationErrors.enabled(plan)) source = applicationErrors.instrument(source);
1849
2062
  source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
1850
2063
  source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan));
2064
+ if (packageApplication.enabled(plan)) source = packageApplication.instrument(source);
2065
+ source = effectInvocations.instrument(source);
1851
2066
  source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan);
2067
+ source = nativeValueBudget.instrument(source, plan);
1852
2068
  const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
1853
2069
  output[effect.kind] = (output[effect.kind] || 0) + 1;
1854
2070
  return output;
@@ -1872,10 +2088,14 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1872
2088
  portableGeneratorVersion: portable.version,
1873
2089
  portableSourceHash: portable.sourceHash,
1874
2090
  schemaCodecs: portable.manifest.schemaCodecs,
2091
+ handlerBodies: portable.manifest.handlerBodies,
2092
+ dispatcher: portable.manifest.dispatcher,
2093
+ stages: portable.manifest.stages,
2094
+ helperBodies: portable.manifest.helperBodies,
1875
2095
  crypto: portable.manifest.crypto,
1876
2096
  jwt: jwtSource
1877
2097
  ? Object.freeze({
1878
- status: jwtCrypto && jwtCrypto.algorithm === 'ES256'
2098
+ status: jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1879
2099
  ? 'implemented-g4'
1880
2100
  : 'implemented-e2',
1881
2101
  package: jwtSource.owner,
@@ -1894,6 +2114,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1894
2114
  })
1895
2115
  : Object.freeze({ status: 'inactive' }),
1896
2116
  effectCount: (plan.effects || []).length,
2117
+ effectInvocations: effectInvocations.policy,
1897
2118
  effectKinds,
1898
2119
  continuationCount: (plan.continuations || []).length,
1899
2120
  groupedContinuationCount: (plan.continuations || []).filter((entry) => (entry.effectIds || []).length > 1).length,
@@ -1912,19 +2133,20 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1912
2133
  ...(bindings.maxDurationMs === undefined ? [] : ['pulse_fastly_request_expired'])
1913
2134
  ]),
1914
2135
  policy: Object.freeze({
2136
+ ...(nativeValueBudget.hasBoundedReadLoop(plan) ? { readLoopMemory: nativeValueBudget.policy } : {}),
1915
2137
  nativeFastly: true,
1916
2138
  provider: 'fastly',
1917
2139
  providerNeutralInput: true,
1918
2140
  javascriptRuntime: false,
1919
2141
  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,
2142
+ 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
2143
  effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
1922
2144
  continuations: true,
1923
2145
  groupedEffects: 'existing fetch start-before-wait ordering remains intact',
1924
2146
  config: 'Fastly Config Store open/get with undefined for missing values',
1925
2147
  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`
2148
+ jwt: hasJwt || hasSign
2149
+ ? `package-owned compact-JWS, registered-claim, and bounded issuance semantics composed with ${jwtCrypto.realization}; provider-owned request, secret, clock, schema, and transport`
1928
2150
  : 'inactive',
1929
2151
  kv: 'Fastly KV Store async lookup/insert; conditional operations preserve u64 generations, bounded readiness, strict Pulse envelopes and uncertain dispatch',
1930
2152
  assets: 'Fastly KV Store lookup with provider-owned content type, cache headers, and GET/HEAD body ownership',
@@ -1960,8 +2182,9 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1960
2182
  });
1961
2183
  }
1962
2184
 
1963
- function inspectFastlyNativePlatformCapabilitiesWasm(input) {
2185
+ function inspectFastlyNativePlatformCapabilitiesWasm(input, options = {}) {
1964
2186
  const bytes = Buffer.isBuffer(input) ? input : fs.readFileSync(path.resolve(input));
2187
+ assertFastlyWasmBudget(bytes.length, options.maxWasmBytes);
1965
2188
  if (!WebAssembly.validate(bytes)) fail('Generated Fastly native platform capabilities module is not valid WebAssembly.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_INVALID');
1966
2189
  const module = new WebAssembly.Module(bytes);
1967
2190
  const imports = WebAssembly.Module.imports(module).map((entry) => Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind }));
@@ -1977,7 +2200,6 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1977
2200
  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
2201
  const missingExports = requiredExports.filter((name) => !exportNames.has(name));
1979
2202
  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
2203
  return Object.freeze({
1982
2204
  valid: true,
1983
2205
  bytes: bytes.length,
@@ -1990,6 +2212,7 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1990
2212
  }
1991
2213
 
1992
2214
  function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2215
+ const maxWasmBytes = normalizeFastlyMaxWasmBytes(options.maxWasmBytes);
1993
2216
  const generated = generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options);
1994
2217
  const cwd = path.resolve(options.cwd || process.cwd());
1995
2218
  const compilerFallback = path.resolve(__dirname, '..', '..', '..', '..', 'wasm', 'packages', 'compiler');
@@ -2022,6 +2245,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2022
2245
  }
2023
2246
  jsonAs = Object.freeze({ transform, dependencyRoot, version: packageJson.version });
2024
2247
  }
2248
+ if (options.emitWat !== undefined && typeof options.emitWat !== 'boolean') throw new TypeError('emitWat must be a boolean.');
2249
+ const emitWat = options.emitWat === true;
2025
2250
  const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-fastly-native-platform-'));
2026
2251
  const requestedOutDir = options.outDir ? path.resolve(options.outDir) : undefined;
2027
2252
  const outputDir = requestedOutDir || stagingDir;
@@ -2029,6 +2254,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2029
2254
  const sourceFile = path.join(stagingDir, 'fastly-native-platform-capabilities.as.ts');
2030
2255
  const wasmFile = path.join(outputDir, options.wasmFile || 'fastly-native-platform-capabilities.wasm');
2031
2256
  const watFile = path.join(outputDir, options.watFile || 'fastly-native-platform-capabilities.wat');
2257
+ if (!emitWat) fs.rmSync(watFile, { force: true });
2032
2258
  const guestLinked = generated.guestUnits.length > 0;
2033
2259
  // asc resolves custom runtimes like module names and appends ".ts".
2034
2260
  const fixedMemoryRuntimeModule = path.join(__dirname, 'fixed-memory-runtime.as');
@@ -2045,16 +2271,16 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2045
2271
  asc.script,
2046
2272
  path.basename(sourceFile),
2047
2273
  '--outFile', assemblyScriptWasmFile,
2048
- '--textFile', assemblyScriptWatFile,
2049
2274
  '--runtime', guestLinked
2050
2275
  ? fixedMemoryRuntimeModule
2051
2276
  : (schemaCodecsActive ? 'incremental' : 'stub'),
2052
2277
  '--noAssert',
2053
2278
  '--optimize'
2054
2279
  ];
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');
2280
+ if (emitWat) args.push('--textFile', assemblyScriptWatFile);
2281
+ const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization, { guestLinked });
2282
+ if (!guestLinked && (nativeValueBudget.hasBoundedReadLoop(plan) || plan.effects.some(effect => effect.kind === 'crypto.digestText' || effect.kind.startsWith('s3.')))) {
2283
+ args.push('--maximumMemory', String(nativeValueBudget.policy.maximumMemoryPages));
2058
2284
  }
2059
2285
  if (generated.guestUnits.length > 0) {
2060
2286
  args.push(
@@ -2078,6 +2304,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2078
2304
  '--path', jsonAs.dependencyRoot
2079
2305
  );
2080
2306
  }
2307
+ const readReportCapture = options.reportCapture === false ? () => null : prepareReportCapture(args, stagingDir, generated.manifest,
2308
+ require(path.join(asc.packageRoot, 'package.json')).version, 'fastly-native-platform-capabilities.as/');
2081
2309
  const startedAt = Date.now();
2082
2310
  const result = spawnSync(asc.executable, args, {
2083
2311
  cwd: stagingDir,
@@ -2132,7 +2360,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2132
2360
  failureMessage: 'Fastly Native start wrapper composition failed.'
2133
2361
  }
2134
2362
  );
2135
- runGuestTool(
2363
+ if (emitWat) runGuestTool(
2136
2364
  'wasm-dis',
2137
2365
  [wasmFile, '--mvp-features', '-o', watFile],
2138
2366
  {
@@ -2162,7 +2390,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2162
2390
  });
2163
2391
  }
2164
2392
  const primaryWasm = fs.readFileSync(wasmFile);
2165
- const primaryWat = fs.existsSync(watFile) ? fs.readFileSync(watFile, 'utf8') : '';
2393
+ const primaryWat = emitWat ? fs.readFileSync(watFile, 'utf8') : '';
2166
2394
  const primaryArtifact = Object.freeze({
2167
2395
  bytes: primaryWasm.length,
2168
2396
  sha256: sha256(primaryWasm)
@@ -2195,7 +2423,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2195
2423
  realization = Object.freeze({
2196
2424
  ...realization,
2197
2425
  wasm: stageResult.wasm,
2198
- wat: stageResult.wat,
2426
+ // Required guest-link disassembly audits remain enabled.
2427
+ wat: emitWat ? stageResult.wat : '',
2199
2428
  guestUnits: stageResult.guestUnits,
2200
2429
  guestLink: Object.freeze({
2201
2430
  version: stageResult.version,
@@ -2213,9 +2442,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2213
2442
  const wat = realization.wat;
2214
2443
  if (requestedOutDir && generated.guestUnits.length > 0) {
2215
2444
  fs.writeFileSync(wasmFile, wasm);
2216
- fs.writeFileSync(watFile, wat, 'utf8');
2445
+ if (emitWat) fs.writeFileSync(watFile, wat, 'utf8');
2217
2446
  }
2218
- const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm);
2447
+ const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm, { maxWasmBytes });
2219
2448
  const imported = new Set(inspection.imports.map((entry) => `${entry.module}:${entry.name}`));
2220
2449
  const requiredImports = requiredImportsForPlan(plan, generated.bindings);
2221
2450
  const missingRequiredImports = requiredImports.filter((key) => !imported.has(key));
@@ -2229,7 +2458,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2229
2458
  : undefined,
2230
2459
  optimization,
2231
2460
  wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
2232
- wat: Object.freeze({ bytes: Buffer.byteLength(wat), sha256: sha256(wat) }),
2461
+ wat: Object.freeze(emitWat
2462
+ ? { emitted: true, bytes: Buffer.byteLength(wat), sha256: sha256(wat) }
2463
+ : { emitted: false, bytes: 0, sha256: null }),
2233
2464
  importModules: inspection.importModules,
2234
2465
  imports: inspection.imports,
2235
2466
  requiredImports,
@@ -2247,6 +2478,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2247
2478
  return Object.freeze({
2248
2479
  version: FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION,
2249
2480
  compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
2481
+ reportAttribution: readReportCapture(wasm),
2250
2482
  plan,
2251
2483
  generated,
2252
2484
  source: generated.source,
@@ -2262,7 +2494,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2262
2494
  assemblyScriptDurationMs,
2263
2495
  primaryArtifact,
2264
2496
  startWrapper,
2265
- output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile }) : undefined
2497
+ output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile: emitWat ? watFile : null }) : undefined
2266
2498
  });
2267
2499
  } finally {
2268
2500
  fs.rmSync(stagingDir, { recursive: true, force: true });
@@ -2280,10 +2512,12 @@ function writeFastlyNativePlatformCapabilitiesModule(compiled, outDir, options =
2280
2512
  const manifestFile = path.join(target, options.manifestFile || 'fastly-native-platform-capabilities-manifest.json');
2281
2513
  fs.writeFileSync(sourceFile, compiled.source, 'utf8');
2282
2514
  fs.writeFileSync(wasmFile, compiled.wasm);
2283
- fs.writeFileSync(watFile, compiled.wat, 'utf8');
2515
+ const emitWat = compiled.manifest.wat.emitted !== false;
2516
+ if (emitWat) fs.writeFileSync(watFile, compiled.wat, 'utf8');
2517
+ else fs.rmSync(watFile, { force: true });
2284
2518
  fs.writeFileSync(planFile, `${stableStringify(compiled.plan, 2)}\n`, 'utf8');
2285
2519
  fs.writeFileSync(manifestFile, `${stableStringify(compiled.manifest, 2)}\n`, 'utf8');
2286
- return Object.freeze({ target, sourceFile, wasmFile, watFile, planFile, manifestFile, manifest: compiled.manifest });
2520
+ return Object.freeze({ target, sourceFile, wasmFile, watFile: emitWat ? watFile : null, planFile, manifestFile, manifest: compiled.manifest });
2287
2521
  }
2288
2522
 
2289
2523
  module.exports = Object.freeze({