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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/dist/config-schema.json +10 -0
  3. package/dist/index.d.ts +5 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +4 -0
  6. package/dist/index.js.map +1 -1
  7. package/package.json +11 -11
  8. package/src/build/canonical-target.js +15 -2
  9. package/src/build/digest-native.js +25 -0
  10. package/src/build/effect-invocations.js +49 -0
  11. package/src/build/embedded-assets.as.ts +32 -0
  12. package/src/build/kv-native.as.ts +18 -8
  13. package/src/build/native-application-errors.js +140 -0
  14. package/src/build/native-http-effects.js +87 -22
  15. package/src/build/native-platform-capabilities.js +480 -151
  16. package/src/build/native-request-body.js +44 -0
  17. package/src/build/native-request-headers.js +93 -0
  18. package/src/build/native-string-values.js +113 -0
  19. package/src/build/native-value-budget.js +58 -0
  20. package/src/build/package-native-application.js +94 -0
  21. package/src/build/request-budget.as.ts +49 -0
  22. package/src/build/request-budget.js +45 -0
  23. package/src/build/s3-body.as.ts +97 -0
  24. package/src/build/s3-native.as.ts +12 -4
  25. package/src/build/s3-native.js +2 -2
  26. package/src/build/schema-admission.js +83 -0
  27. package/src/build/schema-nested-json.js +93 -0
  28. package/src/build/schema-scalar-record.js +41 -0
  29. package/src/build/time-native.js +49 -0
  30. package/src/build/wasm-budget.js +28 -0
  31. package/src/config-api.js +3 -0
  32. package/src/config-schema.json +10 -0
  33. package/src/index.ts +9 -0
  34. package/src/javascript/jwt-verifier.js +14 -4
  35. package/src/javascript/package-effects.js +5 -2
  36. package/src/javascript/runtime-host.js +5 -2
  37. package/src/javascript/target-support-policy.js +18 -5
  38. package/src/javascript/target.js +16 -1
  39. package/src/provider-contract.js +7 -3
  40. package/src/runtime/canonical-api-runtime.js +2 -0
  41. package/src/testing/native-http-effects-host.js +5 -1
  42. package/src/testing/native-platform-capabilities-host.js +78 -6
  43. package/src/toolchain/config.js +8 -1
  44. package/src/toolchain/index.js +31 -2
  45. package/src/toolchain/native-execution.js +1 -0
  46. package/src/toolchain/s3.js +2 -2
@@ -1,6 +1,15 @@
1
1
  'use strict';
2
2
 
3
+ const { scalarRecordProjectionLines } = require('./schema-scalar-record.js');
4
+ const { hasJsonAdmission, tracksJsonDuplicates, schemaParserDepth, nestedJsonProjectionLines, schemaJsonAdmissionSource, schemaFetchJsonSource } = require('./schema-nested-json.js');
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');
10
+
3
11
  const crypto = require('node:crypto');
12
+ const { nativeStringFields, nativeStringConcat, nativeStringTrim, nativeStringIndex, needsNativeValueFailureGuard } = require('./native-string-values.js');
4
13
  const conditionalKv = require('./kv-native.js');
5
14
  const fs = require('node:fs');
6
15
  const os = require('node:os');
@@ -90,7 +99,10 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION = 'pulse.fastly-native-platfor
90
99
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_GENERATOR_VERSION = 'pulse.fastly-native-platform-capabilities-generator.v1';
91
100
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION = 'pulse.fastly-native-platform-capabilities-compiler.v1';
92
101
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION = 1;
93
- const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = 1024 * 1024;
102
+ // Internal compilation budget, not a Fastly service limit. Keep a finite guard
103
+ // while allowing composed applications beyond the original 1 MiB proof fixture.
104
+ const { DEFAULT_FASTLY_MAX_WASM_BYTES, normalizeFastlyMaxWasmBytes, assertFastlyWasmBudget } = require('./wasm-budget.js');
105
+ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = DEFAULT_FASTLY_MAX_WASM_BYTES;
94
106
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES = 65536;
95
107
  const FASTLY_NATIVE_SIZE_OPTIMIZATION = EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION;
96
108
 
@@ -102,6 +114,8 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
102
114
  Object.freeze(['fastly_http_req', 'method_get']),
103
115
  Object.freeze(['fastly_http_req', 'uri_get']),
104
116
  Object.freeze(['fastly_http_req', 'header_value_get']),
117
+ Object.freeze(['fastly_http_req', 'header_names_get']),
118
+ Object.freeze(['fastly_http_req', 'header_values_get']),
105
119
  Object.freeze(['fastly_http_req', 'new']),
106
120
  Object.freeze(['fastly_http_req', 'method_set']),
107
121
  Object.freeze(['fastly_http_req', 'uri_set']),
@@ -152,12 +166,16 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KIND = Object.freeze({
152
166
  'grip.broadcast': 9,
153
167
  'assets.lookup': 10,
154
168
  'jwt.verify': 11,
169
+ 'jwt.sign': 20,
155
170
  's3.head': 12,
156
171
  's3.getText': 13,
157
172
  's3.putText': 14,
173
+ 's3.getBody': 21,
158
174
  'kv.getVersioned': 15,
159
175
  'kv.insertIfAbsent': 16,
160
- 'kv.compareAndSwap': 17
176
+ 'kv.compareAndSwap': 17,
177
+ 'time.now': 18,
178
+ 'crypto.digestText': 19
161
179
  });
162
180
 
163
181
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS = new Set([
@@ -165,6 +183,8 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS = new Set([
165
183
  ]);
166
184
 
167
185
  const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
186
+ 'time.now',
187
+ 'crypto.digestText',
168
188
  'fetch',
169
189
  'config.get',
170
190
  'secret.get',
@@ -177,12 +197,16 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
177
197
  'grip.broadcast',
178
198
  'assets.lookup',
179
199
  'jwt.verify',
200
+ 'jwt.sign',
180
201
  's3.head',
181
202
  's3.getText',
182
- 's3.putText'
203
+ 's3.putText',
204
+ 's3.getBody'
183
205
  ]);
184
206
 
185
207
  const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
208
+ 'time.now',
209
+ 'crypto.digestText',
186
210
  'config.get',
187
211
  'secret.get',
188
212
  'kv.get',
@@ -194,9 +218,11 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
194
218
  'grip.publish',
195
219
  'grip.broadcast',
196
220
  'jwt.verify',
221
+ 'jwt.sign',
197
222
  's3.head',
198
223
  's3.getText',
199
- 's3.putText'
224
+ 's3.putText',
225
+ 's3.getBody'
200
226
  ]);
201
227
 
202
228
  class FastlyNativePlatformCapabilitiesError extends Error {
@@ -258,45 +284,38 @@ function packageFacts(_plan, options = {}) {
258
284
  });
259
285
  }
260
286
 
287
+ function jwtVerificationAlgorithm(effect) {
288
+ return effect.resource.keyType === 'secret' ? 'HS256' : String(effect.resource.keyArtifactId).startsWith('pulse.jwt.rs256-key.') ? 'RS256' : 'ES256';
289
+ }
290
+
261
291
  function selectedJwtCrypto(plan) {
262
- const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
263
- if (!hasJwt) return undefined;
264
- const algorithms = plan.crypto && Array.isArray(plan.crypto.algorithms)
265
- ? plan.crypto.algorithms.filter((entry) => ['HS256', 'ES256'].includes(entry.algorithm))
266
- : [];
267
- if (algorithms.length !== 1) {
268
- fail(
269
- 'Fastly Native JWT requires one exact crypto realization.',
270
- 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID',
271
- { algorithms, automaticFallback: false }
272
- );
273
- }
274
- const selected = algorithms[0];
275
- const hs256 = selected.algorithm === 'HS256'
276
- && selected.realization === 'guest-source:pulse-hmac-as'
277
- && selected.implementation === 'pulse-hmac-as.v1'
278
- && selected.kind === 'guest-source'
279
- && selected.targetImplemented === true;
280
- const es256 = selected.algorithm === 'ES256'
281
- && selected.realization === 'guest-linked:pulse-es256-rustcrypto-p256'
282
- && selected.implementation === 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1'
283
- && selected.kind === 'guest-linked'
284
- && selected.targetImplemented === true;
285
- if ((!hs256 && !es256) || selected.automaticFallback !== false) {
286
- fail(
287
- 'Fastly Native JWT selected an unavailable or non-exact crypto realization.',
288
- 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID',
289
- { selected, automaticFallback: false }
290
- );
292
+ const effects = plan.effects || [];
293
+ const signing = effects.filter(effect => effect.kind === 'jwt.sign');
294
+ const verifies = effects.filter(effect => effect.kind === 'jwt.verify');
295
+ const required = new Set([
296
+ ...signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm') === 'HS256' ? 'HMAC-SHA256' : literalObjectField(inputExpression(effect, 'payload'), 'algorithm')),
297
+ ...verifies.map(effect => jwtVerificationAlgorithm(effect))
298
+ ]);
299
+ if (!required.size) return undefined;
300
+ const definitions = {
301
+ 'HMAC-SHA256': ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
302
+ HS256: ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
303
+ RS256: ['guest-linked:pulse-rs256-bearssl-i31', 'bearssl.0.6.rsa-i31.sha256.v1', 'guest-linked'],
304
+ ES256: ['guest-linked:pulse-es256-rustcrypto-p256', 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1', 'guest-linked']
305
+ };
306
+ for (const algorithm of required) {
307
+ const selected = plan.crypto?.algorithms.find(entry => entry.algorithm === algorithm);
308
+ const expected = definitions[algorithm];
309
+ if (!expected || !selected || selected.realization !== expected[0] || selected.implementation !== expected[1]
310
+ || selected.kind !== expected[2] || selected.targetImplemented !== true || selected.automaticFallback !== false) {
311
+ fail('JWT requires its explicitly selected Crypto realization.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
312
+ }
291
313
  }
292
- return Object.freeze({
293
- algorithm: selected.algorithm,
294
- realization: selected.realization,
295
- implementation: selected.implementation,
296
- kind: selected.kind,
297
- guestUnitRequired: es256,
298
- automaticFallback: false
299
- });
314
+ const verificationAlgorithms = [...new Set(verifies.map(effect => jwtVerificationAlgorithm(effect)))];
315
+ if (verificationAlgorithms.length > 1) fail('Fastly Native JWT verification requires one exact algorithm.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
316
+ const algorithm = verificationAlgorithms[0] || (required.has('RS256') ? 'RS256' : required.has('ES256') ? 'ES256' : 'HMAC-SHA256');
317
+ return Object.freeze({ ...plan.crypto.algorithms.find(entry => entry.algorithm === algorithm),
318
+ guestUnitRequired: required.has('ES256') || required.has('RS256'), signingAlgorithms: signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm')) });
300
319
  }
301
320
 
302
321
  function decodeP256Coordinate(value, artifactId, field) {
@@ -318,6 +337,26 @@ function decodeP256Coordinate(value, artifactId, field) {
318
337
  return bytes;
319
338
  }
320
339
 
340
+ function normalizedRs256Jwk(value, artifactId) {
341
+ const invalid = () => fail('Invalid bounded RSA public key artifact.', 'PULSE_FASTLY_NATIVE_JWT_KEY_ARTIFACT_INVALID', { artifactId });
342
+ if (!value || typeof value !== 'object' || Array.isArray(value)
343
+ || Object.keys(value).some(name => !['kty', 'n', 'e', 'alg', 'use', 'key_ops', 'kid'].includes(name))
344
+ || value.kty !== 'RSA' || value.alg !== undefined && value.alg !== 'RS256'
345
+ || value.use !== undefined && value.use !== 'sig'
346
+ || value.key_ops !== undefined && (!Array.isArray(value.key_ops) || value.key_ops.length !== 1 || value.key_ops[0] !== 'verify')
347
+ || value.kid !== undefined && (typeof value.kid !== 'string' || !value.kid.length || value.kid.includes('\0') || Buffer.byteLength(value.kid) > 256)) invalid();
348
+ const decode = (text, maximum) => {
349
+ if (typeof text !== 'string' || text.length > Math.ceil(maximum * 4 / 3) || !/^[A-Za-z0-9_-]+$/.test(text)) invalid();
350
+ const bytes = Buffer.from(text, 'base64url');
351
+ if (!bytes.length || bytes.length > maximum || bytes[0] === 0 || bytes.toString('base64url') !== text) invalid();
352
+ return bytes;
353
+ };
354
+ const n = decode(value.n, 512), e = decode(value.e, 4), k = n.length;
355
+ if (![256, 384, 512].includes(k) || n[0] < 128 || !(n[k-1] & 1) || e.readUIntBE(0, e.length) < 3 || !(e.at(-1) & 1)) invalid();
356
+ const bytes = Buffer.alloc(8+k); bytes.writeUInt32LE(k); n.copy(bytes, 4); e.copy(bytes, 8+k-e.length);
357
+ return Object.freeze({ kid: value.kid, bytes });
358
+ }
359
+
321
360
  function normalizedEs256Jwk(value, artifactId) {
322
361
  if (
323
362
  !value
@@ -364,13 +403,14 @@ function resolveEs256KeyArtifacts(plan, facts) {
364
403
  }
365
404
  const artifactId = effect.resource.keyArtifactId;
366
405
  const artifact = artifacts.get(artifactId);
406
+ const algorithm = jwtVerificationAlgorithm(effect).toLowerCase();
367
407
  if (
368
408
  !artifact
369
- || artifact.version !== 'pulse.jwt-es256-key-artifact.v1'
409
+ || artifact.version !== `pulse.jwt-${algorithm}-key-artifact.v1`
370
410
  || artifact.contractId !== 'pulse.jwt'
371
411
  || artifact.package !== '@pulse-compute/jwt'
372
- || artifact.kind !== 'jwt-es256-static-public-key'
373
- || artifact.mediaType !== 'application/vnd.pulse.jwt-es256-key+json'
412
+ || artifact.kind !== `jwt-${algorithm}-static-public-key`
413
+ || artifact.mediaType !== `application/vnd.pulse.jwt-${algorithm}-key+json`
374
414
  || artifact.id !== artifactId
375
415
  || artifact.materialHash !== sha256(stableStringify(artifact.data))
376
416
  || !artifact.data
@@ -396,7 +436,7 @@ function resolveEs256KeyArtifacts(plan, facts) {
396
436
  { effectId: effect.id, artifactId, keyCount: keys && keys.length }
397
437
  );
398
438
  }
399
- const normalized = keys.map((key) => normalizedEs256Jwk(key, artifactId));
439
+ const normalized = keys.map((key) => algorithm === 'rs256' ? normalizedRs256Jwk(key, artifactId) : normalizedEs256Jwk(key, artifactId));
400
440
  const kids = normalized.map((key) => key.kid).filter((kid) => kid !== undefined);
401
441
  if (new Set(kids).size !== kids.length) {
402
442
  fail(
@@ -472,8 +512,71 @@ function es256KeyArtifactSource(records) {
472
512
  return lines.join('\n');
473
513
  }
474
514
 
515
+ function jwtSigningCryptoCompositionSource(selected) {
516
+ const algorithms = selected?.signingAlgorithms || [];
517
+ const hs = algorithms.includes('HS256');
518
+ const es = algorithms.includes('ES256');
519
+ const rs = algorithms.includes('RS256');
520
+ return `function __pulse_fastly_jwt_crypto_sign(algorithm: string, key: Uint8Array, data: Uint8Array, output: Uint8Array): i32 {
521
+ ${hs ? `if (algorithm == "HS256") return pulse_crypto_hmac_sha256_bytes_v1(key.dataStart, key.length, data.dataStart, data.length, output.dataStart, output.length)` : ''}
522
+ ${rs ? `if (algorithm == "RS256") return __pulse_fastly_rs256(true, key, data, output)` : ''}
523
+ ${es ? `if (algorithm == "ES256") {
524
+ if (key.length != 96 || output.length != 64 || data.length > 16340) return -2
525
+ const pointer: usize = 524288
526
+ memory.fill(pointer, 0, 16640)
527
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
528
+ store<u32>(pointer + 12, (224 + data.length + 15) & ~15); store<u32>(pointer + 16, 1)
529
+ store<u32>(pointer + 24, 224); store<u32>(pointer + 28, data.length)
530
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, 96)
531
+ store<u32>(pointer + 40, 160); store<u32>(pointer + 44, 64)
532
+ memory.copy(pointer + 64, key.dataStart, 96); memory.copy(pointer + 224, data.dataStart, data.length)
533
+ const status = __pulse_crypto_es256_sign_anchor(i32(pointer), 16640)
534
+ if (status == 1) memory.copy(output.dataStart, pointer + 160, 64)
535
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
536
+ return status
537
+ }` : ''}
538
+ return -3
539
+ }`;
540
+ }
541
+
475
542
  function jwtCryptoCompositionSource(selected) {
476
- if (!selected) {
543
+ const rsa = selected && (selected.algorithm === 'RS256' || selected.signingAlgorithms?.includes('RS256'));
544
+ const helper = rsa ? rs256CompositionSource() : '';
545
+ return helper + jwtVerificationCompositionSource(selected);
546
+ }
547
+
548
+ function rs256CompositionSource() {
549
+ return `function __pulse_fastly_rs256(signing: bool, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
550
+ if (key.length < 8 || data.length > 12288) return -2
551
+ const k = load<u32>(key.dataStart)
552
+ if ((k != 256 && k != 384 && k != 512) || signature.length != i32(k) || key.length != i32(signing ? 8 + k * 9 / 2 : 8 + k)) return -2
553
+ const pointer: usize = 524288
554
+ const output = (64 + key.length + 15) & ~15
555
+ const input = output + signature.length
556
+ memory.fill(pointer, 0, 16640)
557
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
558
+ store<u32>(pointer + 12, (input + data.length + 15) & ~15); store<u32>(pointer + 16, 2)
559
+ store<u32>(pointer + 24, input); store<u32>(pointer + 28, data.length)
560
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, key.length)
561
+ store<u32>(pointer + 40, output); store<u32>(pointer + 44, k)
562
+ memory.copy(pointer + 64, key.dataStart, key.length); memory.copy(pointer + input, data.dataStart, data.length)
563
+ if (!signing) memory.copy(pointer + output, signature.dataStart, signature.length)
564
+ const status = signing ? __pulse_crypto_rs256_sign_anchor(i32(pointer), 16640) : __pulse_crypto_rs256_verify_anchor(i32(pointer), 16640)
565
+ if (signing && status == 1) memory.copy(signature.dataStart, pointer + output, signature.length)
566
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
567
+ return status
568
+ }\n`;
569
+ }
570
+
571
+ function jwtVerificationCompositionSource(selected) {
572
+ if (selected?.algorithm === 'RS256') return `
573
+ function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { __pulse_fastly_jwt_record_signing_input(input.byteLength, "") }
574
+ function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
575
+ if (algorithm != "RS256") return -2
576
+ return __pulse_fastly_rs256(false, key, data, signature)
577
+ }`;
578
+
579
+ if (!selected || selected.algorithm === 'HMAC-SHA256') {
477
580
  return [
478
581
  'function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { void input }',
479
582
  'function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 { void algorithm; void key; void data; void signature; return -3 }'
@@ -586,15 +689,16 @@ function validatePlanBoundary(plan, options = {}) {
586
689
  && effect.kind === 'assets.lookup';
587
690
  const jwt = effect.package === '@pulse-compute/jwt'
588
691
  && effect.contractId === 'pulse.jwt'
589
- && effect.kind === 'jwt.verify'
590
- && effect.operation === 'verify'
591
- && effect.capability === 'jwt.verify';
692
+ && ['verify', 'sign'].includes(effect.operation)
693
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
592
694
  const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
593
- && ['head', 'getText', 'putText'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
594
- return !grip && !assets && !jwt && !s3;
695
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
696
+ const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
697
+ && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
698
+ return !grip && !assets && !jwt && !s3 && !digest;
595
699
  });
596
700
  if (unsupportedPackages.length > 0) {
597
- fail('Fastly native realization only accepts the trusted first-party Assets, GRIP, and JWT package contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', {
701
+ fail('Fastly native realization only accepts the trusted first-party Assets, GRIP, JWT, S3, and Crypto digest package contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', {
598
702
  effects: unsupportedPackages.map((entry) => ({ id: entry.id, kind: entry.kind, package: entry.package, contractId: entry.contractId }))
599
703
  });
600
704
  }
@@ -608,17 +712,18 @@ function validatePlanBoundary(plan, options = {}) {
608
712
  && effect.kind === 'assets.lookup';
609
713
  const jwt = effect.package === '@pulse-compute/jwt'
610
714
  && effect.contractId === 'pulse.jwt'
611
- && effect.kind === 'jwt.verify'
612
- && effect.operation === 'verify'
613
- && effect.capability === 'jwt.verify';
715
+ && ['verify', 'sign'].includes(effect.operation)
716
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
614
717
  const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
615
- && ['head', 'getText', 'putText'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
616
- return !grip && !assets && !jwt && !s3;
718
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
719
+ const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
720
+ && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
721
+ return !grip && !assets && !jwt && !s3 && !digest;
617
722
  });
618
723
  if (unsupportedDeclarations.length > 0) {
619
- fail('Fastly native package realization is restricted to the Assets, GRIP, and JWT contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', { effects: unsupportedDeclarations });
724
+ fail('Fastly native package realization is restricted to the Assets, GRIP, JWT, S3, and Crypto digest contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', { effects: unsupportedDeclarations });
620
725
  }
621
- for (const effect of effects.filter((entry) => entry.kind === 'jwt.verify')) {
726
+ for (const effect of effects.filter((entry) => ['jwt.verify', 'jwt.sign'].includes(entry.kind))) {
622
727
  const resource = effect.resource;
623
728
  const knownShape = resource
624
729
  && !Object.keys(resource)
@@ -633,7 +738,7 @@ function validatePlanBoundary(plan, options = {}) {
633
738
  && typeof resource.keyArtifactId === 'string'
634
739
  && resource.keyArtifactId.length > 0
635
740
  && resource.secretBinding === null;
636
- if (!namedSecret && !staticPublicKey) {
741
+ if (!namedSecret && (!staticPublicKey || effect.kind === 'jwt.sign')) {
637
742
  fail('Fastly Native JWT requires one exact named-secret or bounded static public-key descriptor.', 'PULSE_FASTLY_NATIVE_JWT_KEY_DESCRIPTOR_INVALID', {
638
743
  effectId: effect.id,
639
744
  resource,
@@ -699,8 +804,8 @@ function resolveCapabilityBindings(plan, options = {}) {
699
804
  const effects = plan.effects || [];
700
805
  const hasConfig = effects.some((effect) => effect.kind === 'config.get');
701
806
  const hasSecret = effects.some((effect) => (
702
- effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText'].includes(effect.kind)
703
- || (effect.kind === 'jwt.verify' && effect.resource && effect.resource.keyType === 'secret')
807
+ effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)
808
+ || (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource && effect.resource.keyType === 'secret')
704
809
  ));
705
810
  const hasGripHold = effects.some((effect) => effect.kind === 'grip.hold');
706
811
  const hasGripBroadcast = effects.some((effect) => effect.kind === 'grip.broadcast');
@@ -726,6 +831,7 @@ function resolveCapabilityBindings(plan, options = {}) {
726
831
  for (const [index, effect] of effects.entries()) {
727
832
  if (effect.kind !== 'kv.get' && effect.kind !== 'kv.put' && !conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) && effect.kind !== 'assets.lookup') continue;
728
833
  const packagePayload = inputExpression(effect, 'payload');
834
+ if (effect.kind === 'assets.lookup' && literalObjectField(packagePayload, 'embeddedId')) continue;
729
835
  const logical = effect.resource && effect.resource.kind === 'literal'
730
836
  && effect.kind !== 'assets.lookup'
731
837
  ? String(effect.resource.value)
@@ -821,9 +927,10 @@ function resolveCapabilityBindings(plan, options = {}) {
821
927
  }
822
928
 
823
929
  return Object.freeze({
930
+ maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs),
824
931
  configStore,
825
932
  secretStore,
826
- s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload: { contentType: literalObjectField(inputExpression(effect, 'payload'), 'contentType') || undefined } })), source.s3),
933
+ s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload: Object.fromEntries(['contentType', 'method', 'range', 'ifNoneMatch'].map(name => [name, literalObjectField(inputExpression(effect, 'payload'), name) ?? undefined])) })), source.s3),
827
934
  kv: Object.freeze(kv),
828
935
  fetch: Object.freeze(fetch),
829
936
  backends: Object.freeze(backends),
@@ -857,7 +964,7 @@ function planRequiresFetchBodyRead(plan) {
857
964
  if (Array.isArray(value)) return value.some(visit);
858
965
  return Object.values(value).some(visit);
859
966
  }
860
- return visit(plan.entry);
967
+ return visit(plan.entry) || visit(plan.handlers) || visit(plan.stages) || visit(plan.helpers);
861
968
  }
862
969
 
863
970
  function requiredImportsForPlan(plan, bindings) {
@@ -872,6 +979,7 @@ function requiredImportsForPlan(plan, bindings) {
872
979
  'fastly_http_body:new',
873
980
  'fastly_http_body:write'
874
981
  ]);
982
+ if (bindings && bindings.maxDurationMs !== undefined) { keys.add('wasi_snapshot_preview1:clock_time_get'); keys.add('fastly_async_io:select'); }
875
983
  const kinds = new Set((plan.effects || []).map((effect) => effect.kind));
876
984
  if (kinds.has('fetch') || kinds.has('grip.publish') || kinds.has('grip.broadcast')) {
877
985
  for (const key of [
@@ -884,7 +992,7 @@ function requiredImportsForPlan(plan, bindings) {
884
992
  const gripAuthenticationRequired = kinds.has('grip.broadcast')
885
993
  && (!bindings || Boolean(bindings.grip && bindings.grip.authentication));
886
994
  const jwtSecretRequired = (plan.effects || []).some((effect) => (
887
- effect.kind === 'jwt.verify'
995
+ ['jwt.verify', 'jwt.sign'].includes(effect.kind)
888
996
  && effect.resource
889
997
  && effect.resource.keyType === 'secret'
890
998
  ));
@@ -892,7 +1000,7 @@ function requiredImportsForPlan(plan, bindings) {
892
1000
  for (const key of ['fastly_secret_store:open', 'fastly_secret_store:get', 'fastly_secret_store:plaintext']) keys.add(key);
893
1001
  }
894
1002
  for (const kind of conditionalKv.KV_CONDITIONAL_KINDS) if (kinds.has(kind)) for (const key of conditionalKv.kvImports(kind)) keys.add(key);
895
- if (kinds.has('jwt.verify')) keys.add('wasi_snapshot_preview1:clock_time_get');
1003
+ if (kinds.has('jwt.verify') || kinds.has('jwt.sign') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
896
1004
  if (kinds.has('kv.get') || kinds.has('kv.put') || kinds.has('assets.lookup')) keys.add('fastly_kv_store:open');
897
1005
  if (kinds.has('kv.get') || kinds.has('assets.lookup')) for (const key of ['fastly_kv_store:lookup', 'fastly_kv_store:lookup_wait_v2', 'fastly_http_body:read']) keys.add(key);
898
1006
  if (kinds.has('kv.put')) for (const key of ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait']) keys.add(key);
@@ -901,7 +1009,7 @@ function requiredImportsForPlan(plan, bindings) {
901
1009
  keys.add('fastly_log:endpoint_get');
902
1010
  keys.add('fastly_log:write');
903
1011
  }
904
- if (kinds.has('s3.head') || kinds.has('s3.getText') || kinds.has('s3.putText')) for (const key of require('./s3-native.js').S3_IMPORTS) keys.add(key);
1012
+ if (kinds.has('s3.head') || kinds.has('s3.getText') || kinds.has('s3.putText') || kinds.has('s3.getBody')) for (const key of require('./s3-native.js').S3_IMPORTS) keys.add(key);
905
1013
  return Object.freeze([...keys].sort());
906
1014
  }
907
1015
 
@@ -912,15 +1020,40 @@ function stripPulseHostImports(source) {
912
1020
  .join('\n');
913
1021
  }
914
1022
 
1023
+ function schemaTextEncodeIds(plan) {
1024
+ // Keep the PS3 accounting/failure boundary and all unproved codec families.
1025
+ if (nativeValueBudget.hasBoundedReadLoop(plan)) return [];
1026
+ function closedText(node, depth = 0) {
1027
+ if (depth > 16) return false;
1028
+ if (node.kind === 'string' || node.kind === 'boolean') return true;
1029
+ if (node.kind === 'nullable') return closedText(node.value, depth);
1030
+ if (node.kind === 'array') return closedText(node.element, depth + 1);
1031
+ return node.kind === 'object' && !node.additionalProperties
1032
+ && node.fields.every(field => closedText(field.value, depth + 1));
1033
+ }
1034
+ return ((plan.schemas && plan.schemas.registry && plan.schemas.registry.schemas) || [])
1035
+ .filter(schema => !schema.jsonLimits && schema.root.kind === 'object'
1036
+ && schemaNeedsValueProjection(schema.root) && closedText(schema.root))
1037
+ .map(schema => schema.id);
1038
+ }
1039
+
915
1040
  function generateSchemaRuntime(plan) {
916
1041
  const registry = plan.schemas && plan.schemas.registry ? plan.schemas.registry : { schemas: [], maxBytes: FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES, contentTypePolicy: 'accept-json-or-missing' };
917
1042
  const schemas = registry.schemas || [];
1043
+ const textEncodeIds = new Set(schemaTextEncodeIds(plan));
918
1044
  const nodeLines = [];
1045
+ const scalarProjectors = new Map();
1046
+ const flatObjectProjectors = new Map();
1047
+ const scalarKinds = ['string', 'boolean', 'i32', 'u32', 'f64', 'string-enum'];
919
1048
  let nodeIndex = 0;
920
1049
 
921
1050
  function emitNode(schemaIndex, node) {
922
1051
  const currentIndex = nodeIndex++;
923
1052
  const functionName = `__pulse_fastly_schema_${schemaIndex}_${currentIndex}`;
1053
+ const flatObject = node.kind === 'object' && !node.additionalProperties
1054
+ && node.fields.every(field => scalarKinds.includes(field.value.kind));
1055
+ // Locals belong to this function, so equivalent flat bodies need no node ID.
1056
+ const localIndex = flatObject ? 'flat' : currentIndex;
924
1057
  const child = node.kind === 'nullable'
925
1058
  ? emitNode(schemaIndex, node.value)
926
1059
  : node.kind === 'array'
@@ -929,30 +1062,43 @@ function generateSchemaRuntime(plan) {
929
1062
  const fields = node.kind === 'object'
930
1063
  ? node.fields.map((field) => Object.freeze({ field, apply: emitNode(schemaIndex, field.value) }))
931
1064
  : [];
932
- const body = [`function ${functionName}(valueHandle: i32): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
1065
+ const body = [`function ${functionName}(valueHandle: i32, checkDuplicates: bool): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
933
1066
  if (node.kind === 'nullable') {
934
1067
  body.push(' if (input.kind == PULSE_VALUE_NULL) return valueHandle');
935
- body.push(` return ${child}(valueHandle)`);
1068
+ body.push(` return ${child}(valueHandle, checkDuplicates)`);
936
1069
  } else if (node.kind === 'array') {
937
1070
  body.push(' if (input.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
938
1071
  body.push(' const output = host_value_array()');
939
1072
  body.push(' for (let index = 0; index < input.values.length; index += 1) {');
940
- body.push(` const item = ${child}(unchecked(input.values[index]))`);
1073
+ body.push(` const item = ${child}(unchecked(input.values[index]), checkDuplicates)`);
941
1074
  body.push(' if (item <= 0) return 0');
942
1075
  body.push(' host_value_array_push(output, item)');
943
1076
  body.push(' }');
944
1077
  body.push(' return output');
1078
+ } else if (node.kind === 'json-value' || node.kind === 'json-object') {
1079
+ body.push(...nestedJsonProjectionLines(node));
1080
+ } else if (node.kind === 'scalar-record') {
1081
+ body.push(...scalarRecordProjectionLines(node));
945
1082
  } else if (node.kind === 'object') {
946
1083
  body.push(' if (input.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 50, -1); return 0 }');
1084
+ if (node.additionalProperties) body.push(' if (checkDuplicates && input.duplicateJsonKeys) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }');
947
1085
  body.push(' const output = host_value_object()');
948
1086
  fields.forEach(({ field, apply }, fieldIndex) => {
949
- body.push(` const key_${currentIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
950
- body.push(` const field_${currentIndex}_${fieldIndex} = host_value_property(valueHandle, key_${currentIndex}_${fieldIndex})`);
951
- body.push(` if (__pulse_fastly_value(field_${currentIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
952
- body.push(` const projected_${currentIndex}_${fieldIndex} = ${apply}(field_${currentIndex}_${fieldIndex})`);
953
- body.push(` if (projected_${currentIndex}_${fieldIndex} <= 0) return 0`);
954
- body.push(` host_value_object_set(output, key_${currentIndex}_${fieldIndex}, projected_${currentIndex}_${fieldIndex})`);
1087
+ body.push(` const key_${localIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
1088
+ if (!field.required) body.push(` if (__pulse_fastly_find(input, ${quote(field.name)}) >= 0) {`);
1089
+ body.push(` const field_${localIndex}_${fieldIndex} = host_value_property(valueHandle, key_${localIndex}_${fieldIndex})`);
1090
+ body.push(` if (__pulse_fastly_value(field_${localIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
1091
+ body.push(` const projected_${localIndex}_${fieldIndex} = ${apply}(field_${localIndex}_${fieldIndex}, checkDuplicates)`);
1092
+ body.push(` if (projected_${localIndex}_${fieldIndex} <= 0) return 0`);
1093
+ body.push(` host_value_object_set(output, key_${localIndex}_${fieldIndex}, projected_${localIndex}_${fieldIndex})`);
1094
+ if (!field.required) body.push(' }');
955
1095
  });
1096
+ if (node.additionalProperties) {
1097
+ body.push(' for (let index = 0; index < input.keys.length; index++) {', ' const key = unchecked(input.keys[index])');
1098
+ if (node.fields.length) body.push(` if (${node.fields.map(field => `key == ${quote(field.name)}`).join(' || ')}) continue`);
1099
+ body.push(' const item = __pulse_fastly_copy_json(unchecked(input.values[index]), checkDuplicates)',
1100
+ ' if (item <= 0) return 0', ' host_value_object_set(output, __pulse_fastly_string_value(key), item)', ' }');
1101
+ }
956
1102
  body.push(' return output');
957
1103
  } else if (node.kind === 'string') {
958
1104
  body.push(' if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
@@ -977,6 +1123,32 @@ function generateSchemaRuntime(plan) {
977
1123
  throw new TypeError(`Unsupported Fastly native schema node ${String(node.kind)}.`);
978
1124
  }
979
1125
  body.push('}', '');
1126
+ // Only scalar validators preserve the original handle without allocating or
1127
+ // calling child projectors. Compare the exact emitted body, including enum
1128
+ // values/order and error stages.
1129
+ if (scalarKinds.includes(node.kind)) {
1130
+ const key = body.slice(1).join('\n');
1131
+ const existing = scalarProjectors.get(key);
1132
+ if (existing) return existing;
1133
+ const sharedName = `__pulse_fastly_schema_scalar_${scalarProjectors.size}`;
1134
+ scalarProjectors.set(key, sharedName);
1135
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1136
+ nodeLines.push(...body);
1137
+ return sharedName;
1138
+ }
1139
+ // Share code only after child scalar identities are resolved. Exact bodies
1140
+ // retain field order, presence checks, validation and errors. Each call still
1141
+ // allocates its own output; open/nested/nullable/array bodies remain separate.
1142
+ if (flatObject) {
1143
+ const key = body.slice(1).join('\n');
1144
+ const existing = flatObjectProjectors.get(key);
1145
+ if (existing) return existing;
1146
+ const sharedName = `__pulse_fastly_schema_flat_object_${flatObjectProjectors.size}`;
1147
+ flatObjectProjectors.set(key, sharedName);
1148
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1149
+ nodeLines.push(...body);
1150
+ return sharedName;
1151
+ }
980
1152
  nodeLines.push(...body);
981
1153
  return functionName;
982
1154
  }
@@ -986,17 +1158,22 @@ function generateSchemaRuntime(plan) {
986
1158
  `const __PULSE_SCHEMA_MAX_BYTES: i32 = ${Number(registry.maxBytes || FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES)}`,
987
1159
  `const __PULSE_SCHEMA_REQUIRE_JSON: bool = ${registry.contentTypePolicy === 'require-json' ? 'true' : 'false'}`,
988
1160
  '',
1161
+ schemaJsonAdmissionSource(plan),
1162
+ schemaFetchJsonSource(),
989
1163
  ...nodeLines,
990
- 'function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool): i32 {',
1164
+ `function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool${textEncodeIds.size ? ', textResult: bool = false' : ''}): i32 {`,
991
1165
  ' if (schemaId.length == 0) return valueHandle'
992
1166
  ];
993
1167
  for (const [schemaIndex, schema] of schemas.entries()) {
994
1168
  lines.push(`${schemaIndex === 0 ? ' if' : ' else if'} (schemaId == ${quote(schema.id)}) {`);
995
- lines.push(` const projected = ${roots[schemaIndex]}(valueHandle)`);
1169
+ if (schema.jsonLimits) lines.push(` if (__pulse_fastly_admit_bounded_value(valueHandle, __pulse_schema_json_limits_${schemaIndex}()).failure != 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }`);
1170
+ lines.push(` const projected = ${roots[schemaIndex]}(valueHandle, !encode)`);
996
1171
  lines.push(' if (projected <= 0) return 0');
997
1172
  lines.push(' const input = __pulse_fastly_json(projected, 0)');
998
1173
  lines.push(` const normalized = encode ? __pulse_schema_encode_${schemaIndex}(input) : __pulse_schema_decode_${schemaIndex}(input)`);
999
- lines.push(' return __pulse_fastly_parse_json(normalized)');
1174
+ if (textEncodeIds.has(schema.id)) lines.push(' if (textResult && encode) return __pulse_fastly_string_value(normalized)');
1175
+ if (schema.jsonLimits) lines.push(' const output = __pulse_fastly_parse_json(normalized)', ' if (output > 0) __pulse_fastly_deep_freeze(output)', ' return output');
1176
+ else lines.push(' return __pulse_fastly_parse_json(normalized)');
1000
1177
  lines.push(' }');
1001
1178
  }
1002
1179
  lines.push(' __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1)');
@@ -1019,7 +1196,7 @@ function effectResultSource(plan, bindings) {
1019
1196
  lines.push(`function __pulse_fastly_secret_store(effectIndex: i32): string { return ${quote(bindings.secretStore)} }`, '');
1020
1197
  lines.push('function __pulse_fastly_jwt_secret_binding(effectIndex: i32): string {', ' switch (effectIndex) {');
1021
1198
  for (const [index, effect] of (plan.effects || []).entries()) {
1022
- if (effect.kind === 'jwt.verify' && effect.resource.keyType === 'secret') {
1199
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource.keyType === 'secret') {
1023
1200
  lines.push(` case ${index}: return ${quote(effect.resource.secretBinding)}`);
1024
1201
  }
1025
1202
  }
@@ -1034,12 +1211,31 @@ function effectResultSource(plan, bindings) {
1034
1211
  for (const binding of bindings.grip.publish) lines.push(` case ${binding.index}: return ${quote(binding.authSecretRef || '')}`);
1035
1212
  lines.push(' default: return ""', ' }', '}', '');
1036
1213
 
1214
+ // A config lookup has the same ready-result handoff at every static site.
1215
+ // Keep the site indices in the switch; pass the active index through so the
1216
+ // pending slot and any state error still belong to the original invocation.
1217
+ const configSites = (plan.effects || []).flatMap((effect, index) => effect.kind === 'config.get' ? [index] : []);
1218
+ if (configSites.length > 1) {
1219
+ lines.push('function __pulse_fastly_resolve_config_get(effectIndex: i32): i32 {');
1220
+ lines.push(' const response = __pulse_fastly_wait_ready(effectIndex)');
1221
+ lines.push(' if (response <= 0) return 0');
1222
+ lines.push(' return response');
1223
+ lines.push('}', '');
1224
+ }
1225
+
1037
1226
  lines.push('function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {');
1038
1227
  lines.push(' switch (effectIndex) {');
1039
1228
  for (const [index, effect] of (plan.effects || []).entries()) {
1229
+ if (effect.kind === 'config.get' && configSites.length > 1) {
1230
+ if (index === configSites[0]) {
1231
+ for (const site of configSites) lines.push(` case ${site}:`);
1232
+ lines.push(' return __pulse_fastly_resolve_config_get(effectIndex)');
1233
+ }
1234
+ continue;
1235
+ }
1040
1236
  const result = effect.result || {};
1041
1237
  const decoder = result.decoder;
1042
- const waiter = conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) ? '__pulse_fastly_kv_conditional_wait' : ['s3.head', 's3.getText', 's3.putText'].includes(effect.kind) ? '__pulse_fastly_s3_wait' : effect.kind === 'fetch'
1238
+ const waiter = conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) ? '__pulse_fastly_kv_conditional_wait' : ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind) ? '__pulse_fastly_s3_wait' : effect.kind === 'fetch'
1043
1239
  ? '__pulse_fastly_wait_fetch'
1044
1240
  : effect.kind === 'grip.publish'
1045
1241
  ? '__pulse_fastly_wait_grip_publish'
@@ -1075,13 +1271,17 @@ function effectResultSource(plan, bindings) {
1075
1271
 
1076
1272
  function effectDispatchSource(plan) {
1077
1273
  const handlerByKind = Object.freeze({
1274
+ 'time.now': '__pulse_fastly_time_begin',
1275
+ 'crypto.digestText': '__pulse_fastly_digest_begin',
1078
1276
  fetch: '__pulse_fastly_fetch_begin',
1079
1277
  'config.get': '__pulse_fastly_config_begin',
1080
1278
  'secret.get': '__pulse_fastly_secret_begin',
1081
1279
  'jwt.verify': '__pulse_fastly_jwt_begin',
1280
+ 'jwt.sign': '__pulse_fastly_jwt_sign_begin',
1082
1281
  's3.head': '__pulse_fastly_s3_begin',
1083
1282
  's3.getText': '__pulse_fastly_s3_begin',
1084
1283
  's3.putText': '__pulse_fastly_s3_begin',
1284
+ 's3.getBody': '__pulse_fastly_s3_begin',
1085
1285
  'kv.get': '__pulse_fastly_kv_get_begin',
1086
1286
  'kv.put': '__pulse_fastly_kv_put_begin',
1087
1287
  ...Object.fromEntries(conditionalKv.KV_CONDITIONAL_KINDS.map(kind => [kind, '__pulse_fastly_kv_conditional_begin'])),
@@ -1094,7 +1294,7 @@ function effectDispatchSource(plan) {
1094
1294
  const kinds = [...new Set((plan.effects || []).map((effect) => effect.kind))];
1095
1295
  const lines = [
1096
1296
  'function host_effect_begin(effectIndex: i32, payload: i32): void {',
1097
- ...((plan.schemas && plan.schemas.references || []).some(entry => entry.usage === 'value-encode') ? [' if (__pulse_fastly_last_error != PULSE_ERROR_NONE) return'] : []),
1297
+ ...(needsNativeValueFailureGuard(plan) ? [' if (__pulse_fastly_last_error != PULSE_ERROR_NONE) return'] : []),
1098
1298
  ' if (effectIndex < 0 || effectIndex >= PULSE_FASTLY_EFFECT_COUNT || unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_NONE) { __pulse_fastly_fail(PULSE_ERROR_STATE, 46, effectIndex); return }',
1099
1299
  ' const payloadValue = __pulse_fastly_value(payload)',
1100
1300
  ' if (payloadValue.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 47, effectIndex); return }',
@@ -1115,7 +1315,14 @@ function effectDispatchSource(plan) {
1115
1315
  }
1116
1316
 
1117
1317
  function fastlyRuntimeSource(plan, bindings, options = {}) {
1318
+ const textEncodeIds = schemaTextEncodeIds(plan);
1118
1319
  const effectCount = (plan.effects || []).length;
1320
+ const hasAssets = (plan.effects || []).some(effect => effect.kind === 'assets.lookup');
1321
+ const assetBeginSource = hasAssets ? String.raw`function __pulse_fastly_assets_payload(payload: __PulseFastlyValue): __PulseFastlyValue { const value = __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")); if (value.kind != PULSE_VALUE_OBJECT) __pulse_fastly_fail(PULSE_ERROR_VALUE, 146, -1); return value }
1322
+ function __pulse_fastly_assets_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const value = __pulse_fastly_assets_payload(payload); if (__pulse_fastly_last_error != 0) return; if (__pulse_fastly_value(__pulse_fastly_payload_field(value, 'embeddedId')).kind != PULSE_VALUE_UNDEFINED) { __pulse_fastly_ready_effect(effectIndex, __pulse_fastly_embedded_asset(value)); return; } const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(value, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 147, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }` : '';
1323
+ const assetResultSource = hasAssets ? String.raw`function __pulse_fastly_asset_content_type(key: string): string { const dot = key.lastIndexOf("."); const extension = dot < 0 ? "" : key.substring(dot + 1).toLowerCase(); if (extension == "css") return "text/css; charset=utf-8"; if (extension == "html") return "text/html; charset=utf-8"; if (extension == "js" || extension == "mjs") return "text/javascript; charset=utf-8"; if (extension == "json") return "application/json; charset=utf-8"; if (extension == "svg") return "image/svg+xml"; if (extension == "txt") return "text/plain; charset=utf-8"; if (extension == "webp") return "image/webp"; if (extension == "png") return "image/png"; if (extension == "jpg" || extension == "jpeg") return "image/jpeg"; if (extension == "gif") return "image/gif"; if (extension == "ico") return "image/x-icon"; return "application/octet-stream" }
1324
+ function __pulse_fastly_asset_text(text: string): string { const parser = new __PulseJsonParser(text); const parsed = parser.parse(); if (parser.failed || parsed <= 0) return text; const object = __pulse_fastly_value(parsed); if (object.kind != PULSE_VALUE_OBJECT) return text; const marker = __pulse_fastly_find(object, "__pulseKv"); const value = __pulse_fastly_find(object, "value"); if (marker < 0 || value < 0 || __pulse_fastly_number(unchecked(object.values[marker])) != 1.0) return text; return __pulse_fastly_string(unchecked(object.values[value])) }
1325
+ function __pulse_fastly_wait_assets(effectIndex: i32): i32 { if (__pulse_fastly_pending_mode[effectIndex] == PULSE_FASTLY_PENDING_READY) return __pulse_fastly_wait_ready(effectIndex); if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 148, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const bodyOut = __pulse_fastly_out_i32(); const metadata = new Uint8Array(1); const written = __pulse_fastly_out_i32(); const generation = new StaticArray<i64>(1); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(bodyOut), metadata.dataStart, 0, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 149, effectIndex); return 0 } const outer = __pulse_fastly_value(unchecked(__pulse_fastly_payloads[effectIndex])); const payload = __pulse_fastly_assets_payload(outer); if (__pulse_fastly_last_error != 0) return 0; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; const errorCode = __pulse_fastly_out_value(kvError); output.status = errorCode == 3 ? 404 : 200; if (errorCode != 1 && errorCode != 3) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 150, effectIndex); return 0 } const headersHandle = __pulse_fastly_payload_field(payload, "headers"); const headers = __pulse_fastly_value(headersHandle); let hasContentType = false; if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); if (name.toLowerCase() == "content-type") hasContentType = true; output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } } if (!hasContentType) output.headers.push(new __PulseFastlyHeader("content-type", output.status == 200 ? __pulse_fastly_asset_content_type(key) : "text/plain; charset=utf-8")); const cacheHandle = __pulse_fastly_payload_field(payload, "cacheControl"); if (__pulse_fastly_value(cacheHandle).kind != PULSE_VALUE_UNDEFINED) output.headers.push(new __PulseFastlyHeader("cache-control", __pulse_fastly_string(cacheHandle))); const methodHandle = __pulse_fastly_payload_field(payload, "method"); const method = __pulse_fastly_value(methodHandle).kind == PULSE_VALUE_UNDEFINED ? "GET" : __pulse_fastly_string(methodHandle).toUpperCase(); if (output.status == 200 && method != "HEAD") { const text = __pulse_fastly_read_body(__pulse_fastly_out_value(bodyOut), effectIndex); if (__pulse_fastly_last_error != 0) return 0; output.text = __pulse_fastly_asset_text(text) } return __pulse_fastly_put(output) }` : '';
1119
1326
  const jwtCrypto = options.jwtCrypto || selectedJwtCrypto(plan);
1120
1327
  const es256KeyRecords = options.es256KeyRecords || Object.freeze([]);
1121
1328
  const jwtReality = options.jwtReality && options.jwtReality.enabled === true
@@ -1194,7 +1401,7 @@ function __pulse_fastly_jwt_send_error(): void {}
1194
1401
  const gripAuthorizationSource = gripAuthentication
1195
1402
  ? `const secret = __pulse_fastly_grip_secret(effectIndex, ${quote(gripAuthentication.secretRef)}); if (__pulse_fastly_last_error != 0) return; headers.keys.push("authorization"); headers.values.push(__pulse_fastly_string_value("Bearer " + secret));`
1196
1403
  : '';
1197
- return String.raw`
1404
+ const source = String.raw`
1198
1405
  @external("fastly_abi", "init") declare function fastly_abi_init(version: i64): i32
1199
1406
  @external("fastly_http_req", "body_downstream_get") declare function fastly_http_req_body_downstream_get(requestOut: usize, bodyOut: usize): i32
1200
1407
  @external("fastly_http_req", "method_get") declare function fastly_http_req_method_get(handle: i32, buffer: usize, bufferLength: i32, writtenOut: usize): i32
@@ -1263,19 +1470,40 @@ const PULSE_ERROR_SCHEMA: i32 = 1005
1263
1470
  const PULSE_ERROR_TRANSPORT: i32 = 1006
1264
1471
  const PULSE_ERROR_STATE: i32 = 1007
1265
1472
  const PULSE_ERROR_JWT: i32 = 1008
1473
+ const PULSE_ERROR_REQUEST_BODY: i32 = 1009
1266
1474
 
1267
1475
  class __PulseFastlyValue {
1476
+ ${tracksJsonDuplicates(plan) ? 'duplicateJsonKeys: bool = false' : ''}
1268
1477
  kind: i32 = PULSE_VALUE_UNDEFINED
1269
1478
  boolean: i32 = 0
1270
1479
  number: f64 = 0.0
1271
- text: string = ""
1272
- keys: Array<string> = new Array<string>()
1273
- values: Array<i32> = new Array<i32>()
1480
+ ${nativeStringFields}
1481
+ // Scalar handles do not need collection backing stores.
1482
+ private keysStorage: Array<string> | null = null
1483
+ get keys(): Array<string> {
1484
+ if (this.keysStorage === null) this.keysStorage = new Array<string>()
1485
+ return this.keysStorage!
1486
+ }
1487
+ // Scalar handles do not need collection backing stores.
1488
+ private valuesStorage: Array<i32> | null = null
1489
+ get values(): Array<i32> {
1490
+ if (this.valuesStorage === null) this.valuesStorage = new Array<i32>()
1491
+ return this.valuesStorage!
1492
+ }
1274
1493
  responseHandle: i32 = 0
1275
1494
  bodyHandle: i32 = 0
1276
1495
  bodyLoaded: i32 = 0
1496
+ s3Body: bool = false
1497
+ s3Deadline: i64 = 0
1498
+ s3Length: i32 = 0
1277
1499
  status: i32 = 200
1278
- headers: Array<__PulseFastlyHeader> = new Array<__PulseFastlyHeader>()
1500
+ // Scalar handles do not need collection backing stores.
1501
+ private headersStorage: Array<__PulseFastlyHeader> | null = null
1502
+ get headers(): Array<__PulseFastlyHeader> {
1503
+ if (this.headersStorage === null) this.headersStorage = new Array<__PulseFastlyHeader>()
1504
+ return this.headersStorage!
1505
+ }
1506
+ binaryBody: Uint8Array | null = null
1279
1507
  immutable: bool = false
1280
1508
  }
1281
1509
  class __PulseFastlyHeader {
@@ -1292,7 +1520,7 @@ class __PulseJsonParser {
1292
1520
  parse(): i32 { this.skip(); const value = this.value(0); this.skip(); if (this.index != this.source.length) this.failed = true; return this.failed ? 0 : value }
1293
1521
  value(depth: i32): i32 {
1294
1522
  this.skip(); if (this.conditionalKv && (depth > ${conditionalKv.KV_CONDITIONAL_LIMITS.depth + 1} || ++this.entries > ${conditionalKv.KV_CONDITIONAL_LIMITS.entries + 2})) { this.failed = true; this.tooLarge = true; return 0 }
1295
- if (this.index >= this.source.length || (!this.conditionalKv && depth > ${plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64})) { this.failed = true; return 0 }
1523
+ if (this.index >= this.source.length || (!this.conditionalKv && depth > ${schemaParserDepth(plan, plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64)})) { this.failed = true; return 0 }
1296
1524
  const c = this.source.charCodeAt(this.index)
1297
1525
  if (c == 34) return __pulse_fastly_string_value(this.string())
1298
1526
  if (c == 123) return this.object(depth + 1)
@@ -1306,14 +1534,18 @@ class __PulseJsonParser {
1306
1534
  string(): string {
1307
1535
  // Find a bounded span, then decode each UTF-16 unit once. Concatenating one
1308
1536
  // character at a time exhausts stub/fixed memory on a valid maximum KV value.
1309
- const start = this.index + 1; let end = start;
1537
+ const start = this.index + 1; let end = start; let plain = true;
1310
1538
  while (end < this.source.length) {
1311
1539
  const c = this.source.charCodeAt(end);
1312
1540
  if (c == 34) break;
1313
- if (c == 92) end += 1;
1541
+ if (c == 92) { plain = false; end += 1; }
1542
+ else if (c < 32) plain = false;
1314
1543
  end += 1;
1315
1544
  }
1316
1545
  if (end >= this.source.length) { this.failed = true; return "" }
1546
+ // A validated span needs one owned string, without a temporary UTF-16 array.
1547
+ // Keep escaped and malformed spans on the existing decoder path.
1548
+ if (plain) { this.index = end + 1; return this.source.substring(start, end); }
1317
1549
  const output = new Uint16Array(end - start); let count = 0; this.index = start;
1318
1550
  while (this.index < end) {
1319
1551
  let c = this.source.charCodeAt(this.index++);
@@ -1391,6 +1623,7 @@ class __PulseJsonParser {
1391
1623
  const key = this.string(); this.skip()
1392
1624
  if (this.conditionalKv && __pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) { this.failed = true; break }
1393
1625
  if (this.index >= this.source.length || this.source.charCodeAt(this.index) != 58) { this.failed = true; break }
1626
+ ${tracksJsonDuplicates(plan) ? 'if (__pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) __pulse_fastly_value(output).duplicateJsonKeys = true' : ''}
1394
1627
  this.index += 1; host_value_object_set(output, __pulse_fastly_string_value(key), this.value(depth)); this.skip()
1395
1628
  if (this.index >= this.source.length) { this.failed = true; break }
1396
1629
  const c = this.source.charCodeAt(this.index); this.index += 1
@@ -1405,6 +1638,12 @@ const PULSE_FASTLY_PENDING_NONE: i32 = 0
1405
1638
  const PULSE_FASTLY_PENDING_ASYNC: i32 = 1
1406
1639
  const PULSE_FASTLY_PENDING_READY: i32 = 2
1407
1640
  @lazy const __pulse_fastly_values = new Array<__PulseFastlyValue>()
1641
+ // Primitive handles have value semantics. Bounded caches keep admitted scalar
1642
+ // validation loops from retaining a fresh boxed value for every iteration.
1643
+ @lazy const __pulse_fastly_scalar_handles = new StaticArray<i32>(4)
1644
+ @lazy const __pulse_fastly_integer_handles = new StaticArray<i32>(65536)
1645
+ @lazy const __pulse_fastly_character_handles = new StaticArray<i32>(65537)
1646
+ @lazy const __pulse_fastly_literal_handles = new Map<string, i32>()
1408
1647
  @lazy const __pulse_fastly_pending = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1409
1648
  @lazy const __pulse_fastly_pending_mode = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1410
1649
  @lazy const __pulse_fastly_payloads = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
@@ -1440,8 +1679,18 @@ function __pulse_fastly_utf8(value: string): ArrayBuffer { return String.UTF8.en
1440
1679
  function __pulse_fastly_decode(buffer: Uint8Array, length: i32): string { return String.UTF8.decodeUnsafe(buffer.dataStart, length, false) }
1441
1680
  function __pulse_fastly_put(value: __PulseFastlyValue): i32 { __pulse_fastly_values.push(value); return __pulse_fastly_values.length }
1442
1681
  function __pulse_fastly_value(handle: i32): __PulseFastlyValue { if (handle <= 0 || handle > __pulse_fastly_values.length) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 1, -1); return new __PulseFastlyValue() } return unchecked(__pulse_fastly_values[handle - 1]) }
1682
+ ${nativeStringConcat}
1683
+ ${nativeStringTrim}
1684
+ ${nativeStringIndex}
1443
1685
  function __pulse_fastly_deep_freeze(handle: i32): void { const value = __pulse_fastly_value(handle); if (value.immutable) return; value.immutable = true; if (value.kind == PULSE_VALUE_ARRAY || value.kind == PULSE_VALUE_OBJECT) for (let index = 0; index < value.values.length; index += 1) __pulse_fastly_deep_freeze(unchecked(value.values[index])) }
1444
- function __pulse_fastly_string_value(value: string): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_STRING; item.text = value; return __pulse_fastly_put(item) }
1686
+ function __pulse_fastly_string_value(value: string): i32 {
1687
+ const index = value.length == 0 ? 65536 : value.length == 1 ? value.charCodeAt(0) : -1;
1688
+ if (index >= 0) { const cached = unchecked(__pulse_fastly_character_handles[index]); if (cached != 0) return cached }
1689
+ const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_STRING; item.text = value;
1690
+ const handle = __pulse_fastly_put(item);
1691
+ if (index >= 0) unchecked(__pulse_fastly_character_handles[index] = handle);
1692
+ return handle
1693
+ }
1445
1694
  function __pulse_fastly_find(object: __PulseFastlyValue, key: string): i32 { for (let i = 0; i < object.keys.length; i += 1) if (unchecked(object.keys[i]) == key) return i; return -1 }
1446
1695
  function __pulse_fastly_number_string(value: f64): string { if (value >= -9007199254740991.0 && value <= 9007199254740991.0 && Math.floor(value) == value) return i64(value).toString(); return value.toString() }
1447
1696
  function __pulse_fastly_string(handle: i32): string { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_STRING) return value.text; if (value.kind == PULSE_VALUE_NUMBER) return __pulse_fastly_number_string(value.number); if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? "true" : "false"; if (value.kind == PULSE_VALUE_NULL) return "null"; if (value.kind == PULSE_VALUE_UNDEFINED) return "undefined"; return __pulse_fastly_json(handle, 0) }
@@ -1480,21 +1729,26 @@ function __pulse_fastly_quote(value: string): string {
1480
1729
  out[cursor] = 34;
1481
1730
  return String.UTF16.decodeUnsafe(out.dataStart, out.byteLength);
1482
1731
  }
1483
- function __pulse_fastly_json(handle: i32, depth: i32): string {
1484
- if (depth > ${plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64}) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1); return "null" }
1732
+ function __pulse_fastly_json_parts(handle: i32, depth: i32, parts: Array<string>): void {
1733
+ if (depth > ${schemaParserDepth(plan, plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64)}) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1); parts.push("null"); return }
1485
1734
  const value = __pulse_fastly_value(handle)
1486
- if (value.kind == PULSE_VALUE_UNDEFINED || value.kind == PULSE_VALUE_NULL) return "null"
1487
- if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? "true" : "false"
1488
- if (value.kind == PULSE_VALUE_NUMBER) return __pulse_fastly_number_string(value.number)
1489
- if (value.kind == PULSE_VALUE_STRING) return __pulse_fastly_quote(value.text)
1490
- if (value.kind == PULSE_VALUE_ARRAY) { let out = "["; for (let i = 0; i < value.values.length; i += 1) { if (i > 0) out += ","; out += __pulse_fastly_json(unchecked(value.values[i]), depth + 1) } return out + "]" }
1491
- if (value.kind == PULSE_VALUE_OBJECT) { let out = "{"; for (let i = 0; i < value.keys.length; i += 1) { if (i > 0) out += ","; out += __pulse_fastly_quote(unchecked(value.keys[i])) + ":" + __pulse_fastly_json(unchecked(value.values[i]), depth + 1) } return out + "}" }
1492
- return "null"
1735
+ if (value.kind == PULSE_VALUE_BOOLEAN) { parts.push(value.boolean != 0 ? "true" : "false"); return }
1736
+ if (value.kind == PULSE_VALUE_NUMBER) { parts.push(__pulse_fastly_number_string(value.number)); return }
1737
+ if (value.kind == PULSE_VALUE_STRING) { parts.push(__pulse_fastly_quote(value.text)); return }
1738
+ if (value.kind == PULSE_VALUE_ARRAY) { parts.push("["); for (let i = 0; i < value.values.length; i += 1) { if (i > 0) parts.push(","); __pulse_fastly_json_parts(unchecked(value.values[i]), depth + 1, parts) } parts.push("]"); return }
1739
+ if (value.kind == PULSE_VALUE_OBJECT) { parts.push("{"); for (let i = 0; i < value.keys.length; i += 1) { if (i > 0) parts.push(","); parts.push(__pulse_fastly_quote(unchecked(value.keys[i]))); parts.push(":"); __pulse_fastly_json_parts(unchecked(value.values[i]), depth + 1, parts) } parts.push("}"); return }
1740
+ parts.push("null")
1741
+ }
1742
+ function __pulse_fastly_json(handle: i32, depth: i32): string {
1743
+ // Join once, avoiding a complete copy of a multi-MiB leaf at every ancestor
1744
+ // and at every following member in the response or effect result.
1745
+ const parts = new Array<string>(); __pulse_fastly_json_parts(handle, depth, parts);
1746
+ return parts.join("")
1493
1747
  }
1494
1748
  function __pulse_fastly_parse_json(text: string): i32 { const parser = new __PulseJsonParser(text); const value = parser.parse(); if (parser.failed || value <= 0) { __pulse_fastly_fail(PULSE_ERROR_JSON, 3, -1); return 0 } return value }
1495
1749
  function __pulse_fastly_path(uri: string): string { let start = 0; const scheme = uri.indexOf("://"); if (scheme >= 0) { const slash = uri.indexOf("/", scheme + 3); start = slash >= 0 ? slash : uri.length } let end = uri.length; const query = uri.indexOf("?", start); if (query >= 0 && query < end) end = query; const fragment = uri.indexOf("#", start); if (fragment >= 0 && fragment < end) end = fragment; return start >= end ? "/" : uri.substring(start, end) }
1496
1750
  function __pulse_fastly_read_req_string(kind: i32): string { const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES); const written = __pulse_fastly_out_i32(); const status = kind == 0 ? fastly_http_req_method_get(__pulse_fastly_request_handle, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)) : fastly_http_req_uri_get(__pulse_fastly_request_handle, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 10 + kind, -1); return "" } return __pulse_fastly_decode(buffer, __pulse_fastly_out_value(written)) }
1497
- function __pulse_fastly_read_body(handle: i32, effectIndex: i32): string {
1751
+ function __pulse_fastly_read_body(handle: i32, effectIndex: i32, requestText: bool = false): string {
1498
1752
  const chunks = new Array<Uint8Array>(); let total = 0;
1499
1753
  while (true) {
1500
1754
  const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES), read = __pulse_fastly_out_i32();
@@ -1504,36 +1758,55 @@ function __pulse_fastly_read_body(handle: i32, effectIndex: i32): string {
1504
1758
  if (count < 0 || count > buffer.length) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1505
1759
  if (!count) break;
1506
1760
  total += count;
1507
- if (total > __PULSE_SCHEMA_MAX_BYTES && __PULSE_SCHEMA_MAX_BYTES > 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, effectIndex); return "" }
1761
+ if (total > __PULSE_SCHEMA_MAX_BYTES && __PULSE_SCHEMA_MAX_BYTES > 0) { __pulse_fastly_fail(requestText ? PULSE_ERROR_REQUEST_BODY : PULSE_ERROR_SCHEMA, 21, effectIndex); return "" }
1508
1762
  chunks.push(buffer.subarray(0, count));
1509
1763
  }
1510
1764
  // UTF-8 scalars may straddle host read chunks. Decode the bounded body once.
1511
1765
  const bytes = new Uint8Array(total); let offset = 0;
1512
1766
  for (let i = 0; i < chunks.length; i++) { bytes.set(chunks[i], offset); offset += chunks[i].length; }
1767
+ if (requestText && !__pulse_request_utf8_valid(bytes)) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, 24, effectIndex); return "" }
1513
1768
  return __pulse_fastly_decode(bytes, total);
1514
1769
  }
1515
1770
  function __pulse_fastly_request_header_text(name: string): string { const nameBytes = __pulse_fastly_utf8(name); const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES); const written = __pulse_fastly_out_i32(); const status = fastly_http_req_header_value_get(__pulse_fastly_request_handle, changetype<usize>(nameBytes), nameBytes.byteLength, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)); if (status == FASTLY_STATUS_NONE) return ""; if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 22, -1); return "" } return __pulse_fastly_decode(buffer, __pulse_fastly_out_value(written)) }
1516
1771
  function __pulse_fastly_fetch_header_text(responseHandle: i32, name: string): string { const nameBytes = __pulse_fastly_utf8(name); const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES); const written = __pulse_fastly_out_i32(); const status = fastly_http_resp_header_value_get(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)); if (status == FASTLY_STATUS_NONE) return ""; if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 23, -1); return "" } return __pulse_fastly_decode(buffer, __pulse_fastly_out_value(written)) }
1517
1772
  function __pulse_fastly_fetch_body(value: __PulseFastlyValue): string { if (value.bodyLoaded == 0) { value.text = __pulse_fastly_read_body(value.bodyHandle, -1); value.bodyLoaded = 1 } return value.text }
1518
1773
 
1519
- function host_value_undefined(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_UNDEFINED; return __pulse_fastly_put(item) }
1520
- function host_value_null(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NULL; return __pulse_fastly_put(item) }
1521
- function host_value_boolean(value: i32): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_BOOLEAN; item.boolean = value != 0 ? 1 : 0; return __pulse_fastly_put(item) }
1522
- function host_value_number(value: f64): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value; return __pulse_fastly_put(item) }
1523
- function host_value_string(pointer: i32, length: i32): i32 { return __pulse_fastly_string_value(changetype<string>(pointer)) }
1774
+ function __pulse_fastly_scalar(kind: i32, boolean: i32, slot: i32): i32 {
1775
+ const cached = unchecked(__pulse_fastly_scalar_handles[slot]); if (cached != 0) return cached;
1776
+ const item = new __PulseFastlyValue(); item.kind = kind; item.boolean = boolean;
1777
+ const handle = __pulse_fastly_put(item); unchecked(__pulse_fastly_scalar_handles[slot] = handle); return handle
1778
+ }
1779
+ function host_value_undefined(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_UNDEFINED, 0, 0) }
1780
+ function host_value_null(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_NULL, 0, 1) }
1781
+ function host_value_boolean(value: i32): i32 { return __pulse_fastly_scalar(PULSE_VALUE_BOOLEAN, value != 0 ? 1 : 0, value != 0 ? 3 : 2) }
1782
+ function host_value_number(value: f64): i32 {
1783
+ // Preserve negative zero, NaN, infinities and nonintegral values exactly.
1784
+ const cacheable = value >= 0 && value < 65536 && Math.floor(value) == value && (value != 0 || 1.0 / value > 0);
1785
+ const index = cacheable ? i32(value) : -1;
1786
+ if (index >= 0) { const cached = unchecked(__pulse_fastly_integer_handles[index]); if (cached != 0) return cached }
1787
+ const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value;
1788
+ const handle = __pulse_fastly_put(item); if (index >= 0) unchecked(__pulse_fastly_integer_handles[index] = handle); return handle
1789
+ }
1790
+ function host_value_string(pointer: i32, length: i32): i32 {
1791
+ const text = changetype<string>(pointer);
1792
+ if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.has(text)) return __pulse_fastly_literal_handles.get(text);
1793
+ const handle = __pulse_fastly_string_value(text);
1794
+ if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.size < 1024) __pulse_fastly_literal_handles.set(text, handle);
1795
+ return handle
1796
+ }
1524
1797
  function host_value_array(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_ARRAY; return __pulse_fastly_put(item) }
1525
1798
  function host_value_array_push(target: i32, value: i32): void { const item = __pulse_fastly_value(target); if (item.kind != PULSE_VALUE_ARRAY || item.immutable) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 30, -1); return } item.values.push(value) }
1526
1799
  function host_value_array_spread(target: i32, source: i32): void { const from = __pulse_fastly_value(source); if (from.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 31, -1); return } for (let i = 0; i < from.values.length; i += 1) host_value_array_push(target, unchecked(from.values[i])) }
1527
1800
  function host_value_object(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_OBJECT; return __pulse_fastly_put(item) }
1528
1801
  function host_value_object_set(target: i32, keyHandle: i32, value: i32): void { const item = __pulse_fastly_value(target); if (item.kind != PULSE_VALUE_OBJECT || item.immutable) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 32, -1); return } const key = __pulse_fastly_string(keyHandle); const index = __pulse_fastly_find(item, key); if (index >= 0) unchecked(item.values[index] = value); else { item.keys.push(key); item.values.push(value) } }
1529
1802
  function host_value_object_spread(target: i32, source: i32): void { const from = __pulse_fastly_value(source); if (from.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 33, -1); return } for (let i = 0; i < from.keys.length; i += 1) host_value_object_set(target, __pulse_fastly_string_value(unchecked(from.keys[i])), unchecked(from.values[i])) }
1530
- function host_value_property(target: i32, keyHandle: i32): i32 { const item = __pulse_fastly_value(target); const key = __pulse_fastly_string(keyHandle); if (item.kind == PULSE_VALUE_OBJECT) { const index = __pulse_fastly_find(item, key); return index >= 0 ? unchecked(item.values[index]) : host_value_undefined() } if (item.kind == PULSE_VALUE_FETCH && key == "status") { const out = __pulse_fastly_out_i32(); const status = fastly_http_resp_status_get(item.responseHandle, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 34, -1); return 0 } return host_value_number(__pulse_fastly_out_value(out)) } if ((item.kind == PULSE_VALUE_STRING || item.kind == PULSE_VALUE_ARRAY) && key == "length") return host_value_number(item.kind == PULSE_VALUE_STRING ? item.text.length : item.values.length); return host_value_undefined() }
1803
+ function host_value_property(target: i32, keyHandle: i32): i32 { const item = __pulse_fastly_value(target); const key = __pulse_fastly_string(keyHandle); if (item.kind == PULSE_VALUE_OBJECT) { const index = __pulse_fastly_find(item, key); return index >= 0 ? unchecked(item.values[index]) : host_value_undefined() } if (item.kind == PULSE_VALUE_FETCH && key == "status") { const out = __pulse_fastly_out_i32(); const status = fastly_http_resp_status_get(item.responseHandle, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 34, -1); return 0 } return host_value_number(__pulse_fastly_out_value(out)) } if ((item.kind == PULSE_VALUE_STRING || item.kind == PULSE_VALUE_ARRAY) && key == "length") return host_value_number(item.kind == PULSE_VALUE_STRING ? item.textLength : item.values.length); return host_value_undefined() }
1531
1804
  function host_value_property_set(target: i32, key: i32, value: i32): i32 { host_value_object_set(target, key, value); return value }
1532
- function host_value_element(target: i32, keyHandle: i32): i32 { const item = __pulse_fastly_value(target); if (item.kind == PULSE_VALUE_ARRAY) { const index = i32(__pulse_fastly_number(keyHandle)); return index >= 0 && index < item.values.length ? unchecked(item.values[index]) : host_value_undefined() } return host_value_property(target, keyHandle) }
1805
+ function host_value_element(target: i32, keyHandle: i32): i32 { const item = __pulse_fastly_value(target); if (item.kind == PULSE_VALUE_STRING) return __pulse_fastly_string_index(item, keyHandle); if (item.kind == PULSE_VALUE_ARRAY) { const index = i32(__pulse_fastly_number(keyHandle)); return index >= 0 && index < item.values.length ? unchecked(item.values[index]) : host_value_undefined() } return host_value_property(target, keyHandle) }
1533
1806
  function host_value_element_set(target: i32, keyHandle: i32, value: i32): i32 { const item = __pulse_fastly_value(target); if (item.immutable) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 35, -1); return 0 } if (item.kind == PULSE_VALUE_ARRAY) { const index = i32(__pulse_fastly_number(keyHandle)); if (index < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 35, -1); return 0 } while (item.values.length <= index) item.values.push(host_value_undefined()); unchecked(item.values[index] = value); return value } return host_value_property_set(target, keyHandle, value) }
1534
- function host_value_truthy(handle: i32): i32 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_UNDEFINED || value.kind == PULSE_VALUE_NULL) return 0; if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean; if (value.kind == PULSE_VALUE_NUMBER) return value.number != 0.0 && !isNaN(value.number) ? 1 : 0; if (value.kind == PULSE_VALUE_STRING) return value.text.length > 0 ? 1 : 0; return 1 }
1807
+ function host_value_truthy(handle: i32): i32 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_UNDEFINED || value.kind == PULSE_VALUE_NULL) return 0; if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean; if (value.kind == PULSE_VALUE_NUMBER) return value.number != 0.0 && !isNaN(value.number) ? 1 : 0; if (value.kind == PULSE_VALUE_STRING) return value.textLength > 0 ? 1 : 0; return 1 }
1535
1808
  function host_value_nullish(handle: i32): i32 { const kind = __pulse_fastly_value(handle).kind; return kind == PULSE_VALUE_UNDEFINED || kind == PULSE_VALUE_NULL ? 1 : 0 }
1536
- function host_value_binary(operator: i32, leftHandle: i32, rightHandle: i32): i32 { const left = __pulse_fastly_value(leftHandle); const right = __pulse_fastly_value(rightHandle); if (operator == 0 || operator == 2) { let equal = false; if (left.kind == right.kind) { if (left.kind == PULSE_VALUE_UNDEFINED || left.kind == PULSE_VALUE_NULL) equal = true; else if (left.kind == PULSE_VALUE_BOOLEAN) equal = left.boolean == right.boolean; else if (left.kind == PULSE_VALUE_NUMBER) equal = left.number == right.number; else if (left.kind == PULSE_VALUE_STRING) equal = left.text == right.text; else equal = leftHandle == rightHandle } return host_value_boolean(equal ? 1 : 0) } if (operator == 1 || operator == 3) { const result = host_value_binary(operator == 1 ? 0 : 2, leftHandle, rightHandle); return host_value_boolean(host_value_truthy(result) == 0 ? 1 : 0) } if (operator == 8) { if (left.kind == PULSE_VALUE_STRING || right.kind == PULSE_VALUE_STRING) return __pulse_fastly_string_value(__pulse_fastly_string(leftHandle) + __pulse_fastly_string(rightHandle)); return host_value_number(__pulse_fastly_number(leftHandle) + __pulse_fastly_number(rightHandle)) } if (operator == 4) return host_value_boolean(__pulse_fastly_number(leftHandle) < __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 5) return host_value_boolean(__pulse_fastly_number(leftHandle) <= __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 6) return host_value_boolean(__pulse_fastly_number(leftHandle) > __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 7) return host_value_boolean(__pulse_fastly_number(leftHandle) >= __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 9) return host_value_number(__pulse_fastly_number(leftHandle) - __pulse_fastly_number(rightHandle)); if (operator == 10) return host_value_number(__pulse_fastly_number(leftHandle) * __pulse_fastly_number(rightHandle)); if (operator == 11) return host_value_number(__pulse_fastly_number(leftHandle) / __pulse_fastly_number(rightHandle)); if (operator == 14) return host_value_truthy(leftHandle) != 0 ? rightHandle : leftHandle; if (operator == 15) return host_value_truthy(leftHandle) != 0 ? leftHandle : rightHandle; if (operator == 16) return host_value_nullish(leftHandle) == 0 ? leftHandle : rightHandle; __pulse_fastly_fail(PULSE_ERROR_UNSUPPORTED, 36, -1); return 0 }
1809
+ function host_value_binary(operator: i32, leftHandle: i32, rightHandle: i32): i32 { const left = __pulse_fastly_value(leftHandle); const right = __pulse_fastly_value(rightHandle); if (operator == 0 || operator == 2) { let equal = false; if (left.kind == right.kind) { if (left.kind == PULSE_VALUE_UNDEFINED || left.kind == PULSE_VALUE_NULL) equal = true; else if (left.kind == PULSE_VALUE_BOOLEAN) equal = left.boolean == right.boolean; else if (left.kind == PULSE_VALUE_NUMBER) equal = left.number == right.number; else if (left.kind == PULSE_VALUE_STRING) equal = left.text == right.text; else equal = leftHandle == rightHandle } return host_value_boolean(equal ? 1 : 0) } if (operator == 1 || operator == 3) { const result = host_value_binary(operator == 1 ? 0 : 2, leftHandle, rightHandle); return host_value_boolean(host_value_truthy(result) == 0 ? 1 : 0) } if (operator == 8) { if (left.kind == PULSE_VALUE_STRING || right.kind == PULSE_VALUE_STRING) return __pulse_fastly_concat(leftHandle, rightHandle); return host_value_number(__pulse_fastly_number(leftHandle) + __pulse_fastly_number(rightHandle)) } if (left.kind == PULSE_VALUE_STRING && right.kind == PULSE_VALUE_STRING) { if (operator == 4) return host_value_boolean(left.text < right.text ? 1 : 0); if (operator == 5) return host_value_boolean(left.text <= right.text ? 1 : 0); if (operator == 6) return host_value_boolean(left.text > right.text ? 1 : 0); if (operator == 7) return host_value_boolean(left.text >= right.text ? 1 : 0); } if (operator == 4) return host_value_boolean(__pulse_fastly_number(leftHandle) < __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 5) return host_value_boolean(__pulse_fastly_number(leftHandle) <= __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 6) return host_value_boolean(__pulse_fastly_number(leftHandle) > __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 7) return host_value_boolean(__pulse_fastly_number(leftHandle) >= __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 9) return host_value_number(__pulse_fastly_number(leftHandle) - __pulse_fastly_number(rightHandle)); if (operator == 10) return host_value_number(__pulse_fastly_number(leftHandle) * __pulse_fastly_number(rightHandle)); if (operator == 11) return host_value_number(__pulse_fastly_number(leftHandle) / __pulse_fastly_number(rightHandle)); if (operator == 12) return host_value_number(__pulse_fastly_number(leftHandle) % __pulse_fastly_number(rightHandle)); if (operator == 14) return host_value_truthy(leftHandle) != 0 ? rightHandle : leftHandle; if (operator == 15) return host_value_truthy(leftHandle) != 0 ? leftHandle : rightHandle; if (operator == 16) return host_value_nullish(leftHandle) == 0 ? leftHandle : rightHandle; __pulse_fastly_fail(PULSE_ERROR_UNSUPPORTED, 36, -1); return 0 }
1537
1810
  function host_value_unary(operator: i32, handle: i32): i32 { if (operator == 0) return host_value_boolean(host_value_truthy(handle) == 0 ? 1 : 0); if (operator == 1) return host_value_number(__pulse_fastly_number(handle)); if (operator == 2) return host_value_number(-__pulse_fastly_number(handle)); if (operator == 5) return host_value_undefined(); __pulse_fastly_fail(PULSE_ERROR_UNSUPPORTED, 37, -1); return 0 }
1538
1811
 
1539
1812
  function host_request_method(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_method) }
@@ -1544,21 +1817,35 @@ function __pulse_router_param_text(path: string, pattern: string, name: string):
1544
1817
  function host_router_match(path: i32, pattern: i32): i32 { return host_value_boolean(__pulse_router_match_text(__pulse_fastly_string(path), __pulse_fastly_string(pattern)) ? 1 : 0) }
1545
1818
  function host_router_param(path: i32, pattern: i32, name: i32): i32 { const value = __pulse_router_param_text(__pulse_fastly_string(path), __pulse_fastly_string(pattern), __pulse_fastly_string(name)); return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
1546
1819
  function host_request_path(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_path) }
1547
- function host_request_headers(): i32 { return host_value_object() }
1820
+ function host_request_headers(): i32 { return __pulse_request_headers_read() }
1548
1821
  function host_request_header(name: i32): i32 { const value = __pulse_fastly_request_header_text(__pulse_fastly_string(name)); return value.length == 0 ? host_value_undefined() : __pulse_fastly_string_value(value) }
1549
- function host_request_text(): i32 { if (__pulse_fastly_request_body_loaded == 0) { __pulse_fastly_request_body = __pulse_fastly_read_body(__pulse_fastly_request_body_handle, -1); __pulse_fastly_request_body_loaded = 1 } return __pulse_fastly_string_value(__pulse_fastly_request_body) }
1550
- 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 }
1822
+ function host_request_text(): i32 { if (__pulse_request_body_failure != 0) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, __pulse_request_body_failure, -1); return 0 } if (__pulse_fastly_request_body_loaded == 0) { __pulse_fastly_request_body = __pulse_fastly_read_body(__pulse_fastly_request_body_handle, -1, true); __pulse_fastly_request_body_loaded = 1; if (__pulse_fastly_last_error == PULSE_ERROR_REQUEST_BODY) __pulse_request_body_failure = __pulse_fastly_error_stage } if (__pulse_fastly_last_error != 0) return 0; return __pulse_fastly_string_value(__pulse_fastly_request_body) }
1823
+ function host_request_json(schema: i32): i32 { const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(__pulse_fastly_string(host_request_text()), schema)' : '__pulse_fastly_parse_json(__pulse_fastly_string(host_request_text()))'}; if (parsed <= 0) return 0; const schemaValue = __pulse_fastly_value(schema); return schemaValue.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schemaValue.text, parsed, false) : parsed }
1551
1824
  function __pulse_fastly_headers_from_options(options: __PulseFastlyValue, output: __PulseFastlyValue): void { const index = __pulse_fastly_find(options, "headers"); if (index < 0) return; const headers = __pulse_fastly_value(unchecked(options.values[index])); if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); const value = __pulse_fastly_value(unchecked(headers.values[i])); if (value.kind == PULSE_VALUE_ARRAY) { for (let j = 0; j < value.values.length; j += 1) output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(value.values[j])))) } else output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } return } if (headers.kind == PULSE_VALUE_ARRAY) { for (let i = 0; i < headers.values.length; i += 1) { const pair = __pulse_fastly_value(unchecked(headers.values[i])); if (pair.kind != PULSE_VALUE_ARRAY || pair.values.length < 2) continue; output.headers.push(new __PulseFastlyHeader(__pulse_fastly_string(unchecked(pair.values[0])), __pulse_fastly_string(unchecked(pair.values[1])))) } } }
1552
1825
  function __pulse_fastly_response(value: i32, optionsHandle: i32, json: bool): i32 { const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const options = __pulse_fastly_value(optionsHandle); let bodyValue = value; if (options.kind == PULSE_VALUE_OBJECT) { const statusIndex = __pulse_fastly_find(options, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(options.values[statusIndex]))); const schemaIndex = __pulse_fastly_find(options, "schema"); if (json && schemaIndex >= 0) { bodyValue = __pulse_fastly_schema_apply(__pulse_fastly_string(unchecked(options.values[schemaIndex])), value, true); if (bodyValue <= 0) return 0 } __pulse_fastly_headers_from_options(options, output) } output.text = json ? __pulse_fastly_json(bodyValue, 0) : __pulse_fastly_string(value); let hasContentType = false; for (let i = 0; i < output.headers.length; i += 1) if (unchecked(output.headers[i]).name.toLowerCase() == "content-type") hasContentType = true; if (!hasContentType) output.headers.push(new __PulseFastlyHeader("content-type", json ? "application/json; charset=utf-8" : "text/plain; charset=utf-8")); return __pulse_fastly_put(output) }
1553
1826
  function host_response_json(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, true) }
1827
+ function host_schema_decode(text: i32, schema: i32): i32 {
1828
+ const id = __pulse_fastly_value(schema)
1829
+ if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
1830
+ const input = __pulse_fastly_value(text)
1831
+ if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }
1832
+ if (String.UTF8.byteLength(input.text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1833
+ const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(input.text, schema)' : '__pulse_fastly_parse_json(input.text)'}
1834
+ if (parsed <= 0) return 0
1835
+ const decoded = __pulse_fastly_schema_apply(id.text, parsed, false)
1836
+ if (decoded <= 0) return 0
1837
+ __pulse_fastly_deep_freeze(decoded)
1838
+ return decoded
1839
+ }
1554
1840
  function host_schema_encode(value: i32, schema: i32): i32 {
1555
1841
  const id = __pulse_fastly_value(schema)
1556
1842
  if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
1557
- const projected = __pulse_fastly_schema_apply(id.text, value, true)
1843
+ ${textEncodeIds.length ? `const textResult = ${textEncodeIds.map(id => `id.text == ${quote(id)}`).join(' || ')}
1844
+ const projected = __pulse_fastly_schema_apply(id.text, value, true, textResult)` : 'const projected = __pulse_fastly_schema_apply(id.text, value, true)'}
1558
1845
  if (projected <= 0) return 0
1559
- const text = __pulse_fastly_json(projected, 0)
1846
+ const text = ${textEncodeIds.length ? 'textResult ? __pulse_fastly_string(projected) : ' : ''}__pulse_fastly_json(projected, 0)
1560
1847
  if (String.UTF8.byteLength(text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1561
- return __pulse_fastly_string_value(text)
1848
+ return ${textEncodeIds.length ? 'textResult ? projected : ' : ''}__pulse_fastly_string_value(text)
1562
1849
  }
1563
1850
  function host_response_text(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, false) }
1564
1851
  function host_response_custom(specHandle: i32): i32 { const spec = __pulse_fastly_value(specHandle); if (spec.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 40, -1); return 0 } const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const statusIndex = __pulse_fastly_find(spec, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(spec.values[statusIndex]))); const bodyIndex = __pulse_fastly_find(spec, "body"); if (bodyIndex >= 0) output.text = __pulse_fastly_string(unchecked(spec.values[bodyIndex])); __pulse_fastly_headers_from_options(spec, output); return __pulse_fastly_put(output) }
@@ -1569,7 +1856,7 @@ function host_grip_subscribe(responseHandle: i32, optionsHandle: i32): i32 { con
1569
1856
  function host_grip_handoff(optionsHandle: i32): i32 { const options = __pulse_fastly_value(optionsHandle); if (options.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 139, -1); return 0 } const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const statusIndex = __pulse_fastly_find(options, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(options.values[statusIndex]))); const bodyIndex = __pulse_fastly_find(options, "body"); if (bodyIndex >= 0) output.text = __pulse_fastly_string(unchecked(options.values[bodyIndex])); __pulse_fastly_headers_from_options(options, output); let hasContentType = false; for (let i = 0; i < output.headers.length; i += 1) if (unchecked(output.headers[i]).name.toLowerCase() == "content-type") hasContentType = true; if (!hasContentType) output.headers.push(new __PulseFastlyHeader("Content-Type", "application/websocket-events")); return __pulse_fastly_grip_frame(options, output) ? __pulse_fastly_put(output) : 0 }
1570
1857
  function host_kv_namespace(name: i32): i32 { return name }
1571
1858
  function host_fetch_text(responseHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 42, -1); return 0 } return __pulse_fastly_string_value(__pulse_fastly_fetch_body(response)) }
1572
- function host_fetch_json(responseHandle: i32, schemaHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 43, -1); return 0 } if (__PULSE_SCHEMA_REQUIRE_JSON) { const contentType = __pulse_fastly_fetch_header_text(response.responseHandle, "content-type"); if (contentType.length == 0 || contentType.toLowerCase().indexOf("json") < 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 44, -1); return 0 } } const parsed = __pulse_fastly_parse_json(__pulse_fastly_fetch_body(response)); if (parsed <= 0) return 0; const schema = __pulse_fastly_value(schemaHandle); return schema.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schema.text, parsed, false) : parsed }
1859
+ function host_fetch_json(responseHandle: i32, schemaHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 43, -1); return 0 } if (__PULSE_SCHEMA_REQUIRE_JSON) { const contentType = __pulse_fastly_fetch_header_text(response.responseHandle, "content-type"); if (contentType.length == 0 || contentType.toLowerCase().indexOf("json") < 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 44, -1); return 0 } } const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(__pulse_fastly_fetch_body(response), schemaHandle)' : '__pulse_fastly_parse_json(__pulse_fastly_fetch_body(response))'}; if (parsed <= 0) return 0; const schema = __pulse_fastly_value(schemaHandle); return schema.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schema.text, parsed, false) : parsed }
1573
1860
  function host_fetch_header(responseHandle: i32, nameHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 45, -1); return 0 } const value = __pulse_fastly_fetch_header_text(response.responseHandle, __pulse_fastly_string(nameHandle)); return value.length == 0 ? host_value_undefined() : __pulse_fastly_string_value(value) }
1574
1861
  function __pulse_fastly_payload_field(payload: __PulseFastlyValue, name: string): i32 { const index = __pulse_fastly_find(payload, name); return index < 0 ? host_value_undefined() : unchecked(payload.values[index]) }
1575
1862
  function __pulse_fastly_ready_effect(effectIndex: i32, handle: i32): void { unchecked(__pulse_fastly_ready[effectIndex] = handle); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_READY) }
@@ -1590,12 +1877,12 @@ function __pulse_fastly_jwt_record_verified(): void { __pulse_fastly_jwt_verifie
1590
1877
  function __pulse_fastly_jwt_record_signing_input(bytes: i32, digest: string): void { __pulse_fastly_jwt_signing_input_bytes = bytes; __pulse_fastly_jwt_signing_input_sha256 = digest }
1591
1878
  function __pulse_fastly_jwt_capture_clock(effectIndex: i32): f64 { __pulse_fastly_jwt_clock_calls += 1; __pulse_fastly_jwt_event_order += "C"; const out = new StaticArray<i64>(1); const status = wasi_snapshot_preview1_clock_time_get(0, 1, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 226, effectIndex); return NaN } ${jwtClockOverride ? `return ${jwtClockOverride}` : 'return f64(load<i64>(changetype<usize>(out))) / 1000000000.0'} }
1592
1879
  function __pulse_fastly_jwt_validate_schema(effectIndex: i32, schemaId: string, claimsHandle: i32): i32 { __pulse_fastly_jwt_schema_calls += 1; __pulse_fastly_jwt_event_order += "H"; if (__pulse_fastly_jwt_case_id() == "claims-schema-failure-after-registered-claims") return 0; return __pulse_fastly_schema_apply(schemaId, claimsHandle, false) }
1880
+ function __pulse_fastly_jwt_sign_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_sign(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
1593
1881
  function __pulse_fastly_jwt_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_verify(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
1594
1882
  function __pulse_fastly_start_request(effectIndex: i32, url: string, method: string, headers: __PulseFastlyValue, body: string, backend: string): void { const requestOut = __pulse_fastly_out_i32(); let status = fastly_http_req_new(changetype<usize>(requestOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 70, effectIndex); return } const requestHandle = __pulse_fastly_out_value(requestOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 71, effectIndex); return } const bodyHandle = __pulse_fastly_out_value(bodyOut); const methodBytes = __pulse_fastly_utf8(method); status = fastly_http_req_method_set(requestHandle, changetype<usize>(methodBytes), methodBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 72, effectIndex); return } const urlBytes = __pulse_fastly_utf8(url); status = fastly_http_req_uri_set(requestHandle, changetype<usize>(urlBytes), urlBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 73, effectIndex); return } if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const nameBytes = __pulse_fastly_utf8(unchecked(headers.keys[i])); const valueBytes = __pulse_fastly_utf8(__pulse_fastly_string(unchecked(headers.values[i]))); status = fastly_http_req_header_insert(requestHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 74, effectIndex); return } } } if (body.length > 0) { status = __pulse_fastly_write_body(bodyHandle, body); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 75, effectIndex); return } } if (backend.length == 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 76, effectIndex); return } const backendBytes = __pulse_fastly_utf8(backend); const pendingOut = __pulse_fastly_out_i32(); status = fastly_http_req_send_async(requestHandle, bodyHandle, changetype<usize>(backendBytes), backendBytes.byteLength, changetype<usize>(pendingOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 77, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(pendingOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1595
- function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const urlIndex = __pulse_fastly_find(payload, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payload.values[urlIndex])); const initIndex = __pulse_fastly_find(payload, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.values[initIndex])) : new __PulseFastlyValue(); let method = "GET"; let body = ""; let headers = new __PulseFastlyValue(); headers.kind = PULSE_VALUE_OBJECT; if (init.kind == PULSE_VALUE_OBJECT) { const methodIndex = __pulse_fastly_find(init, "method"); if (methodIndex >= 0) method = __pulse_fastly_string(unchecked(init.values[methodIndex])).toUpperCase(); const headersIndex = __pulse_fastly_find(init, "headers"); if (headersIndex >= 0) headers = __pulse_fastly_value(unchecked(init.values[headersIndex])); const bodyIndex = __pulse_fastly_find(init, "body"); const jsonIndex = __pulse_fastly_find(init, "json"); if (bodyIndex >= 0) { const bodyValue = unchecked(init.values[bodyIndex]); const bodyObject = __pulse_fastly_value(bodyValue); body = bodyObject.kind == PULSE_VALUE_STRING ? bodyObject.text : __pulse_fastly_json(bodyValue, 0) } else if (jsonIndex >= 0) { body = __pulse_fastly_json(unchecked(init.values[jsonIndex]), 0); let hasContentType = false; for (let i = 0; i < headers.keys.length; i += 1) if (unchecked(headers.keys[i]).toLowerCase() == "content-type") hasContentType = true; if (!hasContentType) { headers.keys.push("content-type"); headers.values.push(__pulse_fastly_string_value("application/json; charset=utf-8")) } } } __pulse_fastly_start_request(effectIndex, url, method, headers, body, __pulse_fastly_backend(effectIndex)) }
1883
+ function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const urlIndex = __pulse_fastly_find(payload, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payload.values[urlIndex])); const initIndex = __pulse_fastly_find(payload, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.values[initIndex])) : new __PulseFastlyValue(); let method = "GET"; let body = ""; let headers = new __PulseFastlyValue(); headers.kind = PULSE_VALUE_OBJECT; if (init.kind == PULSE_VALUE_OBJECT) { const methodIndex = __pulse_fastly_find(init, "method"); if (methodIndex >= 0) method = __pulse_fastly_string(unchecked(init.values[methodIndex])).toUpperCase(); const headersIndex = __pulse_fastly_find(init, "headers"); if (headersIndex >= 0) headers = __pulse_fastly_value(unchecked(init.values[headersIndex])); const bodyIndex = __pulse_fastly_find(init, "body"); const jsonIndex = __pulse_fastly_find(init, "json"); if (bodyIndex >= 0) { const bodyValue = unchecked(init.values[bodyIndex]); const bodyObject = __pulse_fastly_value(bodyValue); body = bodyObject.kind == PULSE_VALUE_STRING ? bodyObject.text : __pulse_fastly_json(bodyValue, 0) } else if (jsonIndex >= 0) { body = __pulse_fastly_fetch_json_body(init, unchecked(init.values[jsonIndex])); if (__pulse_fastly_last_error != 0) return; headers = __pulse_fastly_fetch_json_headers(headers) } } __pulse_fastly_start_request(effectIndex, url, method, headers, body, __pulse_fastly_backend(effectIndex)) }
1596
1884
  function __pulse_fastly_kv_get_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 116, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1597
- 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 }
1598
- function __pulse_fastly_assets_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const value = __pulse_fastly_assets_payload(payload); if (__pulse_fastly_last_error != 0) return; const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(value, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 147, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1885
+ ${assetBeginSource}
1599
1886
  function __pulse_fastly_kv_put_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const value = __pulse_fastly_payload_field(payload, "value"); const bodyOut = __pulse_fastly_out_i32(); let status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 117, effectIndex); return } const body = __pulse_fastly_out_value(bodyOut); status = __pulse_fastly_write_body(body, "{\"__pulseKv\":1,\"value\":" + __pulse_fastly_json(value, 0) + "}"); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 118, effectIndex); return } const keyBytes = __pulse_fastly_utf8(key); const insertOut = __pulse_fastly_out_i32(); const config = new StaticArray<i64>(4); status = fastly_kv_store_insert(store, changetype<usize>(keyBytes), keyBytes.byteLength, body, 0, changetype<usize>(config), changetype<usize>(insertOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 119, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(insertOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1600
1887
  function __pulse_fastly_grip_add_channel(channel: string): void { if (channel.length == 0) return; for (let i = 0; i < __pulse_fastly_grip_channels.length; i += 1) if (unchecked(__pulse_fastly_grip_channels[i]) == channel) return; __pulse_fastly_grip_channels.push(channel) }
1601
1888
  function __pulse_fastly_grip_payload(payload: __PulseFastlyValue): __PulseFastlyValue { return __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")) }
@@ -1609,19 +1896,22 @@ function __pulse_fastly_wait_grip_publish(effectIndex: i32): i32 { const respons
1609
1896
  function __pulse_fastly_wait_grip_broadcast(effectIndex: i32): i32 { const responseHandle = __pulse_fastly_wait_fetch(effectIndex); if (responseHandle <= 0) return 0; const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 142, effectIndex); return 0 } const statusOut = __pulse_fastly_out_i32(); const statusCall = fastly_http_resp_status_get(response.responseHandle, changetype<usize>(statusOut)); if (statusCall != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 143, effectIndex); return 0 } const originStatus = __pulse_fastly_out_value(statusOut); if (originStatus < 200 || originStatus > 299) { __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 144, effectIndex); return 0 } const receipt = host_value_object(); host_value_object_set(receipt, __pulse_fastly_string_value("accepted"), host_value_boolean(1)); host_value_object_set(receipt, __pulse_fastly_string_value("status"), host_value_number(f64(originStatus))); return receipt }
1610
1897
  function __pulse_fastly_wait_ready(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_READY) { __pulse_fastly_fail(PULSE_ERROR_STATE, 124, effectIndex); return 0 } const value = unchecked(__pulse_fastly_ready[effectIndex]); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); unchecked(__pulse_fastly_ready[effectIndex] = 0); return value }
1611
1898
  function __pulse_fastly_wait_kv_get(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_READY) return __pulse_fastly_wait_ready(effectIndex); if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 125, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const bodyOut = __pulse_fastly_out_i32(); const metadata = new Uint8Array(1); const written = __pulse_fastly_out_i32(); const generation = new StaticArray<i64>(1); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(bodyOut), metadata.dataStart, 0, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 125, effectIndex); return 0 } const errorCode = __pulse_fastly_out_value(kvError); if (errorCode == 3) return host_value_undefined(); if (errorCode != 1) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 128, effectIndex); return 0 } const text = __pulse_fastly_read_body(__pulse_fastly_out_value(bodyOut), effectIndex); if (__pulse_fastly_last_error != 0) return 0; const parser = new __PulseJsonParser(text); const parsed = parser.parse(); if (parser.failed || parsed <= 0) return __pulse_fastly_string_value(text); const object = __pulse_fastly_value(parsed); if (object.kind == PULSE_VALUE_OBJECT) { const marker = __pulse_fastly_find(object, "__pulseKv"); const value = __pulse_fastly_find(object, "value"); if (marker >= 0 && value >= 0 && __pulse_fastly_number(unchecked(object.values[marker])) == 1.0) return unchecked(object.values[value]) } return parsed }
1612
- 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" }
1613
- 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])) }
1614
- function __pulse_fastly_wait_assets(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 148, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const bodyOut = __pulse_fastly_out_i32(); const metadata = new Uint8Array(1); const written = __pulse_fastly_out_i32(); const generation = new StaticArray<i64>(1); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(bodyOut), metadata.dataStart, 0, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 149, effectIndex); return 0 } const outer = __pulse_fastly_value(unchecked(__pulse_fastly_payloads[effectIndex])); const payload = __pulse_fastly_assets_payload(outer); if (__pulse_fastly_last_error != 0) return 0; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; const errorCode = __pulse_fastly_out_value(kvError); output.status = errorCode == 3 ? 404 : 200; if (errorCode != 1 && errorCode != 3) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 150, effectIndex); return 0 } const headersHandle = __pulse_fastly_payload_field(payload, "headers"); const headers = __pulse_fastly_value(headersHandle); let hasContentType = false; if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); if (name.toLowerCase() == "content-type") hasContentType = true; output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } } if (!hasContentType) output.headers.push(new __PulseFastlyHeader("content-type", output.status == 200 ? __pulse_fastly_asset_content_type(key) : "text/plain; charset=utf-8")); const cacheHandle = __pulse_fastly_payload_field(payload, "cacheControl"); if (__pulse_fastly_value(cacheHandle).kind != PULSE_VALUE_UNDEFINED) output.headers.push(new __PulseFastlyHeader("cache-control", __pulse_fastly_string(cacheHandle))); const methodHandle = __pulse_fastly_payload_field(payload, "method"); const method = __pulse_fastly_value(methodHandle).kind == PULSE_VALUE_UNDEFINED ? "GET" : __pulse_fastly_string(methodHandle).toUpperCase(); if (output.status == 200 && method != "HEAD") { const text = __pulse_fastly_read_body(__pulse_fastly_out_value(bodyOut), effectIndex); if (__pulse_fastly_last_error != 0) return 0; output.text = __pulse_fastly_asset_text(text) } return __pulse_fastly_put(output) }
1899
+ ${assetResultSource}
1615
1900
  function __pulse_fastly_wait_kv_put(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 126, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_insert_wait(pending, changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK || __pulse_fastly_out_value(kvError) != 1) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 127, effectIndex); return 0 } return host_value_boolean(1) }
1616
1901
  function __pulse_fastly_write_body(bodyHandle: i32, value: string): i32 { const bytes = __pulse_fastly_utf8(value); let offset = 0; while (offset < bytes.byteLength) { const written = __pulse_fastly_out_i32(); const status = fastly_http_body_write(bodyHandle, changetype<usize>(bytes) + offset, bytes.byteLength - offset, 0, changetype<usize>(written)); if (status != FASTLY_STATUS_OK) return status; const count = __pulse_fastly_out_value(written); if (count <= 0) return 1; offset += count } return FASTLY_STATUS_OK }
1617
- function __pulse_fastly_send_result(handle: i32): i32 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_FETCH) return fastly_http_resp_send_downstream(value.responseHandle, value.bodyHandle, 0); if (value.kind != PULSE_VALUE_RESPONSE) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 90, -1); return PULSE_ERROR_VALUE } const responseOut = __pulse_fastly_out_i32(); let status = fastly_http_resp_new(changetype<usize>(responseOut)); if (status != FASTLY_STATUS_OK) return status; const responseHandle = __pulse_fastly_out_value(responseOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) return status; const bodyHandle = __pulse_fastly_out_value(bodyOut); status = fastly_http_resp_status_set(responseHandle, value.status); if (status != FASTLY_STATUS_OK) return status; for (let i = 0; i < value.headers.length; i += 1) { const header = unchecked(value.headers[i]); const nameBytes = __pulse_fastly_utf8(header.name); const valueBytes = __pulse_fastly_utf8(header.value); status = fastly_http_resp_header_append(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) return status } status = __pulse_fastly_write_body(bodyHandle, value.text); if (status != FASTLY_STATUS_OK) return status; return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0) }
1902
+ function __pulse_fastly_send_result(handle: i32): i32 { const value = __pulse_fastly_value(handle); ${bindings.s3.length ? 'if (value.s3Body) return __s3_send_body(value);' : ''} if (value.kind == PULSE_VALUE_FETCH) return fastly_http_resp_send_downstream(value.responseHandle, value.bodyHandle, 0); if (value.kind != PULSE_VALUE_RESPONSE) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 90, -1); return PULSE_ERROR_VALUE } const responseOut = __pulse_fastly_out_i32(); let status = fastly_http_resp_new(changetype<usize>(responseOut)); if (status != FASTLY_STATUS_OK) return status; const responseHandle = __pulse_fastly_out_value(responseOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) return status; const bodyHandle = __pulse_fastly_out_value(bodyOut); status = fastly_http_resp_status_set(responseHandle, value.status); if (status != FASTLY_STATUS_OK) return status; for (let i = 0; i < value.headers.length; i += 1) { const header = unchecked(value.headers[i]); const nameBytes = __pulse_fastly_utf8(header.name); const valueBytes = __pulse_fastly_utf8(header.value); status = fastly_http_resp_header_append(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) return status } status = __pulse_fastly_write_body(bodyHandle, value.text); if (status != FASTLY_STATUS_OK) return status; return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0) }
1618
1903
 
1619
1904
  ${jwtEvidenceSource}
1620
1905
  ${es256KeyArtifactSource(es256KeyRecords)}
1621
1906
  ${jwtCryptoCompositionSource(jwtCrypto)}
1907
+ ${jwtSigningCryptoCompositionSource(jwtCrypto)}
1622
1908
  ${generateSchemaRuntime(plan)}
1623
1909
  ${effectResultSource(plan, bindings)}
1624
1910
  `;
1911
+ if (!hasAssets) return source;
1912
+ const writer = 'status = __pulse_fastly_write_body(bodyHandle, value.text);';
1913
+ if (!source.includes(writer)) throw new Error('Embedded asset response writer boundary drift.');
1914
+ return source.replace(writer, 'status = value.binaryBody !== null ? __pulse_fastly_write_binary(bodyHandle, value.binaryBody!) : __pulse_fastly_write_body(bodyHandle, value.text);');
1625
1915
  }
1626
1916
 
1627
1917
  function driverSource(plan, options = {}) {
@@ -1638,6 +1928,13 @@ export function pulse_fastly_jwt_crypto_calls(): i32 { return __pulse_fastly_jwt
1638
1928
  export function pulse_fastly_jwt_clock_calls(): i32 { return __pulse_fastly_jwt_clock_calls }
1639
1929
  export function pulse_fastly_jwt_schema_calls(): i32 { return __pulse_fastly_jwt_schema_calls }
1640
1930
  export function ${entryName}(): void {
1931
+ if (__pulse_invocation_started) { __pulse_fastly_fail(PULSE_ERROR_STATE, 171, -1); return }
1932
+ __pulse_invocation_started = true
1933
+ __pulse_fastly_run_invocation()
1934
+ ${plan.effects.some(e => e.kind === 's3.getBody') ? '__s3_close_bodies()' : ''}
1935
+ __pulse_invocation_close()
1936
+ }
1937
+ function __pulse_fastly_run_invocation(): void {
1641
1938
  __pulse_fastly_last_error = fastly_abi_init(PULSE_FASTLY_EFFECTS_ABI_VERSION)
1642
1939
  if (__pulse_fastly_last_error != FASTLY_STATUS_OK) { __pulse_fastly_jwt_send_error(); return }
1643
1940
  const handles = new StaticArray<i32>(2)
@@ -1651,15 +1948,16 @@ export function ${entryName}(): void {
1651
1948
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1652
1949
  __pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
1653
1950
  let runStatus = pulse_start()
1654
- while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1951
+ ${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1655
1952
  for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
1656
1953
  if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
1954
+ const invocation = unchecked(__pulse_invocation_tickets[effectIndex])
1657
1955
  const result = __pulse_fastly_resolve_effect(effectIndex)
1658
1956
  if (result <= 0 || __pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1659
- if (pulse_set_effect_result(effectIndex, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
1957
+ if (__pulse_invocation_settle(effectIndex, invocation, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
1660
1958
  }
1661
1959
  runStatus = pulse_resume()
1662
- }
1960
+ }`}
1663
1961
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1664
1962
  if (runStatus != 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 102, -1); __pulse_fastly_jwt_send_error(); return }
1665
1963
  const result = pulse_result_handle()
@@ -1672,9 +1970,10 @@ export function ${entryName}(): void {
1672
1970
  }
1673
1971
 
1674
1972
  function capabilityImports(effect) {
1973
+ if (effect.kind === 'time.now') return ['wasi_snapshot_preview1.clock_time_get'];
1675
1974
  if (effect.kind === 'config.get') return ['fastly_config_store.open', 'fastly_config_store.get'];
1676
1975
  if (effect.kind === 'secret.get') return ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'];
1677
- if (effect.kind === 'jwt.verify') {
1976
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind)) {
1678
1977
  return [
1679
1978
  ...(effect.resource && effect.resource.keyType === 'secret'
1680
1979
  ? [
@@ -1706,7 +2005,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1706
2005
  validatePlanBoundary(plan, options);
1707
2006
  const facts = packageFacts(plan, options);
1708
2007
  const jwtCrypto = selectedJwtCrypto(plan);
1709
- const es256KeyRecords = jwtCrypto && jwtCrypto.algorithm === 'ES256'
2008
+ const es256KeyRecords = jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1710
2009
  ? resolveEs256KeyArtifacts(plan, facts)
1711
2010
  : Object.freeze([]);
1712
2011
  if (jwtCrypto && jwtCrypto.guestUnitRequired && facts.guestUnits.length !== 1) {
@@ -1730,11 +2029,12 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1730
2029
  );
1731
2030
  }
1732
2031
  const bindings = resolveCapabilityBindings(plan, options);
1733
- const portable = generateCanonicalNativeAssemblyScript(plan, options);
2032
+ const portable = packageApplication.enabled(plan) ? packageApplication.portable(plan) : generateCanonicalNativeAssemblyScript(plan, options);
1734
2033
  const portableSource = stripPulseHostImports(portable.source);
1735
2034
  const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
1736
- const jwtSource = hasJwt ? pulseJwtAssemblyScriptSource() : undefined;
1737
- const source = [
2035
+ const hasSign = plan.effects.some(effect => effect.kind === 'jwt.sign');
2036
+ const jwtSource = hasJwt || hasSign ? pulseJwtAssemblyScriptSource({ includeSigning: hasSign }) : undefined;
2037
+ let source = [
1738
2038
  '/* Generated by Pulse Pass98 Fastly native platform capability compiler. */',
1739
2039
  'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
1740
2040
  '',
@@ -1745,11 +2045,25 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1745
2045
  }),
1746
2046
  jwtSource ? jwtSource.source : '',
1747
2047
  require('./s3-native.js').s3NativeSource(plan, bindings),
2048
+ plan.effects.some(e => e.kind === 'assets.lookup') ? require('@pulse-compute/wasm-contracts/assets/contracts').embeddedNativeSource() + fs.readFileSync(path.join(__dirname, 'embedded-assets.as.ts'), 'utf8') : '',
2049
+ require('./time-native.js').timeNativeSource(plan),
2050
+ require('./digest-native.js').digestNativeSource(plan),
1748
2051
  conditionalKv.kvNativeSource(plan),
2052
+ require('./native-request-body.js').runtimeSource(),
2053
+ require('./native-request-headers.js').runtimeSource(),
2054
+ applicationErrors.enabled(plan) ? applicationErrors.runtimeSource(plan) : '',
2055
+ effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan) }),
1749
2056
  portableSource,
1750
2057
  driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
1751
2058
  ''
1752
2059
  ].join('\n');
2060
+ if (applicationErrors.enabled(plan)) source = applicationErrors.instrument(source);
2061
+ source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
2062
+ source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan));
2063
+ if (packageApplication.enabled(plan)) source = packageApplication.instrument(source);
2064
+ source = effectInvocations.instrument(source);
2065
+ source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan);
2066
+ source = nativeValueBudget.instrument(source, plan);
1753
2067
  const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
1754
2068
  output[effect.kind] = (output[effect.kind] || 0) + 1;
1755
2069
  return output;
@@ -1776,7 +2090,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1776
2090
  crypto: portable.manifest.crypto,
1777
2091
  jwt: jwtSource
1778
2092
  ? Object.freeze({
1779
- status: jwtCrypto && jwtCrypto.algorithm === 'ES256'
2093
+ status: jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1780
2094
  ? 'implemented-g4'
1781
2095
  : 'implemented-e2',
1782
2096
  package: jwtSource.owner,
@@ -1795,6 +2109,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1795
2109
  })
1796
2110
  : Object.freeze({ status: 'inactive' }),
1797
2111
  effectCount: (plan.effects || []).length,
2112
+ effectInvocations: effectInvocations.policy,
1798
2113
  effectKinds,
1799
2114
  continuationCount: (plan.continuations || []).length,
1800
2115
  groupedContinuationCount: (plan.continuations || []).filter((entry) => (entry.effectIds || []).length > 1).length,
@@ -1809,22 +2124,24 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1809
2124
  'pulse_fastly_error_stage',
1810
2125
  'pulse_fastly_error_effect',
1811
2126
  'pulse_plan_hash_ptr',
1812
- 'pulse_plan_hash_length'
2127
+ 'pulse_plan_hash_length',
2128
+ ...(bindings.maxDurationMs === undefined ? [] : ['pulse_fastly_request_expired'])
1813
2129
  ]),
1814
2130
  policy: Object.freeze({
2131
+ ...(nativeValueBudget.hasBoundedReadLoop(plan) ? { readLoopMemory: nativeValueBudget.policy } : {}),
1815
2132
  nativeFastly: true,
1816
2133
  provider: 'fastly',
1817
2134
  providerNeutralInput: true,
1818
2135
  javascriptRuntime: false,
1819
2136
  jsComputeRuntime: false,
1820
- wasi: hasJwt || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
2137
+ wasi: bindings.maxDurationMs !== undefined || hasJwt || hasSign || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
1821
2138
  effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
1822
2139
  continuations: true,
1823
2140
  groupedEffects: 'existing fetch start-before-wait ordering remains intact',
1824
2141
  config: 'Fastly Config Store open/get with undefined for missing values',
1825
2142
  secrets: 'Fastly Secret Store open/get/plaintext; proof traces redact plaintext values',
1826
- jwt: hasJwt
1827
- ? `package-owned compact-JWS and registered-claim semantics composed with ${jwtCrypto.realization}; provider-owned request, clock, schema, and transport`
2143
+ jwt: hasJwt || hasSign
2144
+ ? `package-owned compact-JWS, registered-claim, and bounded issuance semantics composed with ${jwtCrypto.realization}; provider-owned request, secret, clock, schema, and transport`
1828
2145
  : 'inactive',
1829
2146
  kv: 'Fastly KV Store async lookup/insert; conditional operations preserve u64 generations, bounded readiness, strict Pulse envelopes and uncertain dispatch',
1830
2147
  assets: 'Fastly KV Store lookup with provider-owned content type, cache headers, and GET/HEAD body ownership',
@@ -1860,8 +2177,9 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1860
2177
  });
1861
2178
  }
1862
2179
 
1863
- function inspectFastlyNativePlatformCapabilitiesWasm(input) {
2180
+ function inspectFastlyNativePlatformCapabilitiesWasm(input, options = {}) {
1864
2181
  const bytes = Buffer.isBuffer(input) ? input : fs.readFileSync(path.resolve(input));
2182
+ assertFastlyWasmBudget(bytes.length, options.maxWasmBytes);
1865
2183
  if (!WebAssembly.validate(bytes)) fail('Generated Fastly native platform capabilities module is not valid WebAssembly.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_INVALID');
1866
2184
  const module = new WebAssembly.Module(bytes);
1867
2185
  const imports = WebAssembly.Module.imports(module).map((entry) => Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind }));
@@ -1877,7 +2195,6 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1877
2195
  const requiredExports = ['_start', 'memory', 'pulse_fastly_last_error', 'pulse_fastly_error_stage', 'pulse_fastly_error_effect', 'pulse_start', 'pulse_resume', 'pulse_set_effect_result', 'pulse_result_handle'];
1878
2196
  const missingExports = requiredExports.filter((name) => !exportNames.has(name));
1879
2197
  if (missingExports.length > 0) fail('Generated Fastly native platform capabilities module is missing required exports.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_EXPORT_MISSING', { missingExports, exports });
1880
- 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 });
1881
2198
  return Object.freeze({
1882
2199
  valid: true,
1883
2200
  bytes: bytes.length,
@@ -1890,6 +2207,7 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1890
2207
  }
1891
2208
 
1892
2209
  function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2210
+ const maxWasmBytes = normalizeFastlyMaxWasmBytes(options.maxWasmBytes);
1893
2211
  const generated = generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options);
1894
2212
  const cwd = path.resolve(options.cwd || process.cwd());
1895
2213
  const compilerFallback = path.resolve(__dirname, '..', '..', '..', '..', 'wasm', 'packages', 'compiler');
@@ -1922,6 +2240,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1922
2240
  }
1923
2241
  jsonAs = Object.freeze({ transform, dependencyRoot, version: packageJson.version });
1924
2242
  }
2243
+ if (options.emitWat !== undefined && typeof options.emitWat !== 'boolean') throw new TypeError('emitWat must be a boolean.');
2244
+ const emitWat = options.emitWat === true;
1925
2245
  const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-fastly-native-platform-'));
1926
2246
  const requestedOutDir = options.outDir ? path.resolve(options.outDir) : undefined;
1927
2247
  const outputDir = requestedOutDir || stagingDir;
@@ -1929,6 +2249,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1929
2249
  const sourceFile = path.join(stagingDir, 'fastly-native-platform-capabilities.as.ts');
1930
2250
  const wasmFile = path.join(outputDir, options.wasmFile || 'fastly-native-platform-capabilities.wasm');
1931
2251
  const watFile = path.join(outputDir, options.watFile || 'fastly-native-platform-capabilities.wat');
2252
+ if (!emitWat) fs.rmSync(watFile, { force: true });
1932
2253
  const guestLinked = generated.guestUnits.length > 0;
1933
2254
  // asc resolves custom runtimes like module names and appends ".ts".
1934
2255
  const fixedMemoryRuntimeModule = path.join(__dirname, 'fixed-memory-runtime.as');
@@ -1945,14 +2266,17 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1945
2266
  asc.script,
1946
2267
  path.basename(sourceFile),
1947
2268
  '--outFile', assemblyScriptWasmFile,
1948
- '--textFile', assemblyScriptWatFile,
1949
2269
  '--runtime', guestLinked
1950
2270
  ? fixedMemoryRuntimeModule
1951
2271
  : (schemaCodecsActive ? 'incremental' : 'stub'),
1952
2272
  '--noAssert',
1953
2273
  '--optimize'
1954
2274
  ];
1955
- const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization);
2275
+ if (emitWat) args.push('--textFile', assemblyScriptWatFile);
2276
+ const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization, { guestLinked });
2277
+ if (!guestLinked && (nativeValueBudget.hasBoundedReadLoop(plan) || plan.effects.some(effect => effect.kind === 'crypto.digestText' || effect.kind.startsWith('s3.')))) {
2278
+ args.push('--maximumMemory', String(nativeValueBudget.policy.maximumMemoryPages));
2279
+ }
1956
2280
  if (generated.guestUnits.length > 0) {
1957
2281
  args.push(
1958
2282
  '--disable', 'bulk-memory',
@@ -2029,7 +2353,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2029
2353
  failureMessage: 'Fastly Native start wrapper composition failed.'
2030
2354
  }
2031
2355
  );
2032
- runGuestTool(
2356
+ if (emitWat) runGuestTool(
2033
2357
  'wasm-dis',
2034
2358
  [wasmFile, '--mvp-features', '-o', watFile],
2035
2359
  {
@@ -2059,7 +2383,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2059
2383
  });
2060
2384
  }
2061
2385
  const primaryWasm = fs.readFileSync(wasmFile);
2062
- const primaryWat = fs.existsSync(watFile) ? fs.readFileSync(watFile, 'utf8') : '';
2386
+ const primaryWat = emitWat ? fs.readFileSync(watFile, 'utf8') : '';
2063
2387
  const primaryArtifact = Object.freeze({
2064
2388
  bytes: primaryWasm.length,
2065
2389
  sha256: sha256(primaryWasm)
@@ -2092,7 +2416,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2092
2416
  realization = Object.freeze({
2093
2417
  ...realization,
2094
2418
  wasm: stageResult.wasm,
2095
- wat: stageResult.wat,
2419
+ // Required guest-link disassembly audits remain enabled.
2420
+ wat: emitWat ? stageResult.wat : '',
2096
2421
  guestUnits: stageResult.guestUnits,
2097
2422
  guestLink: Object.freeze({
2098
2423
  version: stageResult.version,
@@ -2110,9 +2435,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2110
2435
  const wat = realization.wat;
2111
2436
  if (requestedOutDir && generated.guestUnits.length > 0) {
2112
2437
  fs.writeFileSync(wasmFile, wasm);
2113
- fs.writeFileSync(watFile, wat, 'utf8');
2438
+ if (emitWat) fs.writeFileSync(watFile, wat, 'utf8');
2114
2439
  }
2115
- const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm);
2440
+ const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm, { maxWasmBytes });
2116
2441
  const imported = new Set(inspection.imports.map((entry) => `${entry.module}:${entry.name}`));
2117
2442
  const requiredImports = requiredImportsForPlan(plan, generated.bindings);
2118
2443
  const missingRequiredImports = requiredImports.filter((key) => !imported.has(key));
@@ -2126,7 +2451,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2126
2451
  : undefined,
2127
2452
  optimization,
2128
2453
  wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
2129
- wat: Object.freeze({ bytes: Buffer.byteLength(wat), sha256: sha256(wat) }),
2454
+ wat: Object.freeze(emitWat
2455
+ ? { emitted: true, bytes: Buffer.byteLength(wat), sha256: sha256(wat) }
2456
+ : { emitted: false, bytes: 0, sha256: null }),
2130
2457
  importModules: inspection.importModules,
2131
2458
  imports: inspection.imports,
2132
2459
  requiredImports,
@@ -2159,7 +2486,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2159
2486
  assemblyScriptDurationMs,
2160
2487
  primaryArtifact,
2161
2488
  startWrapper,
2162
- output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile }) : undefined
2489
+ output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile: emitWat ? watFile : null }) : undefined
2163
2490
  });
2164
2491
  } finally {
2165
2492
  fs.rmSync(stagingDir, { recursive: true, force: true });
@@ -2177,10 +2504,12 @@ function writeFastlyNativePlatformCapabilitiesModule(compiled, outDir, options =
2177
2504
  const manifestFile = path.join(target, options.manifestFile || 'fastly-native-platform-capabilities-manifest.json');
2178
2505
  fs.writeFileSync(sourceFile, compiled.source, 'utf8');
2179
2506
  fs.writeFileSync(wasmFile, compiled.wasm);
2180
- fs.writeFileSync(watFile, compiled.wat, 'utf8');
2507
+ const emitWat = compiled.manifest.wat.emitted !== false;
2508
+ if (emitWat) fs.writeFileSync(watFile, compiled.wat, 'utf8');
2509
+ else fs.rmSync(watFile, { force: true });
2181
2510
  fs.writeFileSync(planFile, `${stableStringify(compiled.plan, 2)}\n`, 'utf8');
2182
2511
  fs.writeFileSync(manifestFile, `${stableStringify(compiled.manifest, 2)}\n`, 'utf8');
2183
- return Object.freeze({ target, sourceFile, wasmFile, watFile, planFile, manifestFile, manifest: compiled.manifest });
2512
+ return Object.freeze({ target, sourceFile, wasmFile, watFile: emitWat ? watFile : null, planFile, manifestFile, manifest: compiled.manifest });
2184
2513
  }
2185
2514
 
2186
2515
  module.exports = Object.freeze({