@pulse-compute/provider-fastly 1.0.0-beta.1 → 1.0.0-beta.10

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 (68) hide show
  1. package/README.md +7 -6
  2. package/dist/config-schema.json +30 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +8 -0
  6. package/dist/index.js.map +1 -1
  7. package/package.json +11 -10
  8. package/src/build/canonical-target.js +35 -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/entities-native.js +6 -11
  13. package/src/build/kv-native.as.ts +331 -0
  14. package/src/build/kv-native.js +27 -0
  15. package/src/build/native-application-errors.js +159 -0
  16. package/src/build/native-binary-resources.js +30 -0
  17. package/src/build/native-body-forwarding.as.ts +341 -0
  18. package/src/build/native-body-forwarding.js +69 -0
  19. package/src/build/native-dynamic-backends.as.ts +115 -0
  20. package/src/build/native-dynamic-backends.js +52 -0
  21. package/src/build/native-fetch-deadlines.as.ts +164 -0
  22. package/src/build/native-fetch-deadlines.js +27 -0
  23. package/src/build/native-fetch-errors.js +38 -0
  24. package/src/build/native-handle-ownership.js +127 -0
  25. package/src/build/native-header-lookup.js +50 -0
  26. package/src/build/native-http-effects.js +155 -32
  27. package/src/build/native-platform-capabilities.js +755 -210
  28. package/src/build/native-request-body.js +44 -0
  29. package/src/build/native-request-headers.js +93 -0
  30. package/src/build/native-send-error-detail.as.ts +12 -0
  31. package/src/build/native-stdio.js +45 -0
  32. package/src/build/native-string-values.js +143 -0
  33. package/src/build/native-transport-details.as.ts +84 -0
  34. package/src/build/native-value-budget.js +90 -0
  35. package/src/build/package-native-application.js +114 -0
  36. package/src/build/request-budget.as.ts +50 -0
  37. package/src/build/request-budget.js +47 -0
  38. package/src/build/s3-body.as.ts +97 -0
  39. package/src/build/s3-native.as.ts +228 -0
  40. package/src/build/s3-native.js +30 -0
  41. package/src/build/schema-admission.js +83 -0
  42. package/src/build/schema-nested-json.js +93 -0
  43. package/src/build/schema-scalar-record.js +41 -0
  44. package/src/build/time-native.js +49 -0
  45. package/src/build/wasm-budget.js +28 -0
  46. package/src/config-api.js +6 -0
  47. package/src/config-schema.json +30 -0
  48. package/src/index.ts +25 -0
  49. package/src/javascript/jwt-verifier.js +14 -4
  50. package/src/javascript/logging.js +11 -2
  51. package/src/javascript/package-effects.js +5 -2
  52. package/src/javascript/runtime-host.js +7 -3
  53. package/src/javascript/source-package.js +4 -2
  54. package/src/javascript/target-support-policy.js +20 -3
  55. package/src/javascript/target.js +17 -1
  56. package/src/javascript/test-runtime.js +1 -0
  57. package/src/provider-contract.js +22 -6
  58. package/src/runtime/canonical-api-runtime.js +6 -0
  59. package/src/testing/conditional-kv-host.js +155 -0
  60. package/src/testing/native-dynamic-backends-host.js +39 -0
  61. package/src/testing/native-http-effects-host.js +9 -1
  62. package/src/testing/native-platform-capabilities-host.js +349 -24
  63. package/src/toolchain/body-forwarding.js +25 -0
  64. package/src/toolchain/config.js +16 -0
  65. package/src/toolchain/index.js +48 -5
  66. package/src/toolchain/logging.js +18 -0
  67. package/src/toolchain/native-execution.js +78 -0
  68. package/src/toolchain/s3.js +26 -0
@@ -1,6 +1,19 @@
1
1
  'use strict';
2
2
 
3
+ const dynamicBackends = require('./native-dynamic-backends.js');
4
+
5
+ const { scalarRecordProjectionLines } = require('./schema-scalar-record.js');
6
+ const { hasJsonAdmission, tracksJsonDuplicates, schemaParserDepth, nestedJsonProjectionLines, schemaJsonAdmissionSource, schemaFetchJsonSource } = require('./schema-nested-json.js');
7
+ const { nativeLoggingSource } = require('./native-stdio.js');
8
+ const applicationErrors = require('./native-application-errors.js');
9
+ const effectInvocations = require('./effect-invocations.js');
10
+ const packageApplication = require('./package-native-application.js');
11
+ const nativeValueBudget = require('./native-value-budget.js');
12
+ const { schemaNeedsValueProjection } = require('@pulse-compute/wasm-contracts/schema-json/registry');
13
+
3
14
  const crypto = require('node:crypto');
15
+ const { nativeStringAssembly, nativeStringFields, nativeStringConcat, nativeStringTrim, nativeStringIndex, needsNativeValueFailureGuard } = require('./native-string-values.js');
16
+ const conditionalKv = require('./kv-native.js');
4
17
  const fs = require('node:fs');
5
18
  const os = require('node:os');
6
19
  const path = require('node:path');
@@ -12,6 +25,7 @@ const {
12
25
  } = require('@pulse-compute/wasm-contracts/package/package-contract');
13
26
  const {
14
27
  EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION,
28
+ prepareReportCapture,
15
29
  appendAssemblyScriptOptimizationArgs
16
30
  } = require('@pulse-compute/wasm-build-support/native-optimization');
17
31
 
@@ -77,6 +91,7 @@ function loadGuestToolchain() {
77
91
 
78
92
  const { resolveAsc } = loadBuildSupport();
79
93
  const { generateCanonicalNativeAssemblyScript } = loadNativeGenerator();
94
+ const nativeHandleOwnership = require('./native-handle-ownership.js');
80
95
  const { pulseJwtAssemblyScriptSource } = loadJwtNativeSource();
81
96
  const {
82
97
  GUEST_LINK_STAGE_INVOCATION_VERSION,
@@ -89,25 +104,34 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION = 'pulse.fastly-native-platfor
89
104
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_GENERATOR_VERSION = 'pulse.fastly-native-platform-capabilities-generator.v1';
90
105
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION = 'pulse.fastly-native-platform-capabilities-compiler.v1';
91
106
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION = 1;
92
- const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = 1024 * 1024;
107
+ // Internal compilation budget, not a Fastly service limit. Keep a finite guard
108
+ // while allowing composed applications beyond the original 1 MiB proof fixture.
109
+ const { DEFAULT_FASTLY_MAX_WASM_BYTES, normalizeFastlyMaxWasmBytes, assertFastlyWasmBudget } = require('./wasm-budget.js');
110
+ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = DEFAULT_FASTLY_MAX_WASM_BYTES;
93
111
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES = 65536;
94
112
  const FASTLY_NATIVE_SIZE_OPTIMIZATION = EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION;
95
113
 
96
114
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
115
+ ...require('./native-body-forwarding.js').IMPORTS.map(key => Object.freeze(key.split(':'))),
116
+ ...conditionalKv.KV_IMPORTS.map((key) => Object.freeze(key.split(':'))),
117
+ ...require('./s3-native.js').S3_IMPORTS.map((key) => Object.freeze(key.split(':'))),
97
118
  Object.freeze(['fastly_abi', 'init']),
98
119
  Object.freeze(['fastly_http_req', 'body_downstream_get']),
99
120
  Object.freeze(['fastly_http_req', 'method_get']),
100
121
  Object.freeze(['fastly_http_req', 'uri_get']),
101
122
  Object.freeze(['fastly_http_req', 'header_value_get']),
123
+ Object.freeze(['fastly_http_req', 'header_names_get']),
124
+ Object.freeze(['fastly_http_req', 'header_values_get']),
102
125
  Object.freeze(['fastly_http_req', 'new']),
103
126
  Object.freeze(['fastly_http_req', 'method_set']),
104
127
  Object.freeze(['fastly_http_req', 'uri_set']),
105
128
  Object.freeze(['fastly_http_req', 'header_insert']),
106
129
  Object.freeze(['fastly_http_req', 'send_async']),
107
- Object.freeze(['fastly_http_req', 'pending_req_wait']),
130
+ Object.freeze(['fastly_http_req', 'pending_req_wait_v2']),
108
131
  Object.freeze(['fastly_http_resp', 'new']),
109
132
  Object.freeze(['fastly_http_resp', 'header_append']),
110
133
  Object.freeze(['fastly_http_resp', 'header_value_get']),
134
+ Object.freeze(['fastly_http_resp', 'header_values_get']),
111
135
  Object.freeze(['fastly_http_resp', 'status_get']),
112
136
  Object.freeze(['fastly_http_resp', 'status_set']),
113
137
  Object.freeze(['fastly_http_resp', 'send_downstream']),
@@ -124,10 +148,15 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
124
148
  Object.freeze(['fastly_kv_store', 'lookup_wait_v2']),
125
149
  Object.freeze(['fastly_kv_store', 'insert']),
126
150
  Object.freeze(['fastly_kv_store', 'insert_wait']),
151
+ Object.freeze(['fastly_kv_store', 'list']),
152
+ Object.freeze(['fastly_kv_store', 'list_wait']),
153
+ Object.freeze(['fastly_kv_store', 'delete']),
154
+ Object.freeze(['fastly_kv_store', 'delete_wait']),
127
155
  Object.freeze(['fastly_log', 'endpoint_get']),
128
156
  Object.freeze(['fastly_log', 'write']),
157
+ Object.freeze(['wasi_snapshot_preview1', 'fd_write']),
129
158
  Object.freeze(['wasi_snapshot_preview1', 'clock_time_get'])
130
- ]);
159
+ ].filter(([module, name], index, entries) => entries.findIndex((item) => item[0] === module && item[1] === name) === index));
131
160
 
132
161
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_REQUIRED_IMPORTS = Object.freeze([
133
162
  Object.freeze(['fastly_abi', 'init']),
@@ -148,38 +177,67 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KIND = Object.freeze({
148
177
  'grip.publish': 8,
149
178
  'grip.broadcast': 9,
150
179
  'assets.lookup': 10,
151
- 'jwt.verify': 11
180
+ 'jwt.verify': 11,
181
+ 'jwt.sign': 20,
182
+ 's3.head': 12,
183
+ 's3.getText': 13,
184
+ 's3.putText': 14,
185
+ 's3.getBody': 21,
186
+ 'kv.delete': 22,
187
+ 'kv.list': 23,
188
+ 'kv.getVersioned': 15,
189
+ 'kv.insertIfAbsent': 16,
190
+ 'kv.compareAndSwap': 17,
191
+ 'time.now': 18,
192
+ 'crypto.digestText': 19
152
193
  });
153
194
 
154
195
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS = new Set([
196
+ 'fastly_http_req:register_dynamic_backend',
155
197
  ...FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS.map(([moduleName, name]) => `${moduleName}:${name}`)
156
198
  ]);
157
199
 
158
200
  const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
201
+ 'time.now',
202
+ 'crypto.digestText',
159
203
  'fetch',
160
204
  'config.get',
161
205
  'secret.get',
162
206
  'kv.get',
163
207
  'kv.put',
208
+ ...conditionalKv.KV_CONDITIONAL_KINDS,
164
209
  'grip.channel',
165
210
  'grip.hold',
166
211
  'grip.publish',
167
212
  'grip.broadcast',
168
213
  'assets.lookup',
169
- 'jwt.verify'
214
+ 'jwt.verify',
215
+ 'jwt.sign',
216
+ 's3.head',
217
+ 's3.getText',
218
+ 's3.putText',
219
+ 's3.getBody'
170
220
  ]);
171
221
 
172
222
  const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
223
+ 'time.now',
224
+ 'crypto.digestText',
173
225
  'config.get',
174
226
  'secret.get',
175
227
  'kv.get',
176
228
  'kv.put',
229
+ ...conditionalKv.KV_CONDITIONAL_KINDS,
177
230
  'assets.lookup',
178
231
  'grip.channel',
179
232
  'grip.hold',
180
233
  'grip.publish',
181
234
  'grip.broadcast',
182
- 'jwt.verify'
235
+ 'jwt.verify',
236
+ 'jwt.sign',
237
+ 's3.head',
238
+ 's3.getText',
239
+ 's3.putText',
240
+ 's3.getBody'
183
241
  ]);
184
242
 
185
243
  class FastlyNativePlatformCapabilitiesError extends Error {
@@ -241,45 +299,38 @@ function packageFacts(_plan, options = {}) {
241
299
  });
242
300
  }
243
301
 
302
+ function jwtVerificationAlgorithm(effect) {
303
+ return effect.resource.keyType === 'secret' ? 'HS256' : String(effect.resource.keyArtifactId).startsWith('pulse.jwt.rs256-key.') ? 'RS256' : 'ES256';
304
+ }
305
+
244
306
  function selectedJwtCrypto(plan) {
245
- const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
246
- if (!hasJwt) return undefined;
247
- const algorithms = plan.crypto && Array.isArray(plan.crypto.algorithms)
248
- ? plan.crypto.algorithms
249
- : [];
250
- if (algorithms.length !== 1) {
251
- fail(
252
- 'Fastly Native JWT requires one exact crypto realization.',
253
- 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID',
254
- { algorithms, automaticFallback: false }
255
- );
256
- }
257
- const selected = algorithms[0];
258
- const hs256 = selected.algorithm === 'HS256'
259
- && selected.realization === 'guest-source:pulse-hmac-as'
260
- && selected.implementation === 'pulse-hmac-as.v1'
261
- && selected.kind === 'guest-source'
262
- && selected.targetImplemented === true;
263
- const es256 = selected.algorithm === 'ES256'
264
- && selected.realization === 'guest-linked:pulse-es256-rustcrypto-p256'
265
- && selected.implementation === 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1'
266
- && selected.kind === 'guest-linked'
267
- && selected.targetImplemented === true;
268
- if ((!hs256 && !es256) || selected.automaticFallback !== false) {
269
- fail(
270
- 'Fastly Native JWT selected an unavailable or non-exact crypto realization.',
271
- 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID',
272
- { selected, automaticFallback: false }
273
- );
307
+ const effects = plan.effects || [];
308
+ const signing = effects.filter(effect => effect.kind === 'jwt.sign');
309
+ const verifies = effects.filter(effect => effect.kind === 'jwt.verify');
310
+ const required = new Set([
311
+ ...signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm') === 'HS256' ? 'HMAC-SHA256' : literalObjectField(inputExpression(effect, 'payload'), 'algorithm')),
312
+ ...verifies.map(effect => jwtVerificationAlgorithm(effect))
313
+ ]);
314
+ if (!required.size) return undefined;
315
+ const definitions = {
316
+ 'HMAC-SHA256': ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
317
+ HS256: ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
318
+ RS256: ['guest-linked:pulse-rs256-bearssl-i31', 'bearssl.0.6.rsa-i31.sha256.v1', 'guest-linked'],
319
+ ES256: ['guest-linked:pulse-es256-rustcrypto-p256', 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1', 'guest-linked']
320
+ };
321
+ for (const algorithm of required) {
322
+ const selected = plan.crypto?.algorithms.find(entry => entry.algorithm === algorithm);
323
+ const expected = definitions[algorithm];
324
+ if (!expected || !selected || selected.realization !== expected[0] || selected.implementation !== expected[1]
325
+ || selected.kind !== expected[2] || selected.targetImplemented !== true || selected.automaticFallback !== false) {
326
+ fail('JWT requires its explicitly selected Crypto realization.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
327
+ }
274
328
  }
275
- return Object.freeze({
276
- algorithm: selected.algorithm,
277
- realization: selected.realization,
278
- implementation: selected.implementation,
279
- kind: selected.kind,
280
- guestUnitRequired: es256,
281
- automaticFallback: false
282
- });
329
+ const verificationAlgorithms = [...new Set(verifies.map(effect => jwtVerificationAlgorithm(effect)))];
330
+ if (verificationAlgorithms.length > 1) fail('Fastly Native JWT verification requires one exact algorithm.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
331
+ const algorithm = verificationAlgorithms[0] || (required.has('RS256') ? 'RS256' : required.has('ES256') ? 'ES256' : 'HMAC-SHA256');
332
+ return Object.freeze({ ...plan.crypto.algorithms.find(entry => entry.algorithm === algorithm),
333
+ guestUnitRequired: required.has('ES256') || required.has('RS256'), signingAlgorithms: signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm')) });
283
334
  }
284
335
 
285
336
  function decodeP256Coordinate(value, artifactId, field) {
@@ -301,6 +352,26 @@ function decodeP256Coordinate(value, artifactId, field) {
301
352
  return bytes;
302
353
  }
303
354
 
355
+ function normalizedRs256Jwk(value, artifactId) {
356
+ const invalid = () => fail('Invalid bounded RSA public key artifact.', 'PULSE_FASTLY_NATIVE_JWT_KEY_ARTIFACT_INVALID', { artifactId });
357
+ if (!value || typeof value !== 'object' || Array.isArray(value)
358
+ || Object.keys(value).some(name => !['kty', 'n', 'e', 'alg', 'use', 'key_ops', 'kid'].includes(name))
359
+ || value.kty !== 'RSA' || value.alg !== undefined && value.alg !== 'RS256'
360
+ || value.use !== undefined && value.use !== 'sig'
361
+ || value.key_ops !== undefined && (!Array.isArray(value.key_ops) || value.key_ops.length !== 1 || value.key_ops[0] !== 'verify')
362
+ || value.kid !== undefined && (typeof value.kid !== 'string' || !value.kid.length || value.kid.includes('\0') || Buffer.byteLength(value.kid) > 256)) invalid();
363
+ const decode = (text, maximum) => {
364
+ if (typeof text !== 'string' || text.length > Math.ceil(maximum * 4 / 3) || !/^[A-Za-z0-9_-]+$/.test(text)) invalid();
365
+ const bytes = Buffer.from(text, 'base64url');
366
+ if (!bytes.length || bytes.length > maximum || bytes[0] === 0 || bytes.toString('base64url') !== text) invalid();
367
+ return bytes;
368
+ };
369
+ const n = decode(value.n, 512), e = decode(value.e, 4), k = n.length;
370
+ if (![256, 384, 512].includes(k) || n[0] < 128 || !(n[k-1] & 1) || e.readUIntBE(0, e.length) < 3 || !(e.at(-1) & 1)) invalid();
371
+ const bytes = Buffer.alloc(8+k); bytes.writeUInt32LE(k); n.copy(bytes, 4); e.copy(bytes, 8+k-e.length);
372
+ return Object.freeze({ kid: value.kid, bytes });
373
+ }
374
+
304
375
  function normalizedEs256Jwk(value, artifactId) {
305
376
  if (
306
377
  !value
@@ -347,13 +418,14 @@ function resolveEs256KeyArtifacts(plan, facts) {
347
418
  }
348
419
  const artifactId = effect.resource.keyArtifactId;
349
420
  const artifact = artifacts.get(artifactId);
421
+ const algorithm = jwtVerificationAlgorithm(effect).toLowerCase();
350
422
  if (
351
423
  !artifact
352
- || artifact.version !== 'pulse.jwt-es256-key-artifact.v1'
424
+ || artifact.version !== `pulse.jwt-${algorithm}-key-artifact.v1`
353
425
  || artifact.contractId !== 'pulse.jwt'
354
426
  || artifact.package !== '@pulse-compute/jwt'
355
- || artifact.kind !== 'jwt-es256-static-public-key'
356
- || artifact.mediaType !== 'application/vnd.pulse.jwt-es256-key+json'
427
+ || artifact.kind !== `jwt-${algorithm}-static-public-key`
428
+ || artifact.mediaType !== `application/vnd.pulse.jwt-${algorithm}-key+json`
357
429
  || artifact.id !== artifactId
358
430
  || artifact.materialHash !== sha256(stableStringify(artifact.data))
359
431
  || !artifact.data
@@ -379,7 +451,7 @@ function resolveEs256KeyArtifacts(plan, facts) {
379
451
  { effectId: effect.id, artifactId, keyCount: keys && keys.length }
380
452
  );
381
453
  }
382
- const normalized = keys.map((key) => normalizedEs256Jwk(key, artifactId));
454
+ const normalized = keys.map((key) => algorithm === 'rs256' ? normalizedRs256Jwk(key, artifactId) : normalizedEs256Jwk(key, artifactId));
383
455
  const kids = normalized.map((key) => key.kid).filter((kid) => kid !== undefined);
384
456
  if (new Set(kids).size !== kids.length) {
385
457
  fail(
@@ -455,8 +527,71 @@ function es256KeyArtifactSource(records) {
455
527
  return lines.join('\n');
456
528
  }
457
529
 
530
+ function jwtSigningCryptoCompositionSource(selected) {
531
+ const algorithms = selected?.signingAlgorithms || [];
532
+ const hs = algorithms.includes('HS256');
533
+ const es = algorithms.includes('ES256');
534
+ const rs = algorithms.includes('RS256');
535
+ return `function __pulse_fastly_jwt_crypto_sign(algorithm: string, key: Uint8Array, data: Uint8Array, output: Uint8Array): i32 {
536
+ ${hs ? `if (algorithm == "HS256") return pulse_crypto_hmac_sha256_bytes_v1(key.dataStart, key.length, data.dataStart, data.length, output.dataStart, output.length)` : ''}
537
+ ${rs ? `if (algorithm == "RS256") return __pulse_fastly_rs256(true, key, data, output)` : ''}
538
+ ${es ? `if (algorithm == "ES256") {
539
+ if (key.length != 96 || output.length != 64 || data.length > 16340) return -2
540
+ const pointer: usize = 524288
541
+ memory.fill(pointer, 0, 16640)
542
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
543
+ store<u32>(pointer + 12, (224 + data.length + 15) & ~15); store<u32>(pointer + 16, 1)
544
+ store<u32>(pointer + 24, 224); store<u32>(pointer + 28, data.length)
545
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, 96)
546
+ store<u32>(pointer + 40, 160); store<u32>(pointer + 44, 64)
547
+ memory.copy(pointer + 64, key.dataStart, 96); memory.copy(pointer + 224, data.dataStart, data.length)
548
+ const status = __pulse_crypto_es256_sign_anchor(i32(pointer), 16640)
549
+ if (status == 1) memory.copy(output.dataStart, pointer + 160, 64)
550
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
551
+ return status
552
+ }` : ''}
553
+ return -3
554
+ }`;
555
+ }
556
+
458
557
  function jwtCryptoCompositionSource(selected) {
459
- if (!selected) {
558
+ const rsa = selected && (selected.algorithm === 'RS256' || selected.signingAlgorithms?.includes('RS256'));
559
+ const helper = rsa ? rs256CompositionSource() : '';
560
+ return helper + jwtVerificationCompositionSource(selected);
561
+ }
562
+
563
+ function rs256CompositionSource() {
564
+ return `function __pulse_fastly_rs256(signing: bool, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
565
+ if (key.length < 8 || data.length > 12288) return -2
566
+ const k = load<u32>(key.dataStart)
567
+ if ((k != 256 && k != 384 && k != 512) || signature.length != i32(k) || key.length != i32(signing ? 8 + k * 9 / 2 : 8 + k)) return -2
568
+ const pointer: usize = 524288
569
+ const output = (64 + key.length + 15) & ~15
570
+ const input = output + signature.length
571
+ memory.fill(pointer, 0, 16640)
572
+ store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
573
+ store<u32>(pointer + 12, (input + data.length + 15) & ~15); store<u32>(pointer + 16, 2)
574
+ store<u32>(pointer + 24, input); store<u32>(pointer + 28, data.length)
575
+ store<u32>(pointer + 32, 64); store<u32>(pointer + 36, key.length)
576
+ store<u32>(pointer + 40, output); store<u32>(pointer + 44, k)
577
+ memory.copy(pointer + 64, key.dataStart, key.length); memory.copy(pointer + input, data.dataStart, data.length)
578
+ if (!signing) memory.copy(pointer + output, signature.dataStart, signature.length)
579
+ const status = signing ? __pulse_crypto_rs256_sign_anchor(i32(pointer), 16640) : __pulse_crypto_rs256_verify_anchor(i32(pointer), 16640)
580
+ if (signing && status == 1) memory.copy(signature.dataStart, pointer + output, signature.length)
581
+ memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
582
+ return status
583
+ }\n`;
584
+ }
585
+
586
+ function jwtVerificationCompositionSource(selected) {
587
+ if (selected?.algorithm === 'RS256') return `
588
+ function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { __pulse_fastly_jwt_record_signing_input(input.byteLength, "") }
589
+ function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
590
+ if (algorithm != "RS256") return -2
591
+ return __pulse_fastly_rs256(false, key, data, signature)
592
+ }`;
593
+
594
+ if (!selected || selected.algorithm === 'HMAC-SHA256') {
460
595
  return [
461
596
  'function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { void input }',
462
597
  '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 }'
@@ -541,6 +676,7 @@ function fastlyGuestLinkedStartWrapperWat() {
541
676
  }
542
677
 
543
678
  function validatePlanBoundary(plan, options = {}) {
679
+ require('./native-body-forwarding.js').validate(plan, options);
544
680
  if (!plan || typeof plan !== 'object') throw new TypeError('Fastly native platform capability realization requires a canonical native plan.');
545
681
  if (plan.version !== CANONICAL_NATIVE_PLAN_VERSION) {
546
682
  fail(`Fastly native platform capability realization requires ${CANONICAL_NATIVE_PLAN_VERSION}.`, 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_PLAN_VERSION_UNSUPPORTED', { version: plan.version });
@@ -569,13 +705,16 @@ function validatePlanBoundary(plan, options = {}) {
569
705
  && effect.kind === 'assets.lookup';
570
706
  const jwt = effect.package === '@pulse-compute/jwt'
571
707
  && effect.contractId === 'pulse.jwt'
572
- && effect.kind === 'jwt.verify'
573
- && effect.operation === 'verify'
574
- && effect.capability === 'jwt.verify';
575
- return !grip && !assets && !jwt;
708
+ && ['verify', 'sign'].includes(effect.operation)
709
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
710
+ const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
711
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
712
+ const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
713
+ && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
714
+ return !grip && !assets && !jwt && !s3 && !digest;
576
715
  });
577
716
  if (unsupportedPackages.length > 0) {
578
- fail('Fastly native realization only accepts the trusted first-party Assets, GRIP, and JWT package contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', {
717
+ 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', {
579
718
  effects: unsupportedPackages.map((entry) => ({ id: entry.id, kind: entry.kind, package: entry.package, contractId: entry.contractId }))
580
719
  });
581
720
  }
@@ -589,15 +728,18 @@ function validatePlanBoundary(plan, options = {}) {
589
728
  && effect.kind === 'assets.lookup';
590
729
  const jwt = effect.package === '@pulse-compute/jwt'
591
730
  && effect.contractId === 'pulse.jwt'
592
- && effect.kind === 'jwt.verify'
593
- && effect.operation === 'verify'
594
- && effect.capability === 'jwt.verify';
595
- return !grip && !assets && !jwt;
731
+ && ['verify', 'sign'].includes(effect.operation)
732
+ && effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
733
+ const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
734
+ && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
735
+ const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
736
+ && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
737
+ return !grip && !assets && !jwt && !s3 && !digest;
596
738
  });
597
739
  if (unsupportedDeclarations.length > 0) {
598
- fail('Fastly native package realization is restricted to the Assets, GRIP, and JWT contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', { effects: unsupportedDeclarations });
740
+ 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 });
599
741
  }
600
- for (const effect of effects.filter((entry) => entry.kind === 'jwt.verify')) {
742
+ for (const effect of effects.filter((entry) => ['jwt.verify', 'jwt.sign'].includes(entry.kind))) {
601
743
  const resource = effect.resource;
602
744
  const knownShape = resource
603
745
  && !Object.keys(resource)
@@ -612,7 +754,7 @@ function validatePlanBoundary(plan, options = {}) {
612
754
  && typeof resource.keyArtifactId === 'string'
613
755
  && resource.keyArtifactId.length > 0
614
756
  && resource.secretBinding === null;
615
- if (!namedSecret && !staticPublicKey) {
757
+ if (!namedSecret && (!staticPublicKey || effect.kind === 'jwt.sign')) {
616
758
  fail('Fastly Native JWT requires one exact named-secret or bounded static public-key descriptor.', 'PULSE_FASTLY_NATIVE_JWT_KEY_DESCRIPTOR_INVALID', {
617
759
  effectId: effect.id,
618
760
  resource,
@@ -678,8 +820,8 @@ function resolveCapabilityBindings(plan, options = {}) {
678
820
  const effects = plan.effects || [];
679
821
  const hasConfig = effects.some((effect) => effect.kind === 'config.get');
680
822
  const hasSecret = effects.some((effect) => (
681
- effect.kind === 'secret.get'
682
- || (effect.kind === 'jwt.verify' && effect.resource && effect.resource.keyType === 'secret')
823
+ effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)
824
+ || (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource && effect.resource.keyType === 'secret')
683
825
  ));
684
826
  const hasGripHold = effects.some((effect) => effect.kind === 'grip.hold');
685
827
  const hasGripBroadcast = effects.some((effect) => effect.kind === 'grip.broadcast');
@@ -703,8 +845,9 @@ function resolveCapabilityBindings(plan, options = {}) {
703
845
 
704
846
  const kv = [];
705
847
  for (const [index, effect] of effects.entries()) {
706
- if (effect.kind !== 'kv.get' && effect.kind !== 'kv.put' && effect.kind !== 'assets.lookup') continue;
848
+ if (effect.kind !== 'kv.get' && effect.kind !== 'kv.put' && !conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) && effect.kind !== 'assets.lookup') continue;
707
849
  const packagePayload = inputExpression(effect, 'payload');
850
+ if (effect.kind === 'assets.lookup' && literalObjectField(packagePayload, 'embeddedId')) continue;
708
851
  const logical = effect.resource && effect.resource.kind === 'literal'
709
852
  && effect.kind !== 'assets.lookup'
710
853
  ? String(effect.resource.value)
@@ -715,6 +858,7 @@ function resolveCapabilityBindings(plan, options = {}) {
715
858
  resource: effect.resource
716
859
  });
717
860
  }
861
+ if (conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) && Buffer.byteLength(logical, 'utf8') > conditionalKv.KV_CONDITIONAL_LIMITS.namespaceBytes) fail('Conditional KV namespace exceeds its portable bound.', 'PULSE_FASTLY_NATIVE_KV_NAMESPACE_DYNAMIC');
718
862
  const physical = stringBinding(
719
863
  kvMap[logical],
720
864
  `Fastly KV binding for ${logical}`,
@@ -770,11 +914,11 @@ function resolveCapabilityBindings(plan, options = {}) {
770
914
  backend = byEffect[effect.id]
771
915
  || (origin && byOrigin[origin])
772
916
  || (effect.resource && effect.resource.value && byOrigin[effect.resource.value]);
773
- if ((typeof backend !== 'string' || backend.trim() === '') && !origin) {
917
+ if ((typeof backend !== 'string' || backend.trim() === '') && !origin && source.dynamicBackends !== true) {
774
918
  const configured = [...new Set(Object.values(byOrigin).filter((value) => typeof value === 'string' && value.trim()).map((value) => value.trim()))];
775
919
  if (configured.length === 1) backend = configured[0];
776
920
  }
777
- if (typeof backend !== 'string' || backend.trim() === '') {
921
+ if ((typeof backend !== 'string' || backend.trim() === '') && !(source.dynamicBackends === true && !(plan.capabilities || []).includes('request.body.forward'))) {
778
922
  fail(`Fastly native effect ${effect.id} has no static backend binding.`, 'PULSE_FASTLY_BACKEND_REQUIRED', {
779
923
  effectId: effect.id,
780
924
  effectIndex: index,
@@ -782,7 +926,7 @@ function resolveCapabilityBindings(plan, options = {}) {
782
926
  hint: `Provide bindings.backends[${JSON.stringify(origin || '<origin>')}] or an explicit effect backend.`
783
927
  });
784
928
  }
785
- backend = backend.trim();
929
+ backend = typeof backend === 'string' ? backend.trim() : '';
786
930
  }
787
931
  const binding = Object.freeze({
788
932
  index,
@@ -791,6 +935,7 @@ function resolveCapabilityBindings(plan, options = {}) {
791
935
  origin,
792
936
  url,
793
937
  backend,
938
+ ...(!backend && effect.kind === 'fetch' ? { dynamic: true } : {}),
794
939
  ...(effect.kind === 'grip.broadcast' && gripSecretRef ? { authSecretRef: gripSecretRef } : {})
795
940
  });
796
941
  backends.push(binding);
@@ -799,8 +944,12 @@ function resolveCapabilityBindings(plan, options = {}) {
799
944
  }
800
945
 
801
946
  return Object.freeze({
947
+ maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs),
948
+ bodyForwarding: require('../toolchain/body-forwarding.js').normalizeBodyForwarding(options.bodyForwarding, options.maxDurationMs),
949
+ ...(source.logEndpoint === undefined ? {} : { logEndpoint: require('../toolchain/logging.js').normalizeFastlyLogEndpoint(source.logEndpoint) }),
802
950
  configStore,
803
951
  secretStore,
952
+ 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),
804
953
  kv: Object.freeze(kv),
805
954
  fetch: Object.freeze(fetch),
806
955
  backends: Object.freeze(backends),
@@ -834,7 +983,7 @@ function planRequiresFetchBodyRead(plan) {
834
983
  if (Array.isArray(value)) return value.some(visit);
835
984
  return Object.values(value).some(visit);
836
985
  }
837
- return visit(plan.entry);
986
+ return visit(plan.entry) || visit(plan.handlers) || visit(plan.stages) || visit(plan.helpers);
838
987
  }
839
988
 
840
989
  function requiredImportsForPlan(plan, bindings) {
@@ -849,33 +998,43 @@ function requiredImportsForPlan(plan, bindings) {
849
998
  'fastly_http_body:new',
850
999
  'fastly_http_body:write'
851
1000
  ]);
1001
+ if (bindings && bindings.maxDurationMs !== undefined) { keys.add('wasi_snapshot_preview1:clock_time_get'); keys.add('fastly_async_io:select'); }
852
1002
  const kinds = new Set((plan.effects || []).map((effect) => effect.kind));
853
1003
  if (kinds.has('fetch') || kinds.has('grip.publish') || kinds.has('grip.broadcast')) {
854
1004
  for (const key of [
855
1005
  'fastly_http_req:new', 'fastly_http_req:method_set', 'fastly_http_req:uri_set',
856
- 'fastly_http_req:header_insert', 'fastly_http_req:send_async', 'fastly_http_req:pending_req_wait'
1006
+ 'fastly_http_req:header_insert', 'fastly_http_req:send_async', 'fastly_http_req:pending_req_wait_v2'
857
1007
  ]) keys.add(key);
858
1008
  }
1009
+ if (bindings && dynamicBackends.active(bindings)) keys.add('fastly_http_req:register_dynamic_backend');
1010
+ if (kinds.has('fetch')) for (const key of ['wasi_snapshot_preview1:clock_time_get', 'fastly_async_io:select', 'fastly_http_req:pending_req_poll_v2', 'fastly_http_resp:close', 'fastly_http_body:close']) keys.add(key);
859
1011
  if (planRequiresFetchBodyRead(plan)) keys.add('fastly_http_body:read');
860
1012
  if (kinds.has('config.get')) for (const key of ['fastly_config_store:open', 'fastly_config_store:get']) keys.add(key);
861
1013
  const gripAuthenticationRequired = kinds.has('grip.broadcast')
862
1014
  && (!bindings || Boolean(bindings.grip && bindings.grip.authentication));
863
1015
  const jwtSecretRequired = (plan.effects || []).some((effect) => (
864
- effect.kind === 'jwt.verify'
1016
+ ['jwt.verify', 'jwt.sign'].includes(effect.kind)
865
1017
  && effect.resource
866
1018
  && effect.resource.keyType === 'secret'
867
1019
  ));
868
1020
  if (kinds.has('secret.get') || jwtSecretRequired || gripAuthenticationRequired) {
869
1021
  for (const key of ['fastly_secret_store:open', 'fastly_secret_store:get', 'fastly_secret_store:plaintext']) keys.add(key);
870
1022
  }
871
- if (kinds.has('jwt.verify')) keys.add('wasi_snapshot_preview1:clock_time_get');
1023
+ for (const kind of conditionalKv.KV_CONDITIONAL_KINDS) if (kinds.has(kind)) for (const key of conditionalKv.kvImports(kind)) keys.add(key);
1024
+ if (kinds.has('jwt.verify') || kinds.has('jwt.sign') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
872
1025
  if (kinds.has('kv.get') || kinds.has('kv.put') || kinds.has('assets.lookup')) keys.add('fastly_kv_store:open');
873
1026
  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);
874
1027
  if (kinds.has('kv.put')) for (const key of ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait']) keys.add(key);
875
1028
  if (kinds.has('grip.hold')) keys.add('fastly_http_resp:header_append');
876
- if (plan.logging && plan.logging.enabledStatements > 0) {
877
- keys.add('fastly_log:endpoint_get');
878
- keys.add('fastly_log:write');
1029
+ if ((plan.logging && plan.logging.enabledStatements > 0) || ['fetch', 'grip.publish', 'grip.broadcast', 's3.head', 's3.getText', 's3.putText', 's3.getBody'].some(kind => kinds.has(kind))) {
1030
+ if (bindings?.logEndpoint === undefined) keys.add('wasi_snapshot_preview1:fd_write');
1031
+ else { keys.add('fastly_log:endpoint_get'); keys.add('fastly_log:write'); }
1032
+ }
1033
+ 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);
1034
+ if (require('./native-body-forwarding.js').enabled(plan)) {
1035
+ keys.delete('fastly_http_req:send_async'); keys.delete('fastly_http_req:pending_req_wait_v2');
1036
+ for (const key of require('./native-body-forwarding.js').IMPORTS) keys.add(key);
1037
+ keys.add('fastly_http_body:read');
879
1038
  }
880
1039
  return Object.freeze([...keys].sort());
881
1040
  }
@@ -887,15 +1046,40 @@ function stripPulseHostImports(source) {
887
1046
  .join('\n');
888
1047
  }
889
1048
 
1049
+ function schemaTextEncodeIds(plan) {
1050
+ // Keep the PS3 accounting/failure boundary and all unproved codec families.
1051
+ if (nativeValueBudget.hasBoundedReadLoop(plan)) return [];
1052
+ function closedText(node, depth = 0) {
1053
+ if (depth > 16) return false;
1054
+ if (node.kind === 'string' || node.kind === 'boolean') return true;
1055
+ if (node.kind === 'nullable') return closedText(node.value, depth);
1056
+ if (node.kind === 'array') return closedText(node.element, depth + 1);
1057
+ return node.kind === 'object' && !node.additionalProperties
1058
+ && node.fields.every(field => closedText(field.value, depth + 1));
1059
+ }
1060
+ return ((plan.schemas && plan.schemas.registry && plan.schemas.registry.schemas) || [])
1061
+ .filter(schema => !schema.jsonLimits && schema.root.kind === 'object'
1062
+ && schemaNeedsValueProjection(schema.root) && closedText(schema.root))
1063
+ .map(schema => schema.id);
1064
+ }
1065
+
890
1066
  function generateSchemaRuntime(plan) {
891
1067
  const registry = plan.schemas && plan.schemas.registry ? plan.schemas.registry : { schemas: [], maxBytes: FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES, contentTypePolicy: 'accept-json-or-missing' };
892
1068
  const schemas = registry.schemas || [];
1069
+ const textEncodeIds = new Set(schemaTextEncodeIds(plan));
893
1070
  const nodeLines = [];
1071
+ const scalarProjectors = new Map();
1072
+ const flatObjectProjectors = new Map();
1073
+ const scalarKinds = ['string', 'boolean', 'i32', 'u32', 'f64', 'string-enum'];
894
1074
  let nodeIndex = 0;
895
1075
 
896
1076
  function emitNode(schemaIndex, node) {
897
1077
  const currentIndex = nodeIndex++;
898
1078
  const functionName = `__pulse_fastly_schema_${schemaIndex}_${currentIndex}`;
1079
+ const flatObject = node.kind === 'object' && !node.additionalProperties
1080
+ && node.fields.every(field => scalarKinds.includes(field.value.kind));
1081
+ // Locals belong to this function, so equivalent flat bodies need no node ID.
1082
+ const localIndex = flatObject ? 'flat' : currentIndex;
899
1083
  const child = node.kind === 'nullable'
900
1084
  ? emitNode(schemaIndex, node.value)
901
1085
  : node.kind === 'array'
@@ -904,30 +1088,43 @@ function generateSchemaRuntime(plan) {
904
1088
  const fields = node.kind === 'object'
905
1089
  ? node.fields.map((field) => Object.freeze({ field, apply: emitNode(schemaIndex, field.value) }))
906
1090
  : [];
907
- const body = [`function ${functionName}(valueHandle: i32): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
1091
+ const body = [`function ${functionName}(valueHandle: i32, checkDuplicates: bool): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
908
1092
  if (node.kind === 'nullable') {
909
1093
  body.push(' if (input.kind == PULSE_VALUE_NULL) return valueHandle');
910
- body.push(` return ${child}(valueHandle)`);
1094
+ body.push(` return ${child}(valueHandle, checkDuplicates)`);
911
1095
  } else if (node.kind === 'array') {
912
1096
  body.push(' if (input.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
913
1097
  body.push(' const output = host_value_array()');
914
1098
  body.push(' for (let index = 0; index < input.values.length; index += 1) {');
915
- body.push(` const item = ${child}(unchecked(input.values[index]))`);
1099
+ body.push(` const item = ${child}(unchecked(input.values[index]), checkDuplicates)`);
916
1100
  body.push(' if (item <= 0) return 0');
917
1101
  body.push(' host_value_array_push(output, item)');
918
1102
  body.push(' }');
919
1103
  body.push(' return output');
1104
+ } else if (node.kind === 'json-value' || node.kind === 'json-object') {
1105
+ body.push(...nestedJsonProjectionLines(node));
1106
+ } else if (node.kind === 'scalar-record') {
1107
+ body.push(...scalarRecordProjectionLines(node));
920
1108
  } else if (node.kind === 'object') {
921
1109
  body.push(' if (input.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 50, -1); return 0 }');
1110
+ if (node.additionalProperties) body.push(' if (checkDuplicates && input.duplicateJsonKeys) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }');
922
1111
  body.push(' const output = host_value_object()');
923
1112
  fields.forEach(({ field, apply }, fieldIndex) => {
924
- body.push(` const key_${currentIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
925
- body.push(` const field_${currentIndex}_${fieldIndex} = host_value_property(valueHandle, key_${currentIndex}_${fieldIndex})`);
926
- body.push(` if (__pulse_fastly_value(field_${currentIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
927
- body.push(` const projected_${currentIndex}_${fieldIndex} = ${apply}(field_${currentIndex}_${fieldIndex})`);
928
- body.push(` if (projected_${currentIndex}_${fieldIndex} <= 0) return 0`);
929
- body.push(` host_value_object_set(output, key_${currentIndex}_${fieldIndex}, projected_${currentIndex}_${fieldIndex})`);
1113
+ body.push(` const key_${localIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
1114
+ if (!field.required) body.push(` if (__pulse_fastly_find(input, ${quote(field.name)}) >= 0) {`);
1115
+ body.push(` const field_${localIndex}_${fieldIndex} = host_value_property(valueHandle, key_${localIndex}_${fieldIndex})`);
1116
+ body.push(` if (__pulse_fastly_value(field_${localIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
1117
+ body.push(` const projected_${localIndex}_${fieldIndex} = ${apply}(field_${localIndex}_${fieldIndex}, checkDuplicates)`);
1118
+ body.push(` if (projected_${localIndex}_${fieldIndex} <= 0) return 0`);
1119
+ body.push(` host_value_object_set(output, key_${localIndex}_${fieldIndex}, projected_${localIndex}_${fieldIndex})`);
1120
+ if (!field.required) body.push(' }');
930
1121
  });
1122
+ if (node.additionalProperties) {
1123
+ body.push(' for (let index = 0; index < input.keys.length; index++) {', ' const key = unchecked(input.keys[index])');
1124
+ if (node.fields.length) body.push(` if (${node.fields.map(field => `key == ${quote(field.name)}`).join(' || ')}) continue`);
1125
+ body.push(' const item = __pulse_fastly_copy_json(unchecked(input.values[index]), checkDuplicates)',
1126
+ ' if (item <= 0) return 0', ' host_value_object_set(output, __pulse_fastly_string_value(key), item)', ' }');
1127
+ }
931
1128
  body.push(' return output');
932
1129
  } else if (node.kind === 'string') {
933
1130
  body.push(' if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
@@ -952,6 +1149,32 @@ function generateSchemaRuntime(plan) {
952
1149
  throw new TypeError(`Unsupported Fastly native schema node ${String(node.kind)}.`);
953
1150
  }
954
1151
  body.push('}', '');
1152
+ // Only scalar validators preserve the original handle without allocating or
1153
+ // calling child projectors. Compare the exact emitted body, including enum
1154
+ // values/order and error stages.
1155
+ if (scalarKinds.includes(node.kind)) {
1156
+ const key = body.slice(1).join('\n');
1157
+ const existing = scalarProjectors.get(key);
1158
+ if (existing) return existing;
1159
+ const sharedName = `__pulse_fastly_schema_scalar_${scalarProjectors.size}`;
1160
+ scalarProjectors.set(key, sharedName);
1161
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1162
+ nodeLines.push(...body);
1163
+ return sharedName;
1164
+ }
1165
+ // Share code only after child scalar identities are resolved. Exact bodies
1166
+ // retain field order, presence checks, validation and errors. Each call still
1167
+ // allocates its own output; open/nested/nullable/array bodies remain separate.
1168
+ if (flatObject) {
1169
+ const key = body.slice(1).join('\n');
1170
+ const existing = flatObjectProjectors.get(key);
1171
+ if (existing) return existing;
1172
+ const sharedName = `__pulse_fastly_schema_flat_object_${flatObjectProjectors.size}`;
1173
+ flatObjectProjectors.set(key, sharedName);
1174
+ body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
1175
+ nodeLines.push(...body);
1176
+ return sharedName;
1177
+ }
955
1178
  nodeLines.push(...body);
956
1179
  return functionName;
957
1180
  }
@@ -961,17 +1184,22 @@ function generateSchemaRuntime(plan) {
961
1184
  `const __PULSE_SCHEMA_MAX_BYTES: i32 = ${Number(registry.maxBytes || FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES)}`,
962
1185
  `const __PULSE_SCHEMA_REQUIRE_JSON: bool = ${registry.contentTypePolicy === 'require-json' ? 'true' : 'false'}`,
963
1186
  '',
1187
+ schemaJsonAdmissionSource(plan),
1188
+ schemaFetchJsonSource(),
964
1189
  ...nodeLines,
965
- 'function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool): i32 {',
1190
+ `function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool${textEncodeIds.size ? ', textResult: bool = false' : ''}): i32 {`,
966
1191
  ' if (schemaId.length == 0) return valueHandle'
967
1192
  ];
968
1193
  for (const [schemaIndex, schema] of schemas.entries()) {
969
1194
  lines.push(`${schemaIndex === 0 ? ' if' : ' else if'} (schemaId == ${quote(schema.id)}) {`);
970
- lines.push(` const projected = ${roots[schemaIndex]}(valueHandle)`);
1195
+ 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 }`);
1196
+ lines.push(` const projected = ${roots[schemaIndex]}(valueHandle, !encode)`);
971
1197
  lines.push(' if (projected <= 0) return 0');
972
1198
  lines.push(' const input = __pulse_fastly_json(projected, 0)');
973
1199
  lines.push(` const normalized = encode ? __pulse_schema_encode_${schemaIndex}(input) : __pulse_schema_decode_${schemaIndex}(input)`);
974
- lines.push(' return __pulse_fastly_parse_json(normalized)');
1200
+ if (textEncodeIds.has(schema.id)) lines.push(' if (textResult && encode) return __pulse_fastly_string_value(normalized)');
1201
+ if (schema.jsonLimits) lines.push(' const output = __pulse_fastly_parse_json(normalized)', ' if (output > 0) __pulse_fastly_deep_freeze(output)', ' return output');
1202
+ else lines.push(' return __pulse_fastly_parse_json(normalized)');
975
1203
  lines.push(' }');
976
1204
  }
977
1205
  lines.push(' __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1)');
@@ -994,7 +1222,7 @@ function effectResultSource(plan, bindings) {
994
1222
  lines.push(`function __pulse_fastly_secret_store(effectIndex: i32): string { return ${quote(bindings.secretStore)} }`, '');
995
1223
  lines.push('function __pulse_fastly_jwt_secret_binding(effectIndex: i32): string {', ' switch (effectIndex) {');
996
1224
  for (const [index, effect] of (plan.effects || []).entries()) {
997
- if (effect.kind === 'jwt.verify' && effect.resource.keyType === 'secret') {
1225
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource.keyType === 'secret') {
998
1226
  lines.push(` case ${index}: return ${quote(effect.resource.secretBinding)}`);
999
1227
  }
1000
1228
  }
@@ -1009,12 +1237,31 @@ function effectResultSource(plan, bindings) {
1009
1237
  for (const binding of bindings.grip.publish) lines.push(` case ${binding.index}: return ${quote(binding.authSecretRef || '')}`);
1010
1238
  lines.push(' default: return ""', ' }', '}', '');
1011
1239
 
1240
+ // A config lookup has the same ready-result handoff at every static site.
1241
+ // Keep the site indices in the switch; pass the active index through so the
1242
+ // pending slot and any state error still belong to the original invocation.
1243
+ const configSites = (plan.effects || []).flatMap((effect, index) => effect.kind === 'config.get' ? [index] : []);
1244
+ if (configSites.length > 1) {
1245
+ lines.push('function __pulse_fastly_resolve_config_get(effectIndex: i32): i32 {');
1246
+ lines.push(' const response = __pulse_fastly_wait_ready(effectIndex)');
1247
+ lines.push(' if (response <= 0) return 0');
1248
+ lines.push(' return response');
1249
+ lines.push('}', '');
1250
+ }
1251
+
1012
1252
  lines.push('function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {');
1013
1253
  lines.push(' switch (effectIndex) {');
1014
1254
  for (const [index, effect] of (plan.effects || []).entries()) {
1255
+ if (effect.kind === 'config.get' && configSites.length > 1) {
1256
+ if (index === configSites[0]) {
1257
+ for (const site of configSites) lines.push(` case ${site}:`);
1258
+ lines.push(' return __pulse_fastly_resolve_config_get(effectIndex)');
1259
+ }
1260
+ continue;
1261
+ }
1015
1262
  const result = effect.result || {};
1016
1263
  const decoder = result.decoder;
1017
- const waiter = effect.kind === 'fetch'
1264
+ 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'
1018
1265
  ? '__pulse_fastly_wait_fetch'
1019
1266
  : effect.kind === 'grip.publish'
1020
1267
  ? '__pulse_fastly_wait_grip_publish'
@@ -1050,12 +1297,20 @@ function effectResultSource(plan, bindings) {
1050
1297
 
1051
1298
  function effectDispatchSource(plan) {
1052
1299
  const handlerByKind = Object.freeze({
1300
+ 'time.now': '__pulse_fastly_time_begin',
1301
+ 'crypto.digestText': '__pulse_fastly_digest_begin',
1053
1302
  fetch: '__pulse_fastly_fetch_begin',
1054
1303
  'config.get': '__pulse_fastly_config_begin',
1055
1304
  'secret.get': '__pulse_fastly_secret_begin',
1056
1305
  'jwt.verify': '__pulse_fastly_jwt_begin',
1306
+ 'jwt.sign': '__pulse_fastly_jwt_sign_begin',
1307
+ 's3.head': '__pulse_fastly_s3_begin',
1308
+ 's3.getText': '__pulse_fastly_s3_begin',
1309
+ 's3.putText': '__pulse_fastly_s3_begin',
1310
+ 's3.getBody': '__pulse_fastly_s3_begin',
1057
1311
  'kv.get': '__pulse_fastly_kv_get_begin',
1058
1312
  'kv.put': '__pulse_fastly_kv_put_begin',
1313
+ ...Object.fromEntries(conditionalKv.KV_CONDITIONAL_KINDS.map(kind => [kind, '__pulse_fastly_kv_conditional_begin'])),
1059
1314
  'assets.lookup': '__pulse_fastly_assets_begin',
1060
1315
  'grip.channel': '__pulse_fastly_grip_channel_begin',
1061
1316
  'grip.hold': '__pulse_fastly_grip_hold_begin',
@@ -1065,6 +1320,7 @@ function effectDispatchSource(plan) {
1065
1320
  const kinds = [...new Set((plan.effects || []).map((effect) => effect.kind))];
1066
1321
  const lines = [
1067
1322
  'function host_effect_begin(effectIndex: i32, payload: i32): void {',
1323
+ ...(needsNativeValueFailureGuard(plan) ? [' if (__pulse_fastly_last_error != PULSE_ERROR_NONE) return'] : []),
1068
1324
  ' 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 }',
1069
1325
  ' const payloadValue = __pulse_fastly_value(payload)',
1070
1326
  ' if (payloadValue.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 47, effectIndex); return }',
@@ -1085,7 +1341,14 @@ function effectDispatchSource(plan) {
1085
1341
  }
1086
1342
 
1087
1343
  function fastlyRuntimeSource(plan, bindings, options = {}) {
1344
+ const textEncodeIds = schemaTextEncodeIds(plan);
1088
1345
  const effectCount = (plan.effects || []).length;
1346
+ const hasAssets = (plan.effects || []).some(effect => effect.kind === 'assets.lookup');
1347
+ 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 }
1348
+ 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) }` : '';
1349
+ 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" }
1350
+ 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])) }
1351
+ 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) }` : '';
1089
1352
  const jwtCrypto = options.jwtCrypto || selectedJwtCrypto(plan);
1090
1353
  const es256KeyRecords = options.es256KeyRecords || Object.freeze([]);
1091
1354
  const jwtReality = options.jwtReality && options.jwtReality.enabled === true
@@ -1143,19 +1406,24 @@ function __pulse_fastly_jwt_attach_evidence(handle: i32): void {
1143
1406
  const value = __pulse_fastly_value(handle)
1144
1407
  if (value.kind == PULSE_VALUE_RESPONSE) __pulse_fastly_jwt_evidence_headers(value)
1145
1408
  }
1409
+ `
1410
+ : String.raw`
1411
+ function __pulse_fastly_jwt_attach_evidence(handle: i32): void { void handle }
1412
+ `;
1413
+ // Keep the legacy hook name: body, fetch, header and deadline owners prepend
1414
+ // their specific responses here. Every remaining fatal path needs a response,
1415
+ // independently of whether JWT evidence was requested for this build.
1416
+ const terminalErrorSource = `
1146
1417
  function __pulse_fastly_jwt_send_error(): void {
1147
1418
  const output = new __PulseFastlyValue()
1148
1419
  output.kind = PULSE_VALUE_RESPONSE
1149
- output.status = 400
1150
- output.text = ""
1420
+ output.status = ${jwtReality ? '__pulse_fastly_jwt_error != 0 ? 400 : 500' : '500'}
1421
+ output.text = output.status == 400 ? "" : "Internal Server Error"
1151
1422
  output.headers.push(new __PulseFastlyHeader("content-type", "text/plain; charset=utf-8"))
1152
- __pulse_fastly_jwt_evidence_headers(output)
1423
+ ${jwtReality ? 'if (__pulse_fastly_jwt_error != 0) __pulse_fastly_jwt_evidence_headers(output)' : ''}
1424
+ // Do not clear the original failure or expose its detail in the response.
1153
1425
  __pulse_fastly_send_result(__pulse_fastly_put(output))
1154
1426
  }
1155
- `
1156
- : String.raw`
1157
- function __pulse_fastly_jwt_attach_evidence(handle: i32): void { void handle }
1158
- function __pulse_fastly_jwt_send_error(): void {}
1159
1427
  `;
1160
1428
  const gripAuthentication = bindings.grip && bindings.grip.authentication;
1161
1429
  const gripSecretSource = gripAuthentication
@@ -1164,7 +1432,7 @@ function __pulse_fastly_jwt_send_error(): void {}
1164
1432
  const gripAuthorizationSource = gripAuthentication
1165
1433
  ? `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));`
1166
1434
  : '';
1167
- return String.raw`
1435
+ const source = String.raw`
1168
1436
  @external("fastly_abi", "init") declare function fastly_abi_init(version: i64): i32
1169
1437
  @external("fastly_http_req", "body_downstream_get") declare function fastly_http_req_body_downstream_get(requestOut: usize, bodyOut: usize): i32
1170
1438
  @external("fastly_http_req", "method_get") declare function fastly_http_req_method_get(handle: i32, buffer: usize, bufferLength: i32, writtenOut: usize): i32
@@ -1175,7 +1443,7 @@ function __pulse_fastly_jwt_send_error(): void {}
1175
1443
  @external("fastly_http_req", "uri_set") declare function fastly_http_req_uri_set(handle: i32, uri: usize, uriLength: i32): i32
1176
1444
  @external("fastly_http_req", "header_insert") declare function fastly_http_req_header_insert(handle: i32, name: usize, nameLength: i32, value: usize, valueLength: i32): i32
1177
1445
  @external("fastly_http_req", "send_async") declare function fastly_http_req_send_async(handle: i32, body: i32, backend: usize, backendLength: i32, pendingOut: usize): i32
1178
- @external("fastly_http_req", "pending_req_wait") declare function fastly_http_req_pending_req_wait(handle: i32, responseOut: usize, bodyOut: usize): i32
1446
+ @external("fastly_http_req", "pending_req_wait_v2") declare function fastly_http_req_pending_req_wait_v2(handle: i32, detail: usize, responseOut: usize, bodyOut: usize): i32
1179
1447
  @external("fastly_http_resp", "new") declare function fastly_http_resp_new(handleOut: usize): i32
1180
1448
  @external("fastly_http_resp", "header_append") declare function fastly_http_resp_header_append(handle: i32, name: usize, nameLength: i32, value: usize, valueLength: i32): i32
1181
1449
  @external("fastly_http_resp", "header_value_get") declare function fastly_http_resp_header_value_get(handle: i32, name: usize, nameLength: i32, value: usize, valueLength: i32, writtenOut: usize): i32
@@ -1195,8 +1463,11 @@ function __pulse_fastly_jwt_send_error(): void {}
1195
1463
  @external("fastly_kv_store", "lookup_wait_v2") declare function fastly_kv_store_lookup_wait_v2(handle: i32, bodyOut: usize, metadata: usize, metadataLength: i32, writtenOut: usize, generationOut: usize, kvErrorOut: usize): i32
1196
1464
  @external("fastly_kv_store", "insert") declare function fastly_kv_store_insert(store: i32, key: usize, keyLength: i32, body: i32, configMask: i32, config: usize, insertOut: usize): i32
1197
1465
  @external("fastly_kv_store", "insert_wait") declare function fastly_kv_store_insert_wait(handle: i32, kvErrorOut: usize): i32
1198
- @external("fastly_log", "endpoint_get") declare function fastly_log_endpoint_get(name: usize, nameLength: i32, handleOut: usize): i32
1199
- @external("fastly_log", "write") declare function fastly_log_write(handle: i32, message: usize, messageLength: i32, writtenOut: usize): i32
1466
+ @external("fastly_kv_store", "delete") declare function fastly_kv_store_delete(store: i32, key: usize, keyLength: i32, configMask: i32, config: usize, pendingOut: usize): i32
1467
+ @external("fastly_kv_store", "delete_wait") declare function fastly_kv_store_delete_wait(handle: i32, kvErrorOut: usize): i32
1468
+ @external("fastly_kv_store", "list") declare function fastly_kv_store_list(store: i32, configMask: i32, config: usize, pendingOut: usize): i32
1469
+ @external("fastly_kv_store", "list_wait") declare function fastly_kv_store_list_wait(handle: i32, bodyOut: usize, kvErrorOut: usize): i32
1470
+ ${nativeLoggingSource(bindings.logEndpoint)}
1200
1471
  @external("wasi_snapshot_preview1", "clock_time_get") declare function wasi_snapshot_preview1_clock_time_get(clockId: i32, precision: i64, timeOut: usize): i32
1201
1472
 
1202
1473
  const PULSE_FASTLY_EFFECTS_ABI_VERSION: i64 = ${FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION}
@@ -1233,20 +1504,42 @@ const PULSE_ERROR_SCHEMA: i32 = 1005
1233
1504
  const PULSE_ERROR_TRANSPORT: i32 = 1006
1234
1505
  const PULSE_ERROR_STATE: i32 = 1007
1235
1506
  const PULSE_ERROR_JWT: i32 = 1008
1507
+ const PULSE_ERROR_REQUEST_BODY: i32 = 1009
1236
1508
 
1237
1509
  class __PulseFastlyValue {
1510
+ ${tracksJsonDuplicates(plan) ? 'duplicateJsonKeys: bool = false' : ''}
1238
1511
  kind: i32 = PULSE_VALUE_UNDEFINED
1239
1512
  boolean: i32 = 0
1240
1513
  number: f64 = 0.0
1241
- text: string = ""
1242
- keys: Array<string> = new Array<string>()
1243
- values: Array<i32> = new Array<i32>()
1514
+ ${nativeStringFields}
1515
+ // Scalar handles do not need collection backing stores.
1516
+ private keysStorage: Array<string> | null = null
1517
+ get keys(): Array<string> {
1518
+ if (this.keysStorage === null) this.keysStorage = new Array<string>()
1519
+ return this.keysStorage!
1520
+ }
1521
+ // Scalar handles do not need collection backing stores.
1522
+ private valuesStorage: Array<i32> | null = null
1523
+ get values(): Array<i32> {
1524
+ if (this.valuesStorage === null) this.valuesStorage = new Array<i32>()
1525
+ return this.valuesStorage!
1526
+ }
1244
1527
  responseHandle: i32 = 0
1245
1528
  bodyHandle: i32 = 0
1246
1529
  bodyLoaded: i32 = 0
1530
+ s3Body: bool = false
1531
+ s3Deadline: i64 = 0
1532
+ s3Length: i32 = 0
1247
1533
  status: i32 = 200
1248
- headers: Array<__PulseFastlyHeader> = new Array<__PulseFastlyHeader>()
1534
+ // Scalar handles do not need collection backing stores.
1535
+ private headersStorage: Array<__PulseFastlyHeader> | null = null
1536
+ get headers(): Array<__PulseFastlyHeader> {
1537
+ if (this.headersStorage === null) this.headersStorage = new Array<__PulseFastlyHeader>()
1538
+ return this.headersStorage!
1539
+ }
1540
+ binaryBody: Uint8Array | null = null
1249
1541
  immutable: bool = false
1542
+ ${nativeHandleOwnership.fields(options.handleTracing)}
1250
1543
  }
1251
1544
  class __PulseFastlyHeader {
1252
1545
  constructor(public name: string, public value: string) {}
@@ -1255,11 +1548,14 @@ class __PulseJsonParser {
1255
1548
  source: string
1256
1549
  index: i32 = 0
1257
1550
  failed: bool = false
1258
- constructor(source: string) { this.source = source }
1551
+ tooLarge: bool = false
1552
+ entries: i32 = 0
1553
+ constructor(source: string, public conditionalKv: bool = false) { this.source = source }
1259
1554
  skip(): void { while (this.index < this.source.length) { const c = this.source.charCodeAt(this.index); if (c != 32 && c != 9 && c != 10 && c != 13) return; this.index += 1 } }
1260
1555
  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 }
1261
1556
  value(depth: i32): i32 {
1262
- this.skip(); if (this.index >= this.source.length || depth > 64) { this.failed = true; return 0 }
1557
+ 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 }
1558
+ 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 }
1263
1559
  const c = this.source.charCodeAt(this.index)
1264
1560
  if (c == 34) return __pulse_fastly_string_value(this.string())
1265
1561
  if (c == 123) return this.object(depth + 1)
@@ -1271,37 +1567,47 @@ class __PulseJsonParser {
1271
1567
  this.failed = true; return 0
1272
1568
  }
1273
1569
  string(): string {
1274
- let output = ""; this.index += 1
1275
- while (this.index < this.source.length) {
1276
- let c = this.source.charCodeAt(this.index); this.index += 1
1277
- if (c == 34) return output
1570
+ // Find a bounded span, then decode each UTF-16 unit once. Concatenating one
1571
+ // character at a time exhausts stub/fixed memory on a valid maximum KV value.
1572
+ const start = this.index + 1; let end = start; let plain = true;
1573
+ while (end < this.source.length) {
1574
+ const c = this.source.charCodeAt(end);
1575
+ if (c == 34) break;
1576
+ if (c == 92) { plain = false; end += 1; }
1577
+ else if (c < 32) plain = false;
1578
+ end += 1;
1579
+ }
1580
+ if (end >= this.source.length) { this.failed = true; return "" }
1581
+ // A validated span needs one owned string, without a temporary UTF-16 array.
1582
+ // Keep escaped and malformed spans on the existing decoder path.
1583
+ if (plain) { this.index = end + 1; return this.source.substring(start, end); }
1584
+ const output = new Uint16Array(end - start); let count = 0; this.index = start;
1585
+ while (this.index < end) {
1586
+ let c = this.source.charCodeAt(this.index++);
1278
1587
  if (c == 92) {
1279
- if (this.index >= this.source.length) { this.failed = true; return "" }
1280
- c = this.source.charCodeAt(this.index); this.index += 1
1281
- if (c == 34 || c == 92 || c == 47) output += String.fromCharCode(c)
1282
- else if (c == 98) output += String.fromCharCode(8)
1283
- else if (c == 102) output += String.fromCharCode(12)
1284
- else if (c == 110) output += "\n"
1285
- else if (c == 114) output += "\r"
1286
- else if (c == 116) output += "\t"
1588
+ if (this.index >= end) { this.failed = true; return "" }
1589
+ c = this.source.charCodeAt(this.index++);
1590
+ if (c == 34 || c == 92 || c == 47) {}
1591
+ else if (c == 98) c = 8;
1592
+ else if (c == 102) c = 12;
1593
+ else if (c == 110) c = 10;
1594
+ else if (c == 114) c = 13;
1595
+ else if (c == 116) c = 9;
1287
1596
  else if (c == 117) {
1288
- if (this.index + 4 > this.source.length) { this.failed = true; return "" }
1289
- let code = 0
1290
- for (let digitIndex = 0; digitIndex < 4; digitIndex += 1) {
1291
- const digitCode = this.source.charCodeAt(this.index + digitIndex)
1292
- let digit = -1
1293
- if (digitCode >= 48 && digitCode <= 57) digit = digitCode - 48
1294
- else if (digitCode >= 65 && digitCode <= 70) digit = digitCode - 55
1295
- else if (digitCode >= 97 && digitCode <= 102) digit = digitCode - 87
1597
+ if (this.index + 4 > end) { this.failed = true; return "" }
1598
+ c = 0;
1599
+ for (let i = 0; i < 4; i++) {
1600
+ const d = this.source.charCodeAt(this.index++);
1601
+ const digit = d >= 48 && d <= 57 ? d - 48 : d >= 65 && d <= 70 ? d - 55 : d >= 97 && d <= 102 ? d - 87 : -1;
1296
1602
  if (digit < 0) { this.failed = true; return "" }
1297
- code = (code << 4) | digit
1603
+ c = (c << 4) | digit;
1298
1604
  }
1299
- this.index += 4; output += String.fromCharCode(code)
1300
1605
  } else { this.failed = true; return "" }
1301
1606
  } else if (c < 32) { this.failed = true; return "" }
1302
- else output += String.fromCharCode(c)
1607
+ output[count++] = u16(c);
1303
1608
  }
1304
- this.failed = true; return ""
1609
+ this.index = end + 1;
1610
+ return String.UTF16.decodeUnsafe(output.dataStart, count * 2);
1305
1611
  }
1306
1612
  numberValue(): i32 {
1307
1613
  const start = this.index
@@ -1350,7 +1656,9 @@ class __PulseJsonParser {
1350
1656
  while (!this.failed) {
1351
1657
  this.skip(); if (this.index >= this.source.length || this.source.charCodeAt(this.index) != 34) { this.failed = true; break }
1352
1658
  const key = this.string(); this.skip()
1659
+ if (this.conditionalKv && __pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) { this.failed = true; break }
1353
1660
  if (this.index >= this.source.length || this.source.charCodeAt(this.index) != 58) { this.failed = true; break }
1661
+ ${tracksJsonDuplicates(plan) ? 'if (__pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) __pulse_fastly_value(output).duplicateJsonKeys = true' : ''}
1354
1662
  this.index += 1; host_value_object_set(output, __pulse_fastly_string_value(key), this.value(depth)); this.skip()
1355
1663
  if (this.index >= this.source.length) { this.failed = true; break }
1356
1664
  const c = this.source.charCodeAt(this.index); this.index += 1
@@ -1364,13 +1672,20 @@ class __PulseJsonParser {
1364
1672
  const PULSE_FASTLY_PENDING_NONE: i32 = 0
1365
1673
  const PULSE_FASTLY_PENDING_ASYNC: i32 = 1
1366
1674
  const PULSE_FASTLY_PENDING_READY: i32 = 2
1367
- @lazy const __pulse_fastly_values = new Array<__PulseFastlyValue>()
1675
+ ${nativeHandleOwnership.tableSource(options.handleTracing)}
1676
+ // Primitive handles have value semantics. Bounded caches keep admitted scalar
1677
+ // validation loops from retaining a fresh boxed value for every iteration.
1678
+ @lazy const __pulse_fastly_scalar_handles = new StaticArray<i32>(4)
1679
+ @lazy const __pulse_fastly_integer_handles = new StaticArray<i32>(65536)
1680
+ @lazy const __pulse_fastly_character_handles = new StaticArray<i32>(65537)
1681
+ @lazy const __pulse_fastly_literal_handles = new Map<string, i32>()
1368
1682
  @lazy const __pulse_fastly_pending = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1369
1683
  @lazy const __pulse_fastly_pending_mode = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1370
1684
  @lazy const __pulse_fastly_payloads = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1371
1685
  @lazy const __pulse_fastly_ready = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1372
1686
  @lazy const __pulse_fastly_grip_channels = new Array<string>()
1373
1687
  @lazy const __pulse_fastly_redactions = new Array<string>()
1688
+ @lazy const __pulse_fastly_field_redactions = new Array<string>()
1374
1689
  let __pulse_fastly_request_handle: i32 = 0
1375
1690
  let __pulse_fastly_request_body_handle: i32 = 0
1376
1691
  let __pulse_fastly_request_method: string = ""
@@ -1381,7 +1696,6 @@ let __pulse_fastly_request_body_loaded: i32 = 0
1381
1696
  let __pulse_fastly_last_error: i32 = 0
1382
1697
  let __pulse_fastly_error_stage: i32 = 0
1383
1698
  let __pulse_fastly_error_effect: i32 = -1
1384
- let __pulse_fastly_log_handle: i32 = -1
1385
1699
  let __pulse_fastly_jwt_error: i32 = 0
1386
1700
  let __pulse_fastly_jwt_secret_calls: i32 = 0
1387
1701
  let __pulse_fastly_jwt_crypto_calls: i32 = 0
@@ -1398,57 +1712,165 @@ function __pulse_fastly_out_i32(): StaticArray<i32> { return new StaticArray<i32
1398
1712
  function __pulse_fastly_out_value(out: StaticArray<i32>): i32 { return load<i32>(changetype<usize>(out)) }
1399
1713
  function __pulse_fastly_utf8(value: string): ArrayBuffer { return String.UTF8.encode(value, false) }
1400
1714
  function __pulse_fastly_decode(buffer: Uint8Array, length: i32): string { return String.UTF8.decodeUnsafe(buffer.dataStart, length, false) }
1401
- function __pulse_fastly_put(value: __PulseFastlyValue): i32 { __pulse_fastly_values.push(value); return __pulse_fastly_values.length }
1402
- 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]) }
1715
+ ${nativeStringAssembly}
1716
+ ${nativeStringConcat}
1717
+ ${nativeStringTrim}
1718
+ ${nativeStringIndex}
1403
1719
  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])) }
1404
- 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) }
1720
+ function __pulse_fastly_string_value(value: string): i32 {
1721
+ const index = value.length == 0 ? 65536 : value.length == 1 ? value.charCodeAt(0) : -1;
1722
+ if (index >= 0) { const cached = unchecked(__pulse_fastly_character_handles[index]); if (cached != 0) return cached }
1723
+ const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_STRING; item.text = value;
1724
+ const handle = __pulse_fastly_put(item);
1725
+ if (index >= 0) { unchecked(__pulse_fastly_character_handles[index] = handle); ${options.handleTracing ? 'if (handle != 0) __pulse_handle_cache_roots.push(handle);' : ''} }
1726
+ return handle
1727
+ }
1405
1728
  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 }
1406
1729
  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() }
1407
1730
  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) }
1408
- function __pulse_fastly_remember_secret(value: string): string { if (value.length == 0) return value; for (let i = 0; i < __pulse_fastly_redactions.length; i += 1) if (unchecked(__pulse_fastly_redactions[i]) == value) return value; __pulse_fastly_redactions.push(value); return value }
1731
+ function __pulse_fastly_remember_secret(value: string, field: bool = false): string { if (value.length == 0) return value; const values = field ? __pulse_fastly_field_redactions : __pulse_fastly_redactions; for (let i = 0; i < values.length; i += 1) if (unchecked(values[i]) == value) return value; values.push(value); return value }
1409
1732
  function __pulse_fastly_replace_secret(input: string, secret: string): string { if (secret.length == 0) return input; let source = input; let output = ""; while (true) { const index = source.indexOf(secret); if (index < 0) return output + source; output += source.substring(0, index) + "<redacted>"; source = source.substring(index + secret.length) } }
1410
- function __pulse_fastly_redact(input: string): string { let output = input; for (let i = 0; i < __pulse_fastly_redactions.length; i += 1) output = __pulse_fastly_replace_secret(output, unchecked(__pulse_fastly_redactions[i])); return output }
1411
- function host_log(level: i32, messageHandle: i32): void { let endpoint = __pulse_fastly_log_handle; if (endpoint == -2) return; if (endpoint == -1) { const name = __pulse_fastly_utf8("stdout"); const out = __pulse_fastly_out_i32(); if (fastly_log_endpoint_get(changetype<usize>(name), name.byteLength, changetype<usize>(out)) != FASTLY_STATUS_OK) { __pulse_fastly_log_handle = -2; return } endpoint = __pulse_fastly_out_value(out); __pulse_fastly_log_handle = endpoint } let label = "unknown"; if (level == 1) label = "error"; else if (level == 2) label = "warn"; else if (level == 3) label = "info"; else if (level == 4) label = "debug"; const bytes = __pulse_fastly_utf8("[pulse:" + label + "] " + __pulse_fastly_redact(__pulse_fastly_string(messageHandle)) + "\n"); const written = __pulse_fastly_out_i32(); fastly_log_write(endpoint, changetype<usize>(bytes), bytes.byteLength, changetype<usize>(written)) }
1733
+ // Walk complete JSON string tokens, including escaped quotes. Substring matching
1734
+ // a quoted field would still corrupt an unrelated token such as "prefix\\\"id".
1735
+ function __pulse_fastly_redact_fields(input: string): string {
1736
+ if (__pulse_fastly_field_redactions.length == 0) return input;
1737
+ let output = "", start = 0, cursor = 0;
1738
+ while (cursor < input.length) {
1739
+ if (input.charCodeAt(cursor) != 34) { cursor += 1; continue; }
1740
+ const begin = cursor++;
1741
+ while (cursor < input.length) {
1742
+ const code = input.charCodeAt(cursor++);
1743
+ if (code == 92 && cursor < input.length) cursor += 1;
1744
+ else if (code == 34) break;
1745
+ }
1746
+ const token = input.substring(begin, cursor);
1747
+ for (let i = 0; i < __pulse_fastly_field_redactions.length; i += 1) {
1748
+ if (token != unchecked(__pulse_fastly_field_redactions[i])) continue;
1749
+ output += input.substring(start, begin) + __pulse_fastly_quote("<redacted>");
1750
+ start = cursor; break;
1751
+ }
1752
+ }
1753
+ return output + input.substring(start);
1754
+ }
1755
+ function __pulse_fastly_redact(input: string): string { let output = __pulse_fastly_redact_fields(input); for (let i = 0; i < __pulse_fastly_redactions.length; i += 1) output = __pulse_fastly_replace_secret(output, unchecked(__pulse_fastly_redactions[i])); return output }
1756
+ function host_log(level: i32, messageHandle: i32): void { let label = "unknown"; if (level == 1) label = "error"; else if (level == 2) label = "warn"; else if (level == 3) label = "info"; else if (level == 4) label = "debug"; const bytes = __pulse_fastly_utf8("[pulse:" + label + "] " + __pulse_fastly_redact(__pulse_fastly_string(messageHandle)) + "\n"); __pulse_fastly_write_stdout(bytes) }
1412
1757
  function __pulse_fastly_number(handle: i32): f64 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_NUMBER) return value.number; if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? 1.0 : 0.0; if (value.kind == PULSE_VALUE_STRING) return F64.parseFloat(value.text); return 0.0 }
1413
1758
  function __pulse_fastly_hex(value: i32): string { return String.fromCharCode(value < 10 ? 48 + value : 87 + value) }
1414
- function __pulse_fastly_quote(value: string): string { let out = "\""; for (let i = 0; i < value.length; i += 1) { const c = value.charCodeAt(i); if (c == 34) out += "\\\""; else if (c == 92) out += "\\\\"; else if (c == 8) out += "\\b"; else if (c == 9) out += "\\t"; else if (c == 10) out += "\\n"; else if (c == 12) out += "\\f"; else if (c == 13) out += "\\r"; else if (c < 32) out += "\\u00" + __pulse_fastly_hex((c >> 4) & 15) + __pulse_fastly_hex(c & 15); else out += String.fromCharCode(c) } return out + "\"" }
1415
- function __pulse_fastly_json(handle: i32, depth: i32): string {
1416
- if (depth > 64) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1); return "null" }
1759
+ function __pulse_fastly_is_unpaired(value: string, i: i32): bool {
1760
+ const c = value.charCodeAt(i);
1761
+ if (c >= 0xd800 && c <= 0xdbff) return i + 1 >= value.length || value.charCodeAt(i + 1) < 0xdc00 || value.charCodeAt(i + 1) > 0xdfff;
1762
+ if (c >= 0xdc00 && c <= 0xdfff) return i == 0 || value.charCodeAt(i - 1) < 0xd800 || value.charCodeAt(i - 1) > 0xdbff;
1763
+ return false;
1764
+ }
1765
+ function __pulse_fastly_quote(value: string): string {
1766
+ // Count once and write UTF-16 units once. Repeated concatenation can allocate
1767
+ // gigabytes for a valid bounded string when every byte needs JSON escaping.
1768
+ let size = 2;
1769
+ for (let i = 0; i < value.length; i++) {
1770
+ const c = value.charCodeAt(i);
1771
+ size += c == 34 || c == 92 || c == 8 || c == 9 || c == 10 || c == 12 || c == 13 ? 2 : (c < 32 || __pulse_fastly_is_unpaired(value, i)) ? 6 : 1;
1772
+ }
1773
+ const out = new Uint16Array(size); let cursor = 0; out[cursor++] = 34;
1774
+ for (let i = 0; i < value.length; i++) {
1775
+ const c = value.charCodeAt(i);
1776
+ if (c == 34 || c == 92) { out[cursor++] = 92; out[cursor++] = u16(c); }
1777
+ else if (c == 8 || c == 9 || c == 10 || c == 12 || c == 13) {
1778
+ out[cursor++] = 92; out[cursor++] = c == 8 ? 98 : c == 9 ? 116 : c == 10 ? 110 : c == 12 ? 102 : 114;
1779
+ } else if (c < 32 || __pulse_fastly_is_unpaired(value, i)) {
1780
+ out[cursor++] = 92; out[cursor++] = 117;
1781
+ out[cursor++] = u16('0123456789abcdef'.charCodeAt((c >> 12) & 15)); out[cursor++] = u16('0123456789abcdef'.charCodeAt((c >> 8) & 15));
1782
+ out[cursor++] = u16('0123456789abcdef'.charCodeAt((c >> 4) & 15)); out[cursor++] = u16('0123456789abcdef'.charCodeAt(c & 15));
1783
+ } else out[cursor++] = u16(c);
1784
+ }
1785
+ out[cursor] = 34;
1786
+ return String.UTF16.decodeUnsafe(out.dataStart, out.byteLength);
1787
+ }
1788
+ function __pulse_fastly_json_parts(handle: i32, depth: i32, parts: Array<string>): void {
1789
+ 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 }
1417
1790
  const value = __pulse_fastly_value(handle)
1418
- if (value.kind == PULSE_VALUE_UNDEFINED || value.kind == PULSE_VALUE_NULL) return "null"
1419
- if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? "true" : "false"
1420
- if (value.kind == PULSE_VALUE_NUMBER) return __pulse_fastly_number_string(value.number)
1421
- if (value.kind == PULSE_VALUE_STRING) return __pulse_fastly_quote(value.text)
1422
- 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 + "]" }
1423
- 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 + "}" }
1424
- return "null"
1791
+ if (value.kind == PULSE_VALUE_BOOLEAN) { parts.push(value.boolean != 0 ? "true" : "false"); return }
1792
+ if (value.kind == PULSE_VALUE_NUMBER) { parts.push(__pulse_fastly_number_string(value.number)); return }
1793
+ if (value.kind == PULSE_VALUE_STRING) { parts.push(__pulse_fastly_quote(value.text)); return }
1794
+ 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 }
1795
+ 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 }
1796
+ parts.push("null")
1797
+ }
1798
+ function __pulse_fastly_json(handle: i32, depth: i32): string {
1799
+ // Join once, avoiding a complete copy of a multi-MiB leaf at every ancestor
1800
+ // and at every following member in the response or effect result.
1801
+ const parts = new Array<string>(); __pulse_fastly_json_parts(handle, depth, parts);
1802
+ return __pulse_fastly_join_strings(parts)
1425
1803
  }
1426
1804
  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 }
1427
1805
  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) }
1428
1806
  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)) }
1429
- function __pulse_fastly_read_body(handle: i32, effectIndex: i32): string { let out = ""; let total = 0; while (true) { const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES); const read = __pulse_fastly_out_i32(); const status = fastly_http_body_read(handle, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(read)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" } const count = __pulse_fastly_out_value(read); if (count <= 0) return out; total += count; if (total > __PULSE_SCHEMA_MAX_BYTES && __PULSE_SCHEMA_MAX_BYTES > 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, effectIndex); return "" } out += __pulse_fastly_decode(buffer, count) } }
1430
- 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)) }
1431
- 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)) }
1807
+ function __pulse_fastly_read_body(handle: i32, effectIndex: i32, requestText: bool = false): string {
1808
+ const chunks = new Array<Uint8Array>(); let total = 0;
1809
+ while (true) {
1810
+ const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES), read = __pulse_fastly_out_i32();
1811
+ const status = fastly_http_body_read(handle, buffer.dataStart, buffer.length, changetype<usize>(read));
1812
+ if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1813
+ const count = __pulse_fastly_out_value(read);
1814
+ if (count < 0 || count > buffer.length) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1815
+ if (!count) break;
1816
+ total += count;
1817
+ 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 "" }
1818
+ chunks.push(buffer.subarray(0, count));
1819
+ }
1820
+ // UTF-8 scalars may straddle host read chunks. Decode the bounded body once.
1821
+ const bytes = new Uint8Array(total); let offset = 0;
1822
+ for (let i = 0; i < chunks.length; i++) { bytes.set(chunks[i], offset); offset += chunks[i].length; }
1823
+ if (requestText && !__pulse_request_utf8_valid(bytes)) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, 24, effectIndex); return "" }
1824
+ return __pulse_fastly_decode(bytes, total);
1825
+ }
1826
+ function __pulse_fastly_request_header_text(name: string): string { const value = __pulse_header_lookup(__pulse_fastly_request_handle, name, false); return value === null ? "" : value }
1827
+ function __pulse_fastly_fetch_header_text(responseHandle: i32, name: string): string { const value = __pulse_header_lookup(responseHandle, name, true); return value === null ? "" : value }
1432
1828
  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 }
1433
1829
 
1434
- function host_value_undefined(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_UNDEFINED; return __pulse_fastly_put(item) }
1435
- function host_value_null(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NULL; return __pulse_fastly_put(item) }
1436
- 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) }
1437
- function host_value_number(value: f64): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value; return __pulse_fastly_put(item) }
1438
- function host_value_string(pointer: i32, length: i32): i32 { return __pulse_fastly_string_value(changetype<string>(pointer)) }
1830
+ function __pulse_fastly_scalar(kind: i32, boolean: i32, slot: i32): i32 {
1831
+ const cached = unchecked(__pulse_fastly_scalar_handles[slot]); if (cached != 0) return cached;
1832
+ const item = new __PulseFastlyValue(); item.kind = kind; item.boolean = boolean;
1833
+ const handle = __pulse_fastly_put(item); unchecked(__pulse_fastly_scalar_handles[slot] = handle); ${options.handleTracing ? 'if (handle != 0) __pulse_handle_cache_roots.push(handle);' : ''} return handle
1834
+ }
1835
+ function host_value_undefined(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_UNDEFINED, 0, 0) }
1836
+ function host_value_null(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_NULL, 0, 1) }
1837
+ function host_value_boolean(value: i32): i32 { return __pulse_fastly_scalar(PULSE_VALUE_BOOLEAN, value != 0 ? 1 : 0, value != 0 ? 3 : 2) }
1838
+ ${plan.callables?.length || plan.binaryResources || plan.numericWorkspaces?.length ? require('./native-binary-resources.js').source() : ''}
1839
+ function host_value_number(value: f64): i32 {
1840
+ // Preserve negative zero, NaN, infinities and nonintegral values exactly.
1841
+ const cacheable = value >= 0 && value < 65536 && Math.floor(value) == value && (value != 0 || 1.0 / value > 0);
1842
+ const index = cacheable ? i32(value) : -1;
1843
+ if (index >= 0) { const cached = unchecked(__pulse_fastly_integer_handles[index]); if (cached != 0) return cached }
1844
+ const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value;
1845
+ const handle = __pulse_fastly_put(item); if (index >= 0) { unchecked(__pulse_fastly_integer_handles[index] = handle); ${options.handleTracing ? 'if (handle != 0) __pulse_handle_cache_roots.push(handle);' : ''} } return handle
1846
+ }
1847
+ function host_value_string(pointer: i32, length: i32): i32 {
1848
+ const text = changetype<string>(pointer);
1849
+ if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.has(text)) return __pulse_fastly_literal_handles.get(text);
1850
+ const handle = __pulse_fastly_string_value(text);
1851
+ if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.size < 1024) { __pulse_fastly_literal_handles.set(text, handle); ${options.handleTracing ? 'if (handle != 0) __pulse_handle_cache_roots.push(handle);' : ''} }
1852
+ return handle
1853
+ }
1854
+ function host_effect_collection_count(handle: i32, max: i32, stateIndex: i32): i32 {
1855
+ const input = __pulse_fastly_value(handle)
1856
+ if (__pulse_fastly_last_error != 0 || input.kind != PULSE_VALUE_ARRAY || max < 1 || max > 32) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 173, -1); return -1 }
1857
+ if (input.values.length > max) { __pulse_fastly_fail(PULSE_ERROR_STATE, 172, -1); return -1 }
1858
+ if (__pulse_invocation_count + input.values.length > PULSE_FASTLY_MAX_EFFECT_INVOCATIONS) { __pulse_fastly_fail(PULSE_ERROR_STATE, 170, -1); return -1 }
1859
+ return input.values.length
1860
+ }
1439
1861
  function host_value_array(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_ARRAY; return __pulse_fastly_put(item) }
1440
1862
  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) }
1441
1863
  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])) }
1442
1864
  function host_value_object(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_OBJECT; return __pulse_fastly_put(item) }
1443
1865
  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) } }
1444
1866
  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])) }
1445
- 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() }
1867
+ 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() }
1446
1868
  function host_value_property_set(target: i32, key: i32, value: i32): i32 { host_value_object_set(target, key, value); return value }
1447
- 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) }
1869
+ 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) }
1448
1870
  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) }
1449
- 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 }
1871
+ 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 }
1450
1872
  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 }
1451
- 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 }
1873
+ 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 }
1452
1874
  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 }
1453
1875
 
1454
1876
  function host_request_method(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_method) }
@@ -1459,13 +1881,36 @@ function __pulse_router_param_text(path: string, pattern: string, name: string):
1459
1881
  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) }
1460
1882
  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) }
1461
1883
  function host_request_path(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_path) }
1462
- function host_request_headers(): i32 { return host_value_object() }
1463
- 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) }
1464
- 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) }
1465
- 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 }
1884
+ function host_request_headers(): i32 { return __pulse_request_headers_read() }
1885
+ function host_request_header(name: i32): i32 { const value = __pulse_header_lookup(__pulse_fastly_request_handle, __pulse_fastly_string(name), false); return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
1886
+ 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) }
1887
+ 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 }
1466
1888
  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])))) } } }
1467
1889
  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) }
1468
1890
  function host_response_json(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, true) }
1891
+ function host_schema_decode(text: i32, schema: i32): i32 {
1892
+ const id = __pulse_fastly_value(schema)
1893
+ if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
1894
+ const input = __pulse_fastly_value(text)
1895
+ if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }
1896
+ if (String.UTF8.byteLength(input.text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1897
+ const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(input.text, schema)' : '__pulse_fastly_parse_json(input.text)'}
1898
+ if (parsed <= 0) return 0
1899
+ const decoded = __pulse_fastly_schema_apply(id.text, parsed, false)
1900
+ if (decoded <= 0) return 0
1901
+ __pulse_fastly_deep_freeze(decoded)
1902
+ return decoded
1903
+ }
1904
+ function host_schema_encode(value: i32, schema: i32): i32 {
1905
+ const id = __pulse_fastly_value(schema)
1906
+ if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
1907
+ ${textEncodeIds.length ? `const textResult = ${textEncodeIds.map(id => `id.text == ${quote(id)}`).join(' || ')}
1908
+ const projected = __pulse_fastly_schema_apply(id.text, value, true, textResult)` : 'const projected = __pulse_fastly_schema_apply(id.text, value, true)'}
1909
+ if (projected <= 0) return 0
1910
+ const text = ${textEncodeIds.length ? 'textResult ? __pulse_fastly_string(projected) : ' : ''}__pulse_fastly_json(projected, 0)
1911
+ if (String.UTF8.byteLength(text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1912
+ return ${textEncodeIds.length ? 'textResult ? projected : ' : ''}__pulse_fastly_string_value(text)
1913
+ }
1469
1914
  function host_response_text(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, false) }
1470
1915
  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) }
1471
1916
  function __pulse_fastly_grip_frame_channels(options: __PulseFastlyValue, output: __PulseFastlyValue): bool { const channelsIndex = __pulse_fastly_find(options, "channels"); const channelIndex = __pulse_fastly_find(options, "channel"); let count = 0; if (channelsIndex >= 0) { const channels = __pulse_fastly_value(unchecked(options.values[channelsIndex])); if (channels.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 133, -1); return false } for (let i = 0; i < channels.values.length; i += 1) { const channel = __pulse_fastly_string(unchecked(channels.values[i])).trim(); if (channel.length == 0 || channel.indexOf(",") >= 0 || channel.indexOf("\r") >= 0 || channel.indexOf("\n") >= 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 134, -1); return false } output.headers.push(new __PulseFastlyHeader("Grip-Channel", channel)); count += 1 } } else if (channelIndex >= 0) { const channel = __pulse_fastly_string(unchecked(options.values[channelIndex])).trim(); if (channel.length == 0 || channel.indexOf(",") >= 0 || channel.indexOf("\r") >= 0 || channel.indexOf("\n") >= 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 134, -1); return false } output.headers.push(new __PulseFastlyHeader("Grip-Channel", channel)); count = 1 } if (count == 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 135, -1); return false } return true }
@@ -1475,8 +1920,8 @@ function host_grip_subscribe(responseHandle: i32, optionsHandle: i32): i32 { con
1475
1920
  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 }
1476
1921
  function host_kv_namespace(name: i32): i32 { return name }
1477
1922
  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)) }
1478
- 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 }
1479
- 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) }
1923
+ 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 }
1924
+ 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_header_lookup(response.responseHandle, __pulse_fastly_string(nameHandle), true); return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
1480
1925
  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]) }
1481
1926
  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) }
1482
1927
  function __pulse_fastly_open_config_store(effectIndex: i32): i32 { const nameBytes = __pulse_fastly_utf8(__pulse_fastly_config_store(effectIndex)); const out = __pulse_fastly_out_i32(); const status = fastly_config_store_open(changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 110, effectIndex); return 0 } return __pulse_fastly_out_value(out) }
@@ -1496,12 +1941,12 @@ function __pulse_fastly_jwt_record_verified(): void { __pulse_fastly_jwt_verifie
1496
1941
  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 }
1497
1942
  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'} }
1498
1943
  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) }
1944
+ 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) }
1499
1945
  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) }
1500
- 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) }
1501
- 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)) }
1946
+ function __pulse_fastly_start_request(effectIndex: i32, url: string, method: string, headers: __PulseFastlyValue, body: string, backend: string): void { __pulse_transport_slots[effectIndex] = null; 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_fetch_close_request(requestHandle); __pulse_fetch_close_body(bodyHandle); __pulse_transport_capture(effectIndex, status, 'send'); __pulse_fetch_fail(status, 77, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(pendingOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1947
+ 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)) }
1502
1948
  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) }
1503
- 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 }
1504
- 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) }
1949
+ ${assetBeginSource}
1505
1950
  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) }
1506
1951
  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) }
1507
1952
  function __pulse_fastly_grip_payload(payload: __PulseFastlyValue): __PulseFastlyValue { return __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")) }
@@ -1510,24 +1955,28 @@ function __pulse_fastly_grip_hold_begin(effectIndex: i32, payload: __PulseFastly
1510
1955
  ${gripSecretSource}
1511
1956
  function __pulse_fastly_grip_publish_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const valueHandle = __pulse_fastly_payload_field(payload, "payload"); const value = __pulse_fastly_value(valueHandle); if (value.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 122, effectIndex); return } const headers = new __PulseFastlyValue(); headers.kind = PULSE_VALUE_OBJECT; headers.keys.push("content-type"); headers.values.push(__pulse_fastly_string_value("application/json; charset=utf-8")); headers.keys.push("accept"); headers.values.push(__pulse_fastly_string_value("application/json")); ${gripAuthorizationSource} const body = __pulse_fastly_json(valueHandle, 0); if (String.UTF8.encode(body, false).byteLength > PULSE_FASTLY_BUFFER_BYTES) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 145, effectIndex); return } __pulse_fastly_start_request(effectIndex, __pulse_fastly_grip_publish_url(effectIndex), "POST", headers, body, __pulse_fastly_backend(effectIndex)) }
1512
1957
  ${effectDispatchSource(plan)}
1513
- function __pulse_fastly_wait_fetch(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 80, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const responseOut = __pulse_fastly_out_i32(); const bodyOut = __pulse_fastly_out_i32(); const status = fastly_http_req_pending_req_wait(pending, changetype<usize>(responseOut), changetype<usize>(bodyOut)); 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_TRANSPORT, 81, effectIndex); return 0 } const value = new __PulseFastlyValue(); value.kind = PULSE_VALUE_FETCH; value.responseHandle = __pulse_fastly_out_value(responseOut); value.bodyHandle = __pulse_fastly_out_value(bodyOut); return __pulse_fastly_put(value) }
1958
+ function __pulse_fastly_wait_fetch(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 80, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const responseOut = __pulse_fastly_out_i32(); const bodyOut = __pulse_fastly_out_i32(); const detail = new StaticArray<i32>(__PULSE_SEND_ERROR_DETAIL_WORDS); __pulse_send_error_detail_reset(changetype<usize>(detail)); const status = fastly_http_req_pending_req_wait_v2(pending, changetype<usize>(detail), changetype<usize>(responseOut), changetype<usize>(bodyOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_transport_capture(effectIndex, status, 'wait', changetype<usize>(detail)); __pulse_fetch_fail(status, 81, effectIndex); return 0 } const value = new __PulseFastlyValue(); value.kind = PULSE_VALUE_FETCH; value.responseHandle = __pulse_fastly_out_value(responseOut); value.bodyHandle = __pulse_fastly_out_value(bodyOut); return __pulse_fastly_put(value) }
1514
1959
  function __pulse_fastly_wait_grip_publish(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, 129, 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, 130, effectIndex); return 0 } const originStatus = __pulse_fastly_out_value(statusOut); if (originStatus < 200 || originStatus > 299) { __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 131, effectIndex); return 0 } const outer = __pulse_fastly_value(unchecked(__pulse_fastly_payloads[effectIndex])); const publicationHandle = __pulse_fastly_payload_field(outer, "payload"); const publication = __pulse_fastly_value(publicationHandle); if (publication.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 132, effectIndex); return 0 } const receipt = host_value_object(); host_value_object_set(receipt, __pulse_fastly_string_value("published"), host_value_boolean(1)); for (let i = 0; i < publication.keys.length; i += 1) host_value_object_set(receipt, __pulse_fastly_string_value(unchecked(publication.keys[i])), unchecked(publication.values[i])); const options = host_value_object(); host_value_object_set(options, __pulse_fastly_string_value("status"), host_value_number(202.0)); return host_response_json(receipt, options) }
1515
1960
  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 }
1516
1961
  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 }
1517
1962
  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 }
1518
- 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" }
1519
- 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])) }
1520
- 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) }
1963
+ ${assetResultSource}
1521
1964
  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) }
1522
1965
  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 }
1523
- 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) }
1966
+ 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) }
1524
1967
 
1525
1968
  ${jwtEvidenceSource}
1969
+ ${terminalErrorSource}
1526
1970
  ${es256KeyArtifactSource(es256KeyRecords)}
1527
1971
  ${jwtCryptoCompositionSource(jwtCrypto)}
1972
+ ${jwtSigningCryptoCompositionSource(jwtCrypto)}
1528
1973
  ${generateSchemaRuntime(plan)}
1529
1974
  ${effectResultSource(plan, bindings)}
1530
1975
  `;
1976
+ if (!hasAssets) return source;
1977
+ const writer = 'status = __pulse_fastly_write_body(bodyHandle, value.text);';
1978
+ if (!source.includes(writer)) throw new Error('Embedded asset response writer boundary drift.');
1979
+ return source.replace(writer, 'status = value.binaryBody !== null ? __pulse_fastly_write_binary(bodyHandle, value.binaryBody!) : __pulse_fastly_write_body(bodyHandle, value.text);');
1531
1980
  }
1532
1981
 
1533
1982
  function driverSource(plan, options = {}) {
@@ -1544,6 +1993,13 @@ export function pulse_fastly_jwt_crypto_calls(): i32 { return __pulse_fastly_jwt
1544
1993
  export function pulse_fastly_jwt_clock_calls(): i32 { return __pulse_fastly_jwt_clock_calls }
1545
1994
  export function pulse_fastly_jwt_schema_calls(): i32 { return __pulse_fastly_jwt_schema_calls }
1546
1995
  export function ${entryName}(): void {
1996
+ if (__pulse_invocation_started) { __pulse_fastly_fail(PULSE_ERROR_STATE, 171, -1); return }
1997
+ __pulse_invocation_started = true
1998
+ __pulse_fastly_run_invocation()
1999
+ ${plan.effects.some(e => e.kind === 's3.getBody') ? '__s3_close_bodies()' : ''}
2000
+ __pulse_invocation_close()
2001
+ }
2002
+ function __pulse_fastly_run_invocation(): void {
1547
2003
  __pulse_fastly_last_error = fastly_abi_init(PULSE_FASTLY_EFFECTS_ABI_VERSION)
1548
2004
  if (__pulse_fastly_last_error != FASTLY_STATUS_OK) { __pulse_fastly_jwt_send_error(); return }
1549
2005
  const handles = new StaticArray<i32>(2)
@@ -1557,15 +2013,16 @@ export function ${entryName}(): void {
1557
2013
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1558
2014
  __pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
1559
2015
  let runStatus = pulse_start()
1560
- while (runStatus == 1 && __pulse_fastly_last_error == 0) {
2016
+ ${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicationErrors.settlementEnabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1561
2017
  for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
1562
2018
  if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
2019
+ const invocation = unchecked(__pulse_invocation_tickets[effectIndex])
1563
2020
  const result = __pulse_fastly_resolve_effect(effectIndex)
1564
2021
  if (result <= 0 || __pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1565
- if (pulse_set_effect_result(effectIndex, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
2022
+ if (__pulse_invocation_settle(effectIndex, invocation, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
1566
2023
  }
1567
2024
  runStatus = pulse_resume()
1568
- }
2025
+ }`}
1569
2026
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1570
2027
  if (runStatus != 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 102, -1); __pulse_fastly_jwt_send_error(); return }
1571
2028
  const result = pulse_result_handle()
@@ -1578,9 +2035,10 @@ export function ${entryName}(): void {
1578
2035
  }
1579
2036
 
1580
2037
  function capabilityImports(effect) {
2038
+ if (effect.kind === 'time.now') return ['wasi_snapshot_preview1.clock_time_get'];
1581
2039
  if (effect.kind === 'config.get') return ['fastly_config_store.open', 'fastly_config_store.get'];
1582
2040
  if (effect.kind === 'secret.get') return ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'];
1583
- if (effect.kind === 'jwt.verify') {
2041
+ if (['jwt.verify', 'jwt.sign'].includes(effect.kind)) {
1584
2042
  return [
1585
2043
  ...(effect.resource && effect.resource.keyType === 'secret'
1586
2044
  ? [
@@ -1592,6 +2050,7 @@ function capabilityImports(effect) {
1592
2050
  'wasi_snapshot_preview1.clock_time_get'
1593
2051
  ];
1594
2052
  }
2053
+ if (conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) return conditionalKv.kvImports(effect.kind).map(key => key.replace(':', '.'));
1595
2054
  if (effect.kind === 'kv.get') return ['fastly_kv_store.open', 'fastly_kv_store.lookup', 'fastly_kv_store.lookup_wait_v2', 'fastly_http_body.read'];
1596
2055
  if (effect.kind === 'kv.put') return ['fastly_kv_store.open', 'fastly_kv_store.insert', 'fastly_kv_store.insert_wait', 'fastly_http_body.new', 'fastly_http_body.write'];
1597
2056
  if (effect.kind === 'assets.lookup') return ['fastly_kv_store.open', 'fastly_kv_store.lookup', 'fastly_kv_store.lookup_wait_v2', 'fastly_http_body.read'];
@@ -1599,7 +2058,7 @@ function capabilityImports(effect) {
1599
2058
  return [
1600
2059
  'fastly_http_req.new',
1601
2060
  'fastly_http_req.send_async',
1602
- 'fastly_http_req.pending_req_wait',
2061
+ 'fastly_http_req.pending_req_wait_v2',
1603
2062
  ...(effect.kind === 'grip.broadcast' ? ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'] : [])
1604
2063
  ];
1605
2064
  }
@@ -1611,7 +2070,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1611
2070
  validatePlanBoundary(plan, options);
1612
2071
  const facts = packageFacts(plan, options);
1613
2072
  const jwtCrypto = selectedJwtCrypto(plan);
1614
- const es256KeyRecords = jwtCrypto && jwtCrypto.algorithm === 'ES256'
2073
+ const es256KeyRecords = jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1615
2074
  ? resolveEs256KeyArtifacts(plan, facts)
1616
2075
  : Object.freeze([]);
1617
2076
  if (jwtCrypto && jwtCrypto.guestUnitRequired && facts.guestUnits.length !== 1) {
@@ -1634,25 +2093,69 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1634
2093
  }
1635
2094
  );
1636
2095
  }
2096
+ const packageDuration = plan.packages?.application?.requestLimits?.maxDurationMs;
2097
+ if (packageDuration !== undefined) options = { ...options, maxDurationMs: Math.min(options.maxDurationMs ?? packageDuration, packageDuration) };
1637
2098
  const bindings = resolveCapabilityBindings(plan, options);
1638
- const portable = generateCanonicalNativeAssemblyScript(plan, options);
2099
+ // Keep final provider eligibility authoritative over the portable artifact
2100
+ // and caller options. Only the selected crypto guest uses the linked policy;
2101
+ // that policy excludes active codecs regardless of JSON arena configuration.
2102
+ const numericRegions = facts.guestUnits.length === 0 ? 'fastly-numeric-regions-v2'
2103
+ : jwtCrypto && jwtCrypto.guestUnitRequired ? 'fastly-linked-numeric-regions-v1' : undefined;
2104
+ const portable = packageApplication.enabled(plan) ? packageApplication.portable(plan) : generateCanonicalNativeAssemblyScript(plan, {
2105
+ ...options, numericRegions,
2106
+ handleOwnership: facts.guestUnits.length === 0 ? 'fastly-handle-tracing-v1' : undefined
2107
+ });
2108
+ nativeHandleOwnership.validateRoots(plan, portable.manifest);
1639
2109
  const portableSource = stripPulseHostImports(portable.source);
1640
2110
  const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
1641
- const jwtSource = hasJwt ? pulseJwtAssemblyScriptSource() : undefined;
1642
- const source = [
2111
+ const hasSign = plan.effects.some(effect => effect.kind === 'jwt.sign');
2112
+ const jwtSource = hasJwt || hasSign ? pulseJwtAssemblyScriptSource({ includeSigning: hasSign }) : undefined;
2113
+ let source = [
1643
2114
  '/* Generated by Pulse Pass98 Fastly native platform capability compiler. */',
1644
2115
  'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
1645
2116
  '',
1646
2117
  fastlyRuntimeSource(plan, bindings, {
1647
2118
  ...options,
2119
+ handleTracing: portable.manifest.handleOwnership?.enabled === true,
1648
2120
  jwtCrypto,
1649
2121
  es256KeyRecords
1650
2122
  }),
1651
2123
  jwtSource ? jwtSource.source : '',
2124
+ ((plan.effects || []).some(effect => ['fetch', 'grip.publish', 'grip.broadcast'].includes(effect.kind)) || bindings.s3.length > 0)
2125
+ ? fs.readFileSync(path.join(__dirname, 'native-send-error-detail.as.ts'), 'utf8') : '',
2126
+ require('./s3-native.js').s3NativeSource(plan, bindings),
2127
+ 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') : '',
2128
+ require('./time-native.js').timeNativeSource(plan),
2129
+ require('./digest-native.js').digestNativeSource(plan),
2130
+ conditionalKv.kvNativeSource(plan),
2131
+ require('./native-request-body.js').runtimeSource(),
2132
+ require('./native-request-headers.js').runtimeSource(),
2133
+ require('./native-header-lookup.js').runtimeSource(),
2134
+ fs.readFileSync(path.join(__dirname, 'native-transport-details.as.ts'), 'utf8'),
2135
+ require('./native-fetch-errors.js').runtimeSource(dynamicBackends.active(bindings)),
2136
+ require('./native-fetch-deadlines.js').runtimeSource(plan),
2137
+ applicationErrors.settlementEnabled(plan) ? applicationErrors.runtimeSource(plan) : '',
2138
+ effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan) }),
2139
+ ...(portable.manifest.numericRegions?.accounting === 'fastly-read-loop-v1' && portable.manifest.numericRegions.regions.length > 0
2140
+ ? [nativeValueBudget.numericRegionSource()] : []),
1652
2141
  portableSource,
2142
+ nativeHandleOwnership.providerSource(plan, portable.manifest.handleOwnership?.enabled === true),
1653
2143
  driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
1654
2144
  ''
1655
2145
  ].join('\n');
2146
+ if (applicationErrors.settlementEnabled(plan)) source = applicationErrors.instrument(source);
2147
+ source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
2148
+ source = require('./native-header-lookup.js').instrument(source);
2149
+ source = require('./native-fetch-errors.js').instrument(source);
2150
+ source = require('./native-fetch-deadlines.js').instrument(source, plan);
2151
+ source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan));
2152
+ if (packageApplication.enabled(plan)) source = packageApplication.instrument(source);
2153
+ source = effectInvocations.instrument(source);
2154
+ source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan);
2155
+ source = packageApplication.terminalFailure(source, plan);
2156
+ source = nativeValueBudget.instrument(source, plan);
2157
+ source = require('./native-body-forwarding.js').instrument(source, plan, bindings);
2158
+ source = dynamicBackends.instrument(source, bindings, true, (options.bindings || options).backends);
1656
2159
  const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
1657
2160
  output[effect.kind] = (output[effect.kind] || 0) + 1;
1658
2161
  return output;
@@ -1664,7 +2167,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1664
2167
  capability: effect.capability,
1665
2168
  package: effect.package,
1666
2169
  contractId: effect.contractId,
1667
- imports: Object.freeze(capabilityImports(effect))
2170
+ imports: Object.freeze([...capabilityImports(effect), ...(bindings.backends.some(b => b.index === index && b.dynamic) ? ['fastly_http_req.register_dynamic_backend'] : [])])
1668
2171
  })));
1669
2172
  const manifest = Object.freeze({
1670
2173
  version: FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION,
@@ -1676,10 +2179,23 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1676
2179
  portableGeneratorVersion: portable.version,
1677
2180
  portableSourceHash: portable.sourceHash,
1678
2181
  schemaCodecs: portable.manifest.schemaCodecs,
2182
+ ...(portable.manifest.numericRegions ? { numericRegions: portable.manifest.numericRegions } : {}),
2183
+ handleOwnership: portable.manifest.handleOwnership ? Object.freeze({ ...portable.manifest.handleOwnership,
2184
+ providerRoots: portable.manifest.handleOwnership.enabled ? Object.freeze(nativeHandleOwnership.providerRoots(plan)) : Object.freeze([])
2185
+ }) : Object.freeze({
2186
+ policy: null, enabled: false,
2187
+ reason: packageApplication.enabled(plan) ? 'package-owned-emitter' : 'linked-runtime',
2188
+ roots: Object.freeze([]), checkpoints: 'none'
2189
+ }),
2190
+ handlerBodies: portable.manifest.handlerBodies,
2191
+ ...(portable.manifest.reportOwnership ? { reportOwnership: portable.manifest.reportOwnership } : {}),
2192
+ dispatcher: portable.manifest.dispatcher,
2193
+ stages: portable.manifest.stages,
2194
+ helperBodies: portable.manifest.helperBodies,
1679
2195
  crypto: portable.manifest.crypto,
1680
2196
  jwt: jwtSource
1681
2197
  ? Object.freeze({
1682
- status: jwtCrypto && jwtCrypto.algorithm === 'ES256'
2198
+ status: jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
1683
2199
  ? 'implemented-g4'
1684
2200
  : 'implemented-e2',
1685
2201
  package: jwtSource.owner,
@@ -1698,13 +2214,17 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1698
2214
  })
1699
2215
  : Object.freeze({ status: 'inactive' }),
1700
2216
  effectCount: (plan.effects || []).length,
2217
+ effectInvocations: effectInvocations.policy,
1701
2218
  effectKinds,
1702
2219
  continuationCount: (plan.continuations || []).length,
1703
2220
  groupedContinuationCount: (plan.continuations || []).filter((entry) => (entry.effectIds || []).length > 1).length,
1704
2221
  bindings,
1705
2222
  backends: bindings.backends,
1706
2223
  capabilityDiagnostics,
1707
- allowedImports: FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS,
2224
+ ...(dynamicBackends.active(bindings) ? { dynamicBackends: dynamicBackends.describe((options.bindings || options).backends) } : {}),
2225
+ allowedImports: dynamicBackends.active(bindings)
2226
+ ? Object.freeze([...FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS, Object.freeze(['fastly_http_req', 'register_dynamic_backend'])])
2227
+ : FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS,
1708
2228
  requiredImports: requiredImportsForPlan(plan, bindings),
1709
2229
  requiredExports: Object.freeze([
1710
2230
  '_start',
@@ -1712,27 +2232,30 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1712
2232
  'pulse_fastly_error_stage',
1713
2233
  'pulse_fastly_error_effect',
1714
2234
  'pulse_plan_hash_ptr',
1715
- 'pulse_plan_hash_length'
2235
+ 'pulse_plan_hash_length',
2236
+ ...(bindings.maxDurationMs === undefined ? [] : ['pulse_fastly_request_expired'])
1716
2237
  ]),
1717
2238
  policy: Object.freeze({
2239
+ ...(dynamicBackends.active(bindings) ? { dynamicBackends: dynamicBackends.describe((options.bindings || options).backends) } : {}),
2240
+ ...(nativeValueBudget.hasBoundedReadLoop(plan) ? { readLoopMemory: nativeValueBudget.policy } : {}),
1718
2241
  nativeFastly: true,
1719
2242
  provider: 'fastly',
1720
2243
  providerNeutralInput: true,
1721
2244
  javascriptRuntime: false,
1722
2245
  jsComputeRuntime: false,
1723
- wasi: hasJwt ? 'clock_time_get only' : false,
1724
- effects: 'fetch, config.get, secret.get, kv.get, kv.put, assets.lookup, grip.channel, grip.hold, grip.publish, grip.broadcast, jwt.verify',
2246
+ wasi: requiredImportsForPlan(plan, bindings).filter(key => key.startsWith('wasi_snapshot_preview1:')).map(key => key.split(':')[1]).join(', ').replace(/(.+)/, '$1 only') || false,
2247
+ effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
1725
2248
  continuations: true,
1726
2249
  groupedEffects: 'existing fetch start-before-wait ordering remains intact',
1727
2250
  config: 'Fastly Config Store open/get with undefined for missing values',
1728
2251
  secrets: 'Fastly Secret Store open/get/plaintext; proof traces redact plaintext values',
1729
- jwt: hasJwt
1730
- ? `package-owned compact-JWS and registered-claim semantics composed with ${jwtCrypto.realization}; provider-owned request, clock, schema, and transport`
2252
+ jwt: hasJwt || hasSign
2253
+ ? `package-owned compact-JWS, registered-claim, and bounded issuance semantics composed with ${jwtCrypto.realization}; provider-owned request, secret, clock, schema, and transport`
1731
2254
  : 'inactive',
1732
- kv: 'Fastly KV Store async lookup/insert with Pulse JSON envelope compatibility',
2255
+ kv: 'Fastly KV Store async lookup/insert/delete and one bounded list page with opaque cursor; conditional operations preserve u64 generations, bounded readiness, strict Pulse envelopes and uncertain dispatch',
1733
2256
  assets: 'Fastly KV Store lookup with provider-owned content type, cache headers, and GET/HEAD body ownership',
1734
2257
  grip: 'canonical stateless framing plus bound, secret-referenced broadcast HTTP realization and stable acknowledgement',
1735
- logging: 'best-effort redacted writes to the provider-owned stdout logging endpoint',
2258
+ logging: bindings.logEndpoint === undefined ? 'best-effort redacted WASI fd_write to stdout (descriptor 1), one attempt per statement' : 'best-effort named fastly_log endpoint; one lookup and at most one write per statement, 65536 UTF-8 byte maximum, no retries or stdout fallback; downstream delivery unverified',
1736
2259
  schemas: 'fetch JSON decode and structured response encode',
1737
2260
  opaqueResponse: 'origin fetch response/body handles and direct GRIP hold responses pass through without JavaScript runtime ownership',
1738
2261
  transportErrors: 'host status failures remain distinct from HTTP response status',
@@ -1763,8 +2286,9 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1763
2286
  });
1764
2287
  }
1765
2288
 
1766
- function inspectFastlyNativePlatformCapabilitiesWasm(input) {
2289
+ function inspectFastlyNativePlatformCapabilitiesWasm(input, options = {}) {
1767
2290
  const bytes = Buffer.isBuffer(input) ? input : fs.readFileSync(path.resolve(input));
2291
+ assertFastlyWasmBudget(bytes.length, options.maxWasmBytes);
1768
2292
  if (!WebAssembly.validate(bytes)) fail('Generated Fastly native platform capabilities module is not valid WebAssembly.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_INVALID');
1769
2293
  const module = new WebAssembly.Module(bytes);
1770
2294
  const imports = WebAssembly.Module.imports(module).map((entry) => Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind }));
@@ -1780,7 +2304,6 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1780
2304
  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'];
1781
2305
  const missingExports = requiredExports.filter((name) => !exportNames.has(name));
1782
2306
  if (missingExports.length > 0) fail('Generated Fastly native platform capabilities module is missing required exports.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_EXPORT_MISSING', { missingExports, exports });
1783
- 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 });
1784
2307
  return Object.freeze({
1785
2308
  valid: true,
1786
2309
  bytes: bytes.length,
@@ -1793,6 +2316,7 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
1793
2316
  }
1794
2317
 
1795
2318
  function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2319
+ const maxWasmBytes = normalizeFastlyMaxWasmBytes(options.maxWasmBytes);
1796
2320
  const generated = generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options);
1797
2321
  const cwd = path.resolve(options.cwd || process.cwd());
1798
2322
  const compilerFallback = path.resolve(__dirname, '..', '..', '..', '..', 'wasm', 'packages', 'compiler');
@@ -1825,6 +2349,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1825
2349
  }
1826
2350
  jsonAs = Object.freeze({ transform, dependencyRoot, version: packageJson.version });
1827
2351
  }
2352
+ if (options.emitWat !== undefined && typeof options.emitWat !== 'boolean') throw new TypeError('emitWat must be a boolean.');
2353
+ const emitWat = options.emitWat === true;
1828
2354
  const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-fastly-native-platform-'));
1829
2355
  const requestedOutDir = options.outDir ? path.resolve(options.outDir) : undefined;
1830
2356
  const outputDir = requestedOutDir || stagingDir;
@@ -1832,6 +2358,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1832
2358
  const sourceFile = path.join(stagingDir, 'fastly-native-platform-capabilities.as.ts');
1833
2359
  const wasmFile = path.join(outputDir, options.wasmFile || 'fastly-native-platform-capabilities.wasm');
1834
2360
  const watFile = path.join(outputDir, options.watFile || 'fastly-native-platform-capabilities.wat');
2361
+ if (!emitWat) fs.rmSync(watFile, { force: true });
1835
2362
  const guestLinked = generated.guestUnits.length > 0;
1836
2363
  // asc resolves custom runtimes like module names and appends ".ts".
1837
2364
  const fixedMemoryRuntimeModule = path.join(__dirname, 'fixed-memory-runtime.as');
@@ -1848,14 +2375,17 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1848
2375
  asc.script,
1849
2376
  path.basename(sourceFile),
1850
2377
  '--outFile', assemblyScriptWasmFile,
1851
- '--textFile', assemblyScriptWatFile,
1852
2378
  '--runtime', guestLinked
1853
2379
  ? fixedMemoryRuntimeModule
1854
2380
  : (schemaCodecsActive ? 'incremental' : 'stub'),
1855
2381
  '--noAssert',
1856
2382
  '--optimize'
1857
2383
  ];
1858
- const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization);
2384
+ if (emitWat) args.push('--textFile', assemblyScriptWatFile);
2385
+ const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization, { guestLinked });
2386
+ if (!guestLinked && (plan.callables?.length || nativeValueBudget.hasBoundedReadLoop(plan) || plan.effects.some(effect => effect.kind === 'crypto.digestText' || effect.kind.startsWith('s3.')))) {
2387
+ args.push('--maximumMemory', String(nativeValueBudget.policy.maximumMemoryPages));
2388
+ }
1859
2389
  if (generated.guestUnits.length > 0) {
1860
2390
  args.push(
1861
2391
  '--disable', 'bulk-memory',
@@ -1878,17 +2408,20 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1878
2408
  '--path', jsonAs.dependencyRoot
1879
2409
  );
1880
2410
  }
1881
- const startedAt = Date.now();
2411
+ require('@pulse-compute/wasm-build-support/native-optimization').appendJsonCacheArenaArgs(args, options.elideJsonCacheArena, schemaCodecsActive);
2412
+ const readReportCapture = options.reportCapture === false ? () => null : prepareReportCapture(args, stagingDir, generated.manifest,
2413
+ require(path.join(asc.packageRoot, 'package.json')).version, 'fastly-native-platform-capabilities.as/');
2414
+ const startedAt = process.hrtime.bigint();
1882
2415
  const result = spawnSync(asc.executable, args, {
1883
2416
  cwd: stagingDir,
1884
2417
  encoding: 'utf8',
1885
2418
  maxBuffer: 64 * 1024 * 1024,
1886
2419
  timeout: Number(options.timeoutMs || 180000),
1887
2420
  env: schemaCodecsActive
1888
- ? { ...process.env, JSON_STRICT: 'true', JSON_USE_FAST_PATH: '0' }
2421
+ ? { ...process.env, JSON_STRICT: 'true', JSON_USE_FAST_PATH: '0', JSON_MODE: 'NAIVE' }
1889
2422
  : process.env
1890
2423
  });
1891
- const assemblyScriptDurationMs = Date.now() - startedAt;
2424
+ const assemblyScriptDurationMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
1892
2425
  if (result.error || result.status !== 0 || !fs.existsSync(assemblyScriptWasmFile)) {
1893
2426
  fail('AssemblyScript failed to compile the Fastly native platform capabilities module.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_ASC_FAILED', {
1894
2427
  status: result.status,
@@ -1932,7 +2465,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1932
2465
  failureMessage: 'Fastly Native start wrapper composition failed.'
1933
2466
  }
1934
2467
  );
1935
- runGuestTool(
2468
+ if (emitWat) runGuestTool(
1936
2469
  'wasm-dis',
1937
2470
  [wasmFile, '--mvp-features', '-o', watFile],
1938
2471
  {
@@ -1962,7 +2495,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1962
2495
  });
1963
2496
  }
1964
2497
  const primaryWasm = fs.readFileSync(wasmFile);
1965
- const primaryWat = fs.existsSync(watFile) ? fs.readFileSync(watFile, 'utf8') : '';
2498
+ const primaryWat = emitWat ? fs.readFileSync(watFile, 'utf8') : '';
1966
2499
  const primaryArtifact = Object.freeze({
1967
2500
  bytes: primaryWasm.length,
1968
2501
  sha256: sha256(primaryWasm)
@@ -1995,7 +2528,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1995
2528
  realization = Object.freeze({
1996
2529
  ...realization,
1997
2530
  wasm: stageResult.wasm,
1998
- wat: stageResult.wat,
2531
+ // Required guest-link disassembly audits remain enabled.
2532
+ wat: emitWat ? stageResult.wat : '',
1999
2533
  guestUnits: stageResult.guestUnits,
2000
2534
  guestLink: Object.freeze({
2001
2535
  version: stageResult.version,
@@ -2013,9 +2547,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2013
2547
  const wat = realization.wat;
2014
2548
  if (requestedOutDir && generated.guestUnits.length > 0) {
2015
2549
  fs.writeFileSync(wasmFile, wasm);
2016
- fs.writeFileSync(watFile, wat, 'utf8');
2550
+ if (emitWat) fs.writeFileSync(watFile, wat, 'utf8');
2017
2551
  }
2018
- const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm);
2552
+ const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm, { maxWasmBytes });
2019
2553
  const imported = new Set(inspection.imports.map((entry) => `${entry.module}:${entry.name}`));
2020
2554
  const requiredImports = requiredImportsForPlan(plan, generated.bindings);
2021
2555
  const missingRequiredImports = requiredImports.filter((key) => !imported.has(key));
@@ -2025,11 +2559,13 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2025
2559
  compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
2026
2560
  assemblyScript: Object.freeze({ package: 'assemblyscript', version: require(path.join(asc.packageRoot, 'package.json')).version }),
2027
2561
  jsonAs: jsonAs
2028
- ? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true })
2562
+ ? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true, mode: 'NAIVE', fastPath: false, elideDisabledCacheArena: options.elideJsonCacheArena === true })
2029
2563
  : undefined,
2030
2564
  optimization,
2031
2565
  wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
2032
- wat: Object.freeze({ bytes: Buffer.byteLength(wat), sha256: sha256(wat) }),
2566
+ wat: Object.freeze(emitWat
2567
+ ? { emitted: true, bytes: Buffer.byteLength(wat), sha256: sha256(wat) }
2568
+ : { emitted: false, bytes: 0, sha256: null }),
2033
2569
  importModules: inspection.importModules,
2034
2570
  imports: inspection.imports,
2035
2571
  requiredImports,
@@ -2043,10 +2579,17 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2043
2579
  providerPackaging: realization.guestLink.providerPackaging
2044
2580
  } : {})
2045
2581
  });
2046
- const durationMs = Date.now() - startedAt;
2582
+ const durationMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
2047
2583
  return Object.freeze({
2048
2584
  version: FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION,
2049
2585
  compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
2586
+ reportAttribution: readReportCapture(wasm),
2587
+ buildObservation: require('@pulse-compute/wasm-build-support/native-optimization').observeAssemblyScript(args, {
2588
+ version: require(path.join(asc.packageRoot, 'package.json')).version,
2589
+ assemblyScriptRoot: asc.packageRoot, timeoutMs: Number(options.timeoutMs || 180000),
2590
+ compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
2591
+ durationMs: assemblyScriptDurationMs, jsonAsVersion: jsonAs && jsonAs.version
2592
+ }),
2050
2593
  plan,
2051
2594
  generated,
2052
2595
  source: generated.source,
@@ -2062,7 +2605,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2062
2605
  assemblyScriptDurationMs,
2063
2606
  primaryArtifact,
2064
2607
  startWrapper,
2065
- output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile }) : undefined
2608
+ output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile: emitWat ? watFile : null }) : undefined
2066
2609
  });
2067
2610
  } finally {
2068
2611
  fs.rmSync(stagingDir, { recursive: true, force: true });
@@ -2080,10 +2623,12 @@ function writeFastlyNativePlatformCapabilitiesModule(compiled, outDir, options =
2080
2623
  const manifestFile = path.join(target, options.manifestFile || 'fastly-native-platform-capabilities-manifest.json');
2081
2624
  fs.writeFileSync(sourceFile, compiled.source, 'utf8');
2082
2625
  fs.writeFileSync(wasmFile, compiled.wasm);
2083
- fs.writeFileSync(watFile, compiled.wat, 'utf8');
2626
+ const emitWat = compiled.manifest.wat.emitted !== false;
2627
+ if (emitWat) fs.writeFileSync(watFile, compiled.wat, 'utf8');
2628
+ else fs.rmSync(watFile, { force: true });
2084
2629
  fs.writeFileSync(planFile, `${stableStringify(compiled.plan, 2)}\n`, 'utf8');
2085
2630
  fs.writeFileSync(manifestFile, `${stableStringify(compiled.manifest, 2)}\n`, 'utf8');
2086
- return Object.freeze({ target, sourceFile, wasmFile, watFile, planFile, manifestFile, manifest: compiled.manifest });
2631
+ return Object.freeze({ target, sourceFile, wasmFile, watFile: emitWat ? watFile : null, planFile, manifestFile, manifest: compiled.manifest });
2087
2632
  }
2088
2633
 
2089
2634
  module.exports = Object.freeze({